John Vandenberg has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/181127

Change subject: API support for datetime.datetime
......................................................................

API support for datetime.datetime

A class Timestamp is a subclass of datetime.datetime that has
extra functionality used to convert to and from the two datestamp
formats used by MediaWiki.  Timestamp.toISOformat outputs the
datetime in the format required for parameters to the MediaWiki API,
which is ISO 8601 with additional requirements, such as the 'Z' must
exist even though the timezone info is not included.

Timestamp.__str__ is an alias for Timestamp.toISOformat, and was used
when building API requests using str(timestamp).

Rather than require a Timestamp and implicitly use its __str__ to
obtain the required formatted string, api.py now supports datetime
directly and explicitly formats it using a format string to
obtain the representation the API requires for parameters.

Timestamp.isoformat now performs the same formatting, meaning its
output is slightly different that of the superclass, however they
are both valid ISO 8601 representations.

Timestamp.toISOformat is deprecated.

Change-Id: I6b975946bffd92f06dba53206f9097b573a0803d
---
M pywikibot/__init__.py
M pywikibot/data/api.py
M pywikibot/site.py
M tests/site_tests.py
M tests/timestamp_tests.py
5 files changed, 91 insertions(+), 66 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core 
refs/changes/27/181127/1

diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 1cf0436..40dde82 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -125,13 +125,17 @@
     mediawikiTSFormat = "%Y%m%d%H%M%S"
     ISO8601Format = "%Y-%m-%dT%H:%M:%SZ"
 
+    def clone(self):
+        """Clone this instance."""
+        return self.replace(microsecond=self.microsecond)
+
     @classmethod
     def fromISOformat(cls, ts):
         """Convert an ISO 8601 timestamp to a Timestamp object."""
         # If inadvertantly passed a Timestamp object, use replace()
         # to create a clone.
         if isinstance(ts, cls):
-            return ts.replace(microsecond=ts.microsecond)
+            return ts.clone()
         return cls.strptime(ts, cls.ISO8601Format)
 
     @classmethod
@@ -140,12 +144,21 @@
         # If inadvertantly passed a Timestamp object, use replace()
         # to create a clone.
         if isinstance(ts, cls):
-            return ts.replace(microsecond=ts.microsecond)
+            return ts.clone()
         return cls.strptime(ts, cls.mediawikiTSFormat)
 
-    def toISOformat(self):
-        """Convert object to an ISO 8601 timestamp."""
+    def isoformat(self):
+        """
+        Convert object to an ISO 8601 timestamp accepted by MediaWiki.
+
+        datetime.datetime.isoformat does not postfix the ISO formatted date
+        with a 'Z' unless a timezone is included, which causes MediaWiki
+        ~1.19 and earlier to fail.
+        """
         return self.strftime(self.ISO8601Format)
+
+    toISOformat = redirect_func(isoformat, old_name='toISOformat',
+                                class_name='Timestamp')
 
     def totimestampformat(self):
         """Convert object to a MediaWiki internal timestamp."""
@@ -153,7 +166,7 @@
 
     def __str__(self):
         """Return a string format recognized by the API."""
-        return self.toISOformat()
+        return self.isoformat()
 
     def __add__(self, other):
         """Perform addition, returning a Timestamp instead of datetime."""
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 5b70fe1..2f14676 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -609,9 +609,12 @@
 
         @param key: param key
         @type key: basestring
