*needs-reply (Edwin) This seems like a very nice new feature. I tried to run the tests with nose, as you implied might work. At least with a simple run, nose fails a few tests because it does not recognize (is not told that) the ELLIPSIS and NORMALIZE_WHITESPACE flags are supposed to be on. This would be fixed with making wadllib use zc.buildout and the zope testrunner, like the lazr packages. Barring objections, I may do that soonish. For now, I think having the tests run in the Launchpad testrunner is OK. Comments below. > === modified file 'wadllib/application.py' > --- wadllib/application.py 2008-08-13 13:26:16 +0000 > +++ wadllib/application.py 2009-01-21 19:58:38 +0000 > @@ -43,6 +43,8 @@ > 'WADLError', > ] > > +import datetime > +import time > import urllib > import simplejson > try: > @@ -52,6 +54,8 @@ > import cElementTree as ET > except ImportError: > import elementtree.ElementTree as ET > + > +from iso_strptime import iso_strptime > from wadllib._utils import uri > > def wadl_tag(tag_name): > @@ -424,7 +428,22 @@ > raise NotImplementedError( > "Don't know how to find value for a parameter of " > "type %s." % parameter.style) > - return self.representation[parameter.name] > + value = self.representation[parameter.name] > + if value is not None: > + if parameter.type in ['xsd:dateTime', 'xsd:date']: As you said in the cover letter, it would be nice if this were using proper namespacing. In the "Parsing with Prefixes" section of http://effbot.org/zone/element-namespaces.htm that doesn't look too bad--at least he seems to have a recipe for an almost-identical situation. Perhaps I suggest looking that over and seeing if that can be incorporated quickly? If not, I'm OK with this for now, particularly if there is an XXX and a bug number. > + try: > + # Parse it as an ISO 1601 date and time. > + value = iso_strptime(value) > + except ValueError: > + # Parse it as an ISO 1601 date. > + try: > + value = datetime.datetime( > + *(time.strptime(value, "%Y-%m-%d")[0:6])) > + except ValueError: > + # Oh well. Leave it as a string. > + pass Does wadllib have an error channel for this sort of thing? logging seems minimally appropriate. Alternatively, I think raising an error would be appropriate, and more "Pythonic". A strawman: if the value is None, you leave it alone. If the value parses to a datetime, you parse it. If it is a string that doesn't parse (or anything else), you raise an error, with the bad value as an attribute. If wadl allows you to specify whether a value may be None, then that could be a better, tighter constraint (that is, if it can be None, let it through; otherwise raise an error). That's what I would recommend and prefer. > + > + return value > > raise NotImplementedError("Path traversal not implemented for " > "a representation of media type %s." > @@ -786,6 +805,11 @@ > return self.tag.attrib.get('style') > > @property > + def type(self): > + """The XSD type of this parameter.""" > + return self.tag.attrib.get('type') > + > + @property > def fixed_value(self): > """The value to which this parameter is fixed, if any. > > @@ -935,7 +959,7 @@ > # representation of a non-root resource to its definition at > # the server root. > raise NotImplementedError("Can't look up definition in another " > - "url (%s)" % url) > + "url (%s)" % (url)) This doesn't accomplish anything, because (string) == string. Because strings are iterable in Python and string interpolation can get confused, I usually use (string,), that is, a tuple of one value. Is that what is intended here? > > def get_resource_by_path(self, path): > """Locate one of the resources described by this document. > > === removed file 'wadllib/diff' > --- wadllib/diff 2008-08-01 15:07:59 +0000 > +++ wadllib/diff 1970-01-01 00:00:00 +0000 > @@ -1,327 +0,0 @@ > -=== added file 'COPYING.txt' > ---- COPYING.txt 1970-01-01 00:00:00 +0000 > -+++ COPYING.txt 2008-08-01 14:41:07 +0000 > -@@ -0,0 +1,165 @@ > -+ GNU LESSER GENERAL PUBLIC LICENSE > -+ Version 3, 29 June 2007 > -+ > -+ Copyright (C) 2007 Free Software Foundation, Inc. > -+ Everyone is permitted to copy and distribute verbatim copies > -+ of this license document, but changing it is not allowed. > -+ > -+ > -+ This version of the GNU Lesser General Public License incorporates > -+the terms and conditions of version 3 of the GNU General Public > -+License, supplemented by the additional permissions listed below. > -+ > -+ 0. Additional Definitions. > -+ > -+ As used herein, "this License" refers to version 3 of the GNU Lesser > -+General Public License, and the "GNU GPL" refers to version 3 of the GNU > -+General Public License. > -+ > -+ "The Library" refers to a covered work governed by this License, > -+other than an Application or a Combined Work as defined below. > -+ > -+ An "Application" is any work that makes use of an interface provided > -+by the Library, but which is not otherwise based on the Library. > -+Defining a subclass of a class defined by the Library is deemed a mode > -+of using an interface provided by the Library. > -+ > -+ A "Combined Work" is a work produced by combining or linking an > -+Application with the Library. The particular version of the Library > -+with which the Combined Work was made is also called the "Linked > -+Version". > -+ > -+ The "Minimal Corresponding Source" for a Combined Work means the > -+Corresponding Source for the Combined Work, excluding any source code > -+for portions of the Combined Work that, considered in isolation, are > -+based on the Application, and not on the Linked Version. > -+ > -+ The "Corresponding Application Code" for a Combined Work means the > -+object code and/or source code for the Application, including any data > -+and utility programs needed for reproducing the Combined Work from the > -+Application, but excluding the System Libraries of the Combined Work. > -+ > -+ 1. Exception to Section 3 of the GNU GPL. > -+ > -+ You may convey a covered work under sections 3 and 4 of this License > -+without being bound by section 3 of the GNU GPL. > -+ > -+ 2. Conveying Modified Versions. > -+ > -+ If you modify a copy of the Library, and, in your modifications, a > -+facility refers to a function or data to be supplied by an Application > -+that uses the facility (other than as an argument passed when the > -+facility is invoked), then you may convey a copy of the modified > -+version: > -+ > -+ a) under this License, provided that you make a good faith effort to > -+ ensure that, in the event an Application does not supply the > -+ function or data, the facility still operates, and performs > -+ whatever part of its purpose remains meaningful, or > -+ > -+ b) under the GNU GPL, with none of the additional permissions of > -+ this License applicable to that copy. > -+ > -+ 3. Object Code Incorporating Material from Library Header Files. > -+ > -+ The object code form of an Application may incorporate material from > -+a header file that is part of the Library. You may convey such object > -+code under terms of your choice, provided that, if the incorporated > -+material is not limited to numerical parameters, data structure > -+layouts and accessors, or small macros, inline functions and templates > -+(ten or fewer lines in length), you do both of the following: > -+ > -+ a) Give prominent notice with each copy of the object code that the > -+ Library is used in it and that the Library and its use are > -+ covered by this License. > -+ > -+ b) Accompany the object code with a copy of the GNU GPL and this license > -+ document. > -+ > -+ 4. Combined Works. > -+ > -+ You may convey a Combined Work under terms of your choice that, > -+taken together, effectively do not restrict modification of the > -+portions of the Library contained in the Combined Work and reverse > -+engineering for debugging such modifications, if you also do each of > -+the following: > -+ > -+ a) Give prominent notice with each copy of the Combined Work that > -+ the Library is used in it and that the Library and its use are > -+ covered by this License. > -+ > -+ b) Accompany the Combined Work with a copy of the GNU GPL and this license > -+ document. > -+ > -+ c) For a Combined Work that displays copyright notices during > -+ execution, include the copyright notice for the Library among > -+ these notices, as well as a reference directing the user to the > -+ copies of the GNU GPL and this license document. > -+ > -+ d) Do one of the following: > -+ > -+ 0) Convey the Minimal Corresponding Source under the terms of this > -+ License, and the Corresponding Application Code in a form > -+ suitable for, and under terms that permit, the user to > -+ recombine or relink the Application with a modified version of > -+ the Linked Version to produce a modified Combined Work, in the > -+ manner specified by section 6 of the GNU GPL for conveying > -+ Corresponding Source. > -+ > -+ 1) Use a suitable shared library mechanism for linking with the > -+ Library. A suitable mechanism is one that (a) uses at run time > -+ a copy of the Library already present on the user's computer > -+ system, and (b) will operate properly with a modified version > -+ of the Library that is interface-compatible with the Linked > -+ Version. > -+ > -+ e) Provide Installation Information, but only if you would otherwise > -+ be required to provide such information under section 6 of the > -+ GNU GPL, and only to the extent that such information is > -+ necessary to install and execute a modified version of the > -+ Combined Work produced by recombining or relinking the > -+ Application with a modified version of the Linked Version. (If > -+ you use option 4d0, the Installation Information must accompany > -+ the Minimal Corresponding Source and Corresponding Application > -+ Code. If you use option 4d1, you must provide the Installation > -+ Information in the manner specified by section 6 of the GNU GPL > -+ for conveying Corresponding Source.) > -+ > -+ 5. Combined Libraries. > -+ > -+ You may place library facilities that are a work based on the > -+Library side by side in a single library together with other library > -+facilities that are not Applications and are not covered by this > -+License, and convey such a combined library under terms of your > -+choice, if you do both of the following: > -+ > -+ a) Accompany the combined library with a copy of the same work based > -+ on the Library, uncombined with any other library facilities, > -+ conveyed under the terms of this License. > -+ > -+ b) Give prominent notice with the combined library that part of it > -+ is a work based on the Library, and explaining where to find the > -+ accompanying uncombined form of the same work. > -+ > -+ 6. Revised Versions of the GNU Lesser General Public License. > -+ > -+ The Free Software Foundation may publish revised and/or new versions > -+of the GNU Lesser General Public License from time to time. Such new > -+versions will be similar in spirit to the present version, but may > -+differ in detail to address new problems or concerns. > -+ > -+ Each version is given a distinguishing version number. If the > -+Library as you received it specifies that a certain numbered version > -+of the GNU Lesser General Public License "or any later version" > -+applies to it, you have the option of following the terms and > -+conditions either of that published version or of any later version > -+published by the Free Software Foundation. If the Library as you > -+received it does not specify a version number of the GNU Lesser > -+General Public License, you may choose any version of the GNU Lesser > -+General Public License ever published by the Free Software Foundation. > -+ > -+ If the Library as you received it specifies that a proxy can decide > -+whether future versions of the GNU Lesser General Public License shall > -+apply, that proxy's public statement of acceptance of any version is > -+permanent authorization for you to choose that version for the > -+Library. > - > -=== modified file 'README' > ---- README 2008-08-01 14:40:55 +0000 > -+++ README 2008-08-01 14:53:44 +0000 > -@@ -1,5 +1,15 @@ > --Copyright (C) 2008 Canonical Ltd. All rights reserved. > -- > --wadllib is a client-side package for inspecting and navigating between > --HTTP resources described using the Web Application Description > --Language. > -+Copyright (C) 2008 Canonical Ltd. > -+ > -+wadllib is free software: you can redistribute it and/or modify it > -+under the terms of the GNU Lesser General Public License as published > -+by the Free Software Foundation, either version 3 of the License, or > -+(at your option) any later version. > -+ > -+wadllib is distributed in the hope that it will be useful, but WITHOUT > -+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > -+FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public > -+License for more details. > -+ > -+You should have received a copy of the GNU Lesser General Public > -+License along with wadllib. If not, see > -+. > - > -=== modified file 'setup.py' > ---- setup.py 2008-08-01 14:40:55 +0000 > -+++ setup.py 2008-08-01 14:41:07 +0000 > -@@ -27,6 +27,7 @@ > - 'setuptools_bzr', > - ] > - , > -+ license='LGPL 3', > - extras_require = { > - 'nose': ['nose'], > - }, > - > -=== modified file 'wadllib/__init__.py' > ---- wadllib/__init__.py 2008-08-01 14:40:55 +0000 > -+++ wadllib/__init__.py 2008-08-01 14:53:01 +0000 > -@@ -1,3 +1,19 @@ > - # Copyright 2008 Canonical Ltd. All rights reserved. > - > -+# This file is part of wadllib. > -+# > -+# wadllib is free software: you can redistribute it and/or modify it > -+# under the terms of the GNU Lesser General Public License as > -+# published by the Free Software Foundation, either version 3 of the > -+# License, or (at your option) any later version. > -+# > -+# wadllib is distributed in the hope that it will be useful, but > -+# WITHOUT ANY WARRANTY; without even the implied warranty of > -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > -+# Lesser General Public License for more details. > -+# > -+# You should have received a copy of the GNU Lesser General Public > -+# License along with wadllib. If not, see > -+# . > -+ > - __version__ = '0.1' > - > -=== modified file 'wadllib/_utils/__init__.py' > ---- wadllib/_utils/__init__.py 2008-08-01 14:40:55 +0000 > -+++ wadllib/_utils/__init__.py 2008-08-01 14:53:08 +0000 > -@@ -0,0 +1,17 @@ > -+# Copyright 2008 Canonical Ltd. All rights reserved. > -+ > -+# This file is part of wadllib. > -+# > -+# wadllib is free software: you can redistribute it and/or modify it > -+# under the terms of the GNU Lesser General Public License as > -+# published by the Free Software Foundation, either version 3 of the > -+# License, or (at your option) any later version. > -+# > -+# wadllib is distributed in the hope that it will be useful, but > -+# WITHOUT ANY WARRANTY; without even the implied warranty of > -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > -+# Lesser General Public License for more details. > -+# > -+# You should have received a copy of the GNU Lesser General Public > -+# License along with wadllib. If not, see > -+# . > - > -=== modified file 'wadllib/_utils/uri.py' > ---- wadllib/_utils/uri.py 2008-08-01 14:40:55 +0000 > -+++ wadllib/_utils/uri.py 2008-08-01 14:53:22 +0000 > -@@ -1,4 +1,20 @@ > --# Copyright 2006 Canonical Ltd. All rights reserved. > -+# Copyright 2008 Canonical Ltd. All rights reserved. > -+ > -+# This file is part of wadllib. > -+# > -+# wadllib is free software: you can redistribute it and/or modify it > -+# under the terms of the GNU Lesser General Public License as > -+# published by the Free Software Foundation, either version 3 of the > -+# License, or (at your option) any later version. > -+# > -+# wadllib is distributed in the hope that it will be useful, but > -+# WITHOUT ANY WARRANTY; without even the implied warranty of > -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > -+# Lesser General Public License for more details. > -+# > -+# You should have received a copy of the GNU Lesser General Public > -+# License along with wadllib. If not, see > -+# . > - > - """Functions for working with generic syntax URIs.""" > - > - > -=== modified file 'wadllib/application.py' > ---- wadllib/application.py 2008-08-01 14:40:55 +0000 > -+++ wadllib/application.py 2008-08-01 14:52:57 +0000 > -@@ -1,5 +1,21 @@ > - # Copyright 2008 Canonical Ltd. All rights reserved. > - > -+# This file is part of wadllib. > -+# > -+# wadllib is free software: you can redistribute it and/or modify it > -+# under the terms of the GNU Lesser General Public License as > -+# published by the Free Software Foundation, either version 3 of the > -+# License, or (at your option) any later version. > -+# > -+# wadllib is distributed in the hope that it will be useful, but > -+# WITHOUT ANY WARRANTY; without even the implied warranty of > -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > -+# Lesser General Public License for more details. > -+# > -+# You should have received a copy of the GNU Lesser General Public > -+# License along with wadllib. If not, see > -+# . > -+ > - """Navigate the resources exposed by a web service. > - > - The wadllib library helps a web client navigate the resources > - > -=== modified file 'wadllib/docs/__init__.py' > ---- wadllib/docs/__init__.py 2008-08-01 14:40:55 +0000 > -+++ wadllib/docs/__init__.py 2008-08-01 14:54:47 +0000 > -@@ -0,0 +1,17 @@ > -+# Copyright 2008 Canonical Ltd. All rights reserved. > -+ > -+# This file is part of wadllib. > -+# > -+# wadllib is free software: you can redistribute it and/or modify it > -+# under the terms of the GNU Lesser General Public License as > -+# published by the Free Software Foundation, either version 3 of the > -+# License, or (at your option) any later version. > -+# > -+# wadllib is distributed in the hope that it will be useful, but > -+# WITHOUT ANY WARRANTY; without even the implied warranty of > -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > -+# Lesser General Public License for more details. > -+# > -+# You should have received a copy of the GNU Lesser General Public > -+# License along with wadllib. If not, see > -+# . > - > > === added file 'wadllib/docs/testdata/data-types-wadl.xml' > --- wadllib/docs/testdata/data-types-wadl.xml 1970-01-01 00:00:00 +0000 > +++ wadllib/docs/testdata/data-types-wadl.xml 2009-01-21 19:58:38 +0000 > @@ -0,0 +1,24 @@ > + > + + xmlns="http://research.sun.com/wadl/2006/10" > + xmlns:xsd="http://www.w3.org/2001/XMLSchema" > + xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd"> > + > + > + > + > + > + > + > + > + > + > + > + > + > + + path="$['a_date']" name="a_date" /> > + + path="$['a_datetime']" name="a_datetime" /> > + > + > > === modified file 'wadllib/docs/wadllib.txt' > --- wadllib/docs/wadllib.txt 2008-08-11 17:56:11 +0000 > +++ wadllib/docs/wadllib.txt 2009-01-21 19:58:38 +0000 > @@ -116,8 +116,8 @@ > test data. > > >>> bound_service_root = bind_to_testdata(service_root, 'root') > - >>> bound_service_root.parameter_names() > - ['people_collection_link', 'bugs_collection_link'] > + >>> sorted(bound_service_root.parameter_names()) > + ['bugs_collection_link', 'people_collection_link'] > >>> [method.id for method in bound_service_root.method_iter] > ['service-root-get'] > > @@ -266,7 +266,8 @@ > > >>> bound_limi = bind_to_testdata(limi_person, 'person-limi') > >>> sorted(bound_limi.parameter_names())[:3] > - ['admins_collection_link', 'confirmed_email_addresses_collection_link', 'date_created'] > + ['admins_collection_link', 'confirmed_email_addresses_collection_link', > + 'date_created'] > >>> languages_link = bound_limi.get_parameter("languages_collection_link") > >>> languages_link.get_value() > u'http://api.launchpad.dev/beta/~limi/languages' > @@ -302,7 +303,7 @@ > an example. > > There's a method on a person resource such as bound_limi that's > -identified by a distinctive query argument: ws.op=findPathToTeam. > +identified by a distinctive query argument: ws.op=getMembersByStatus. > > >>> method = bound_limi.get_method( > ... query_params={'ws.op' : 'findPathToTeam'}) > @@ -353,6 +354,51 @@ > >>> print method.response.get_representation_definition('text/html') > None > > +=== Data type converstion === > + > +The values of date and dateTime parameters are automatically converted to > +Python datetime objects. > + > + >>> data_type_stream = pkg_resources.resource_stream( > + ... 'wadllib.docs.testdata', 'data-types-wadl.xml') > + >>> data_type_wadl = Application( > + ... "http://www.example.com/", data_type_stream) > + >>> service_root = data_type_wadl.get_resource_by_path('') > + > + >>> representation = simplejson.dumps( > + ... {'a_date': '2007-10-20', > + ... 'a_datetime': '2005-06-06T08:59:51.619713+00:00'}) > + >>> bound_root = service_root.bind(representation, 'application/json') > + > + >>> bound_root.get_parameter('a_date').get_value() > + datetime.datetime(2007, 10, 20, 0, 0) > + >>> bound_root.get_parameter('a_datetime').get_value() > + datetime.datetime(2005, 6, 6, 8, ...) > + > +A 'date' field can include a timestamp, and a 'datetime' field can > +omit one. wadllib will turn both into datetime objects. > + > + >>> representation = simplejson.dumps( > + ... {'a_date': '2005-06-06T08:59:51.619713+00:00', > + ... 'a_datetime': '2007-10-20'}) > + >>> bound_root = service_root.bind(representation, 'application/json') > + > + >>> bound_root.get_parameter('a_datetime').get_value() > + datetime.datetime(2007, 10, 20, 0, 0) > + >>> bound_root.get_parameter('a_date').get_value() > + datetime.datetime(2005, 6, 6, 8, ...) > + > +If a date or dateTime parameter has a value that can't be parsed to a > +datetime object, you get the original value. > + > + >>> representation = simplejson.dumps( > + ... {'a_date': 'foo', 'a_datetime': None}) > + >>> bound_root = service_root.bind(representation, 'application/json') > + >>> bound_root.get_parameter('a_date').get_value() > + u'foo' > + >>> print bound_root.get_parameter('a_datetime').get_value() > + None > + > > == Representation creation == > > > === added file 'wadllib/iso_strptime.py' > --- wadllib/iso_strptime.py 1970-01-01 00:00:00 +0000 > +++ wadllib/iso_strptime.py 2009-01-21 19:58:38 +0000 > @@ -0,0 +1,85 @@ > +# Copyright 2009 Canonical Ltd. All rights reserved. > + > +# This file is part of wadllib. > +# > +# wadllib is free software: you can redistribute it and/or modify it > +# under the terms of the GNU Lesser General Public License as > +# published by the Free Software Foundation, either version 3 of the > +# License, or (at your option) any later version. > +# > +# wadllib is distributed in the hope that it will be useful, but > +# WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +# Lesser General Public License for more details. > +# > +# You should have received a copy of the GNU Lesser General Public > +# License along with wadllib. If not, see > +# . > +""" > +Parser for ISO 1601 time strings > +================================ > + > +>>> d = iso_strptime("2008-01-07T05:30:30.345323+03:00") > +>>> d > +datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(10800)) > +>>> d.timetuple() > +(2008, 1, 7, 5, 30, 30, 0, 7, 0) > +>>> d.utctimetuple() > +(2008, 1, 7, 2, 30, 30, 0, 7, 0) > +>>> iso_strptime("2008-01-07T05:30:30.345323-03:00") > +datetime.datetime(2008, 1, 7, 5, 30, 30, 345323, tzinfo=TimeZone(-10800)) > +>>> iso_strptime("2008-01-07T05:30:30.345323") > +datetime.datetime(2008, 1, 7, 5, 30, 30, 345323) > +>>> iso_strptime("2008-01-07T05:30:30") > +datetime.datetime(2008, 1, 7, 5, 30, 30) > +>>> iso_strptime("2008-01-07T05:30:30+02:00") > +datetime.datetime(2008, 1, 7, 5, 30, 30, tzinfo=TimeZone(7200)) > +""" This is not currently hooked up as a doc test, I believe. It should be. > + > + > +import re > +import datetime > + > +RE_TIME = re.compile(r"""^ > + (?P\d{4})\-(?P\d{2})\-(?P\d{2}) # pattern matching date > + T # seperator > + (?P\d{2})\:(?P\d{2})\:(?P\d{2}) # pattern matching time > + (\.(?P\d{6}))? # pattern matching optional microseconds > + (?P[\-\+]\d{2}\:\d{2})? # pattern matching optional timezone offset > + $""", re.VERBOSE) Would be really nice if this (and other lines below) could fit within the 79-char limit. Maybe the regex can't be made to fit attractively (though I think it can) but certainly the other lines can. It's actually a shame that this is in wadllib. IMO it actually belongs in a separate package. It is small, but fills a unique, stand-alone need. > + > +class TimeZone(datetime.tzinfo): > + > + def __init__(self, tz_string): > + hours, minutes = tz_string.lstrip("-+").split(":") > + self.stdoffset = datetime.timedelta(hours=int(hours), minutes=int(minutes)) > + if tz_string.startswith("-"): > + self.stdoffset *= -1 > + > + def __repr__(self): > + return "TimeZone(%s)" %(self.stdoffset.days*24*60*60 + self.stdoffset.seconds) > + > + def utcoffset(self, dt): > + return self.stdoffset > + > + def dst(self, dt): > + return datetime.timedelta(0) > + > + > + > +def iso_strptime(time_str): > + x = RE_TIME.match(time_str) > + if not x: > + raise ValueError > + d = datetime.datetime(int(x.group("year")), int(x.group("month")), > + int(x.group("day")), int(x.group("hour")), int(x.group("minutes")), > + int(x.group("seconds"))) > + if x.group("microseconds"): > + d = d.replace(microsecond=int(x.group("microseconds"))) > + if x.group("tz_offset"): > + d = d.replace(tzinfo=TimeZone(x.group("tz_offset"))) > + return d > + > +if __name__ == '__main__': > + import doctest > + doctest.testmod() I don't think we want if __name__ == '__main__' stuff in this kind of file. > >