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