-        @param value: param value
-        @type value: list of unicode, unicode, or str in site encoding
-            Any string type may use a |-separated list
+        @param value: param value(s)
+        @type value: unicode or str in site encoding
+            (string types may be a |-separated list)
+            iterable, where items are converted to unicode
+            with special handling for datetime.datetime to convert it to a
+            string using the ISO 8601 format accepted by the MediaWiki API.
         """
         # Allow site encoded bytes (note: str is a subclass of bytes in py2)
         if isinstance(value, bytes):
@@ -622,11 +625,18 @@
 
         try:
             iter(value)
+            values = value
         except TypeError:
             # convert any non-iterable value into a single-element list
-            value = [unicode(value)]
+            values = [value]
 
-        self._params[key] = value
+        normalised_values = [
+            item.strftime(pywikibot.Timestamp.ISO8601Format)
+            if isinstance(item, datetime.datetime)
+            else unicode(item)
+            for item in values]
+
+        self._params[key] = normalised_values
 
     def __delitem__(self, key):
         del self._params[key]
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 8f82568..39e2e39 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -2864,12 +2864,12 @@
             (starttime, endtime) = (endtime, starttime)
             (startsort, endsort) = (endsort, startsort)
         if starttime and sortby == "timestamp":
-            cmargs["gcmstart"] = str(starttime)
+            cmargs["gcmstart"] = starttime
         elif starttime:
             raise ValueError("categorymembers: "
                              "invalid combination of 'sortby' and 'starttime'")
         if endtime and sortby == "timestamp":
-            cmargs["gcmend"] = str(endtime)
+            cmargs["gcmend"] = endtime
         elif endtime:
             raise ValueError("categorymembers: "
                              "invalid combination of 'sortby' and 'endtime'")
@@ -2987,9 +2987,9 @@
         if endid:
             rvargs[u"rvendid"] = endid
         if starttime:
-            rvargs[u"rvstart"] = str(starttime)
+            rvargs[u"rvstart"] = starttime
         if endtime:
-            rvargs[u"rvend"] = str(endtime)
+            rvargs[u"rvend"] = endtime
         if user:
             rvargs[u"rvuser"] = user
         elif excludeuser:
@@ -3352,9 +3352,9 @@
                                 step=step, total=total)
         bkgen.request["bkprop"] = 
"id|user|by|timestamp|expiry|reason|range|flags"
         if starttime:
-            bkgen.request["bkstart"] = str(starttime)
+            bkgen.request["bkstart"] = starttime
         if endtime:
-            bkgen.request["bkend"] = str(endtime)
+            bkgen.request["bkend"] = endtime
         if reverse:
             bkgen.request["bkdir"] = "newer"
         if blockids:
@@ -3429,9 +3429,9 @@
         if page is not None:
             legen.request["letitle"] = page.title(withSection=False)
         if start is not None:
-            legen.request["lestart"] = str(start)
+            legen.request["lestart"] = start
         if end is not None:
-            legen.request["leend"] = str(end)
+            legen.request["leend"] = end
         if reverse:
             legen.request["ledir"] = "newer"
         if namespace:
@@ -3490,9 +3490,9 @@
                                 namespaces=namespaces, step=step,
                                 total=total)
         if start is not None:
-            rcgen.request["rcstart"] = str(start)
+            rcgen.request["rcstart"] = start
         if end is not None:
-            rcgen.request["rcend"] = str(end)
+            rcgen.request["rcend"] = end
         if reverse:
             rcgen.request["rcdir"] = "newer"
         if pagelist:
@@ -3641,9 +3641,9 @@
                                 step=step, total=total)
         # TODO: allow users to ask for "patrol" as well?
         if start is not None:
-            wlgen.request["wlstart"] = str(start)
+            wlgen.request["wlstart"] = start
         if end is not None:
-            wlgen.request["wlend"] = str(end)
+            wlgen.request["wlend"] = end
         if reverse:
             wlgen.request["wldir"] = "newer"
         filters = {'minor': showMinor,
@@ -3710,9 +3710,9 @@
             drgen.request['drprop'] = (drgen.request['drprop'] +
                                        "|content|token")
         if start is not None:
-            drgen.request["drstart"] = str(start)
+            drgen.request["drstart"] = start
         if end is not None:
-            drgen.request["drend"] = str(end)
+            drgen.request["drend"] = end
         if reverse:
             drgen.request["drdir"] = "newer"
         return drgen
@@ -4206,8 +4206,6 @@
                           protections=protectList,
                           reason=reason,
                           **kwargs)
-        if isinstance(expiry, pywikibot.Timestamp):
-            expiry = expiry.toISOformat()
         if expiry:
             req['expiry'] = expiry
         try:
@@ -4325,8 +4323,6 @@
                   autoblock=True, noemail=False, reblock=False):
 
         token = self.tokens['block']
-        if isinstance(expiry, pywikibot.Timestamp):
-            expiry = expiry.toISOformat()
         req = api.Request(site=self, action='block', user=user.username,
                           expiry=expiry, reason=reason, token=token)
         if anononly:
diff --git a/tests/site_tests.py b/tests/site_tests.py
index 406e969..9897e2f 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -607,34 +607,34 @@
         for t in range(1, len(timestamps)):
             self.assertGreaterEqual(timestamps[t], timestamps[t - 1])
 
-        for block in mysite.blocks(starttime="2008-07-01T00:00:01Z", total=5):
+        for block in 
mysite.blocks(starttime=pywikibot.Timestamp.fromISOformat("2008-07-01T00:00:01Z"),
 total=5):
             self.assertIsInstance(block, dict)
             for prop in props:
                 self.assertIn(prop, block)
-        for block in mysite.blocks(endtime="2008-07-31T23:59:59Z", total=5):
+        for block in 
mysite.blocks(endtime=pywikibot.Timestamp.fromISOformat("2008-07-31T23:59:59Z"),
 total=5):
             self.assertIsInstance(block, dict)
             for prop in props:
                 self.assertIn(prop, block)
-        for block in mysite.blocks(starttime="2008-08-02T00:00:01Z",
-                                   endtime="2008-08-02T23:59:59Z",
+        for block in 
mysite.blocks(starttime=pywikibot.Timestamp.fromISOformat("2008-08-02T00:00:01Z"),
+                                   
endtime=pywikibot.Timestamp.fromISOformat("2008-08-02T23:59:59Z"),
                                    reverse=True, total=5):
             self.assertIsInstance(block, dict)
             for prop in props:
                 self.assertIn(prop, block)
-        for block in mysite.blocks(starttime="2008-08-03T23:59:59Z",
-                                   endtime="2008-08-03T00:00:01Z",
+        for block in 
mysite.blocks(starttime=pywikibot.Timestamp.fromISOformat("2008-08-03T23:59:59Z"),
+                                   
endtime=pywikibot.Timestamp.fromISOformat("2008-08-03T00:00:01Z"),
                                    total=5):
             self.assertIsInstance(block, dict)
             for prop in props:
                 self.assertIn(prop, block)
         # starttime earlier than endtime
         self.assertRaises(pywikibot.Error, mysite.blocks,
-                          starttime="2008-08-03T00:00:01Z",
-                          endtime="2008-08-03T23:59:59Z", total=5)
+                          
starttime=pywikibot.Timestamp.fromISOformat("2008-08-03T00:00:01Z"),
+                          
endtime=pywikibot.Timestamp.fromISOformat("2008-08-03T23:59:59Z"), total=5)
         # reverse: endtime earlier than starttime
         self.assertRaises(pywikibot.Error, mysite.blocks,
-                          starttime="2008-08-03T23:59:59Z",
-                          endtime="2008-08-03T00:00:01Z", reverse=True, 
total=5)
+                          
starttime=pywikibot.Timestamp.fromISOformat("2008-08-03T23:59:59Z"),
+                          
endtime=pywikibot.Timestamp.fromISOformat("2008-08-03T00:00:01Z"), 
reverse=True, total=5)
         for block in mysite.blocks(users='80.100.22.71', total=5):
             self.assertIsInstance(block, dict)
             self.assertEqual(block['user'], '80.100.22.71')
@@ -774,32 +774,32 @@
             self.assertEqual(entry.title().title(), mainpage.title())
         for entry in mysite.logevents(user=mysite.user(), total=3):
             self.assertEqual(entry.user(), mysite.user())
-        for entry in mysite.logevents(start="2008-09-01T00:00:01Z", total=5):
+        for entry in 
mysite.logevents(start=pywikibot.Timestamp.fromISOformat("2008-09-01T00:00:01Z"),
 total=5):
             self.assertIsInstance(entry, pywikibot.logentries.LogEntry)
             self.assertLessEqual(str(entry.timestamp()), 
"2008-09-01T00:00:01Z")
-        for entry in mysite.logevents(end="2008-09-02T23:59:59Z", total=5):
+        for entry in 
mysite.logevents(end=pywikibot.Timestamp.fromISOformat("2008-09-02T23:59:59Z"), 
total=5):
             self.assertIsInstance(entry, pywikibot.logentries.LogEntry)
             self.assertGreaterEqual(str(entry.timestamp()), 
"2008-09-02T23:59:59Z")
-        for entry in mysite.logevents(start="2008-02-02T00:00:01Z",
-                                      end="2008-02-02T23:59:59Z",
+        for entry in 
mysite.logevents(start=pywikibot.Timestamp.fromISOformat("2008-02-02T00:00:01Z"),
+                                      
end=pywikibot.Timestamp.fromISOformat("2008-02-02T23:59:59Z"),
                                       reverse=True, total=5):
             self.assertIsInstance(entry, pywikibot.logentries.LogEntry)
             self.assertTrue(
                 "2008-02-02T00:00:01Z" <= str(entry.timestamp()) <= 
"2008-02-02T23:59:59Z")
-        for entry in mysite.logevents(start="2008-02-03T23:59:59Z",
-                                      end="2008-02-03T00:00:01Z",
+        for entry in 
mysite.logevents(start=pywikibot.Timestamp.fromISOformat("2008-02-03T23:59:59Z"),
+                                      
end=pywikibot.Timestamp.fromISOformat("2008-02-03T00:00:01Z"),
                                       total=5):
             self.assertIsInstance(entry, pywikibot.logentries.LogEntry)
             self.assertTrue(
                 "2008-02-03T00:00:01Z" <= str(entry.timestamp()) <= 
"2008-02-03T23:59:59Z")
         # starttime earlier than endtime
         self.assertRaises(pywikibot.Error, mysite.logevents,
-                          start="2008-02-03T00:00:01Z",
-                          end="2008-02-03T23:59:59Z", total=5)
+                          
start=pywikibot.Timestamp.fromISOformat("2008-02-03T00:00:01Z"),
+                          
end=pywikibot.Timestamp.fromISOformat("2008-02-03T23:59:59Z"), total=5)
         # reverse: endtime earlier than starttime
         self.assertRaises(pywikibot.Error, mysite.logevents,
-                          start="2008-02-03T23:59:59Z",
-                          end="2008-02-03T00:00:01Z", reverse=True, total=5)
+                          
start=pywikibot.Timestamp.fromISOformat("2008-02-03T23:59:59Z"),
+                          
end=pywikibot.Timestamp.fromISOformat("2008-02-03T00:00:01Z"), reverse=True, 
total=5)
 
     def testRecentchanges(self):
         """Test the site.recentchanges() method."""
@@ -815,30 +815,30 @@
         self.assertLessEqual(len(rc), 10)
         self.assertTrue(all(isinstance(change, dict)
                             for change in rc))
-        for change in mysite.recentchanges(start="2008-10-01T01:02:03Z",
+        for change in 
mysite.recentchanges(start=pywikibot.Timestamp.fromISOformat("2008-10-01T01:02:03Z"),
                                            total=5):
             self.assertIsInstance(change, dict)
             self.assertLessEqual(change['timestamp'], "2008-10-01T01:02:03Z")
-        for change in mysite.recentchanges(end="2008-04-01T02:03:04Z",
+        for change in 
mysite.recentchanges(end=pywikibot.Timestamp.fromISOformat("2008-04-01T02:03:04Z"),
                                            total=5):
             self.assertIsInstance(change, dict)
             self.assertGreaterEqual(change['timestamp'], 
"2008-10-01T02:03:04Z")
-        for change in mysite.recentchanges(start="2008-10-01T03:05:07Z",
+        for change in 
mysite.recentchanges(start=pywikibot.Timestamp.fromISOformat("2008-10-01T03:05:07Z"),
                                            total=5, reverse=True):
             self.assertIsInstance(change, dict)
             self.assertGreaterEqual(change['timestamp'], 
"2008-10-01T03:05:07Z")
-        for change in mysite.recentchanges(end="2008-10-01T04:06:08Z",
+        for change in 
mysite.recentchanges(end=pywikibot.Timestamp.fromISOformat("2008-10-01T04:06:08Z"),
                                            total=5, reverse=True):
             self.assertIsInstance(change, dict)
             self.assertLessEqual(change['timestamp'], "2008-10-01T04:06:08Z")
-        for change in mysite.recentchanges(start="2008-10-03T11:59:59Z",
-                                           end="2008-10-03T00:00:01Z",
+        for change in 
mysite.recentchanges(start=pywikibot.Timestamp.fromISOformat("2008-10-03T11:59:59Z"),
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-03T00:00:01Z"),
                                            total=5):
             self.assertIsInstance(change, dict)
             self.assertTrue(
                 "2008-10-03T00:00:01Z" <= change['timestamp'] <= 
"2008-10-03T11:59:59Z")
-        for change in mysite.recentchanges(start="2008-10-05T06:00:01Z",
-                                           end="2008-10-05T23:59:59Z",
+        for change in 
mysite.recentchanges(start=pywikibot.Timestamp.fromISOformat("2008-10-05T06:00:01Z"),
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-05T23:59:59Z"),
                                            reverse=True, total=5):
             self.assertIsInstance(change, dict)
             self.assertTrue(
@@ -849,8 +849,8 @@
                           end="2008-02-03T23:59:59Z", total=5)
         # reverse: end earlier than start
         self.assertRaises(pywikibot.Error, mysite.recentchanges,
-                          start="2008-02-03T23:59:59Z",
-                          end="2008-02-03T00:00:01Z", reverse=True, total=5)
+                          
start=pywikibot.Timestamp.fromISOformat("2008-02-03T23:59:59Z"),
+                          
end=pywikibot.Timestamp.fromISOformat("2008-02-03T00:00:01Z"), reverse=True, 
total=5)
         for change in mysite.recentchanges(namespaces=[6, 7], total=5):
             self.assertIsInstance(change, dict)
             self.assertTrue("title" in change and "ns" in change)
@@ -948,30 +948,30 @@
                 self.assertIn(key, contrib)
             self.assertTrue(contrib["user"].startswith("John"))
         for contrib in mysite.usercontribs(userprefix="Jane",
-                                           start="2008-10-06T01:02:03Z",
+                                           
start=pywikibot.Timestamp.fromISOformat("2008-10-06T01:02:03Z"),
                                            total=5):
             self.assertLessEqual(contrib['timestamp'], "2008-10-06T01:02:03Z")
         for contrib in mysite.usercontribs(userprefix="Jane",
-                                           end="2008-10-07T02:03:04Z",
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-07T02:03:04Z"),
                                            total=5):
             self.assertGreaterEqual(contrib['timestamp'], 
"2008-10-07T02:03:04Z")
         for contrib in mysite.usercontribs(userprefix="Brion",
-                                           start="2008-10-08T03:05:07Z",
+                                           
start=pywikibot.Timestamp.fromISOformat("2008-10-08T03:05:07Z"),
                                            total=5, reverse=True):
             self.assertGreaterEqual(contrib['timestamp'], 
"2008-10-08T03:05:07Z")
         for contrib in mysite.usercontribs(userprefix="Brion",
-                                           end="2008-10-09T04:06:08Z",
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-09T04:06:08Z"),
                                            total=5, reverse=True):
             self.assertLessEqual(contrib['timestamp'], "2008-10-09T04:06:08Z")
         for contrib in mysite.usercontribs(userprefix="Tim",
-                                           start="2008-10-10T11:59:59Z",
-                                           end="2008-10-10T00:00:01Z",
+                                           
start=pywikibot.Timestamp.fromISOformat("2008-10-10T11:59:59Z"),
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-10T00:00:01Z"),
                                            total=5):
             self.assertTrue(
                 "2008-10-10T00:00:01Z" <= contrib['timestamp'] <= 
"2008-10-10T11:59:59Z")
         for contrib in mysite.usercontribs(userprefix="Tim",
-                                           start="2008-10-11T06:00:01Z",
-                                           end="2008-10-11T23:59:59Z",
+                                           
start=pywikibot.Timestamp.fromISOformat("2008-10-11T06:00:01Z"),
+                                           
end=pywikibot.Timestamp.fromISOformat("2008-10-11T23:59:59Z"),
                                            reverse=True, total=5):
             self.assertTrue(
                 "2008-10-11T06:00:01Z" <= contrib['timestamp'] <= 
"2008-10-11T23:59:59Z")
diff --git a/tests/timestamp_tests.py b/tests/timestamp_tests.py
index a2dd8b8..362247b 100644
--- a/tests/timestamp_tests.py
+++ b/tests/timestamp_tests.py
@@ -21,6 +21,12 @@
 
     net = False
 
+    def test_clone(self):
+        t1 = T.utcnow()
+        t2 = t1.clone()
+        self.assertEqual(t1, t2)
+        self.assertIsInstance(t2, T)
+
     def test_instantiate_from_instance(self):
         """Test passing instance to factory methods works."""
         t1 = T.utcnow()

-- 
To view, visit https://gerrit.wikimedia.org/r/181127
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6b975946bffd92f06dba53206f9097b573a0803d
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to