From ef7b467900f8c511b6774baab9619f666e9ce04b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Sat, 4 Feb 2012 10:18:44 -0500 Subject: [PATCH] test the iso8601 package and add a licensing exception --- LICENSE.txt | 3 + colander/iso8601.py | 32 +++++- colander/tests/__init__.py | 2 + colander/{tests.py => tests/test_colander.py} | 54 +++++---- colander/tests/test_iso8601.py | 104 ++++++++++++++++++ 5 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 colander/tests/__init__.py rename colander/{tests.py => tests/test_colander.py} (98%) create mode 100644 colander/tests/test_iso8601.py diff --git a/LICENSE.txt b/LICENSE.txt index 5ced96e..c4a6ed2 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -39,3 +39,6 @@ License THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +This package uses code from the "pyiso8601" package by Michael Twomey, +licensed under the MIT license. See the source file named "iso8601.py" for +copyright information and license text. diff --git a/colander/iso8601.py b/colander/iso8601.py index 390ef62..af0fad4 100644 --- a/colander/iso8601.py +++ b/colander/iso8601.py @@ -1,13 +1,35 @@ -"""ISO 8601 date time string parsing +""" +Copyright (c) 2007 Michael Twomey + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +ISO 8601 date time string parsing Basic usage: >>> import iso8601 >>> iso8601.parse_date("2007-01-25T12:00:00Z") datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) >>> - """ + from datetime import datetime, timedelta, tzinfo import re @@ -16,11 +38,13 @@ from .compat import string_types __all__ = ["parse_date", "ParseError", "Utc", "FixedOffset"] # Adapted from http://delete.me.uk/2005/03/iso8601.html -ISO8601_REGEX = re.compile(r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" +ISO8601_REGEX = re.compile( + r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" r"((?P.)(?P[0-9]{2}):(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?" r"(?PZ|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" ) -TIMEZONE_REGEX = re.compile("(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") +TIMEZONE_REGEX = re.compile( + "(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") class ParseError(Exception): """Raised when there is a problem parsing a date string""" diff --git a/colander/tests/__init__.py b/colander/tests/__init__.py new file mode 100644 index 0000000..1cdade8 --- /dev/null +++ b/colander/tests/__init__.py @@ -0,0 +1,2 @@ +# package +fixture = 1 diff --git a/colander/tests.py b/colander/tests/test_colander.py similarity index 98% rename from colander/tests.py rename to colander/tests/test_colander.py index 20c4270..924dd5d 100644 --- a/colander/tests.py +++ b/colander/tests/test_colander.py @@ -1,6 +1,6 @@ # -*- coding:utf-8 -*- import unittest -from .compat import text_, text_type +from colander.compat import text_, text_type def invalid_exc(func, *arg, **kw): from colander import Invalid @@ -1244,7 +1244,7 @@ class TestGlobalObject(unittest.TestCase): def test_zope_dottedname_style_resolve_absolute(self): typ = self._makeOne() result = typ._zope_dottedname_style(None, - 'colander.tests.TestGlobalObject') + 'colander.tests.test_colander.TestGlobalObject') self.assertEqual(result, self.__class__) def test_zope_dottedname_style_irrresolveable_absolute(self): @@ -1256,14 +1256,18 @@ class TestGlobalObject(unittest.TestCase): import colander typ = self._makeOne(package=colander) node = DummySchemaNode(None) - result = typ._zope_dottedname_style(node, '.tests.TestGlobalObject') + result = typ._zope_dottedname_style( + node, + '.tests.test_colander.TestGlobalObject') self.assertEqual(result, self.__class__) def test__zope_dottedname_style_resolve_relative_leading_dots(self): import colander typ = self._makeOne(package=colander.tests) node = DummySchemaNode(None) - result = typ._zope_dottedname_style(node, '..tests.TestGlobalObject') + result = typ._zope_dottedname_style( + node, + '..tests.test_colander.TestGlobalObject') self.assertEqual(result, self.__class__) def test__zope_dottedname_style_resolve_relative_is_dot(self): @@ -1301,36 +1305,40 @@ class TestGlobalObject(unittest.TestCase): def test__zope_dottedname_style_irresolveable_absolute(self): typ = self._makeOne() - self.assertRaises(ImportError, - typ._zope_dottedname_style, None, 'colander.fudge.bar') + self.assertRaises( + ImportError, + typ._zope_dottedname_style, None, 'colander.fudge.bar') def test__zope_dottedname_style_resolveable_absolute(self): typ = self._makeOne() - result = typ._zope_dottedname_style(None, - 'colander.tests.TestGlobalObject') + result = typ._zope_dottedname_style( + None, + 'colander.tests.test_colander.TestGlobalObject') self.assertEqual(result, self.__class__) def test__pkg_resources_style_resolve_absolute(self): typ = self._makeOne() result = typ._pkg_resources_style(None, - 'colander.tests:TestGlobalObject') + 'colander.tests.test_colander:TestGlobalObject') self.assertEqual(result, self.__class__) def test__pkg_resources_style_irrresolveable_absolute(self): typ = self._makeOne() self.assertRaises(ImportError, typ._pkg_resources_style, None, - 'colander.tests:nonexisting') + 'colander.tests.test_colander:nonexisting') def test__pkg_resources_style_resolve_relative_startswith_colon(self): import colander.tests typ = self._makeOne(package=colander.tests) - result = typ._pkg_resources_style(None, ':TestGlobalObject') - self.assertEqual(result, self.__class__) + result = typ._pkg_resources_style(None, ':fixture') + self.assertEqual(result, 1) def test__pkg_resources_style_resolve_relative_startswith_dot(self): import colander typ = self._makeOne(package=colander) - result = typ._pkg_resources_style(None, '.tests:TestGlobalObject') + result = typ._pkg_resources_style( + None, + '.tests.test_colander:TestGlobalObject') self.assertEqual(result, self.__class__) def test__pkg_resources_style_resolve_relative_is_dot(self): @@ -1374,13 +1382,17 @@ class TestGlobalObject(unittest.TestCase): def test_deserialize_using_pkgresources_style(self): typ = self._makeOne() node = DummySchemaNode(None) - result = typ.deserialize(node, 'colander.tests:TestGlobalObject') + result = typ.deserialize( + node, + 'colander.tests.test_colander:TestGlobalObject') self.assertEqual(result, self.__class__) def test_deserialize_using_zope_dottedname_style(self): typ = self._makeOne() node = DummySchemaNode(None) - result = typ.deserialize(node, 'colander.tests.TestGlobalObject') + result = typ.deserialize( + node, + 'colander.tests.test_colander.TestGlobalObject') self.assertEqual(result, self.__class__) def test_deserialize_style_raises(self): @@ -1425,12 +1437,12 @@ class TestDateTime(unittest.TestCase): return datetime.date.today() def test_ctor_default_tzinfo_None(self): - from . import iso8601 + from .. import iso8601 typ = self._makeOne() self.assertEqual(typ.default_tzinfo.__class__, iso8601.Utc) def test_ctor_default_tzinfo_non_None(self): - from . import iso8601 + from .. import iso8601 tzinfo = iso8601.FixedOffset(1, 0, 'myname') typ = self._makeOne(default_tzinfo=tzinfo) self.assertEqual(typ.default_tzinfo, tzinfo) @@ -1476,7 +1488,7 @@ class TestDateTime(unittest.TestCase): self.assertEqual(result, dt.isoformat()) def test_serialize_with_tzware_datetime(self): - from . import iso8601 + from .. import iso8601 typ = self._makeOne() dt = self._dt() tzinfo = iso8601.FixedOffset(1, 0, 'myname') @@ -1488,7 +1500,7 @@ class TestDateTime(unittest.TestCase): def test_deserialize_date(self): import datetime - from . import iso8601 + from .. import iso8601 date = self._today() typ = self._makeOne() formatted = date.isoformat() @@ -1520,7 +1532,7 @@ class TestDateTime(unittest.TestCase): self.assertEqual(result, colander.null) def test_deserialize_success(self): - from . import iso8601 + from .. import iso8601 typ = self._makeOne() dt = self._dt() tzinfo = iso8601.FixedOffset(1, 0, 'myname') @@ -1531,7 +1543,7 @@ class TestDateTime(unittest.TestCase): self.assertEqual(result.isoformat(), iso) def test_deserialize_naive_with_default_tzinfo(self): - from . import iso8601 + from .. import iso8601 tzinfo = iso8601.FixedOffset(1, 0, 'myname') typ = self._makeOne(default_tzinfo=tzinfo) dt = self._dt() diff --git a/colander/tests/test_iso8601.py b/colander/tests/test_iso8601.py new file mode 100644 index 0000000..4791205 --- /dev/null +++ b/colander/tests/test_iso8601.py @@ -0,0 +1,104 @@ +import unittest +import datetime + +class Test_Utc(unittest.TestCase): + def _makeOne(self): + from ..iso8601 import Utc + return Utc() + + def test_utcoffset(self): + from ..iso8601 import ZERO + inst = self._makeOne() + result = inst.utcoffset(None) + self.assertEqual(result, ZERO) + + def test_tzname(self): + inst = self._makeOne() + result = inst.tzname(None) + self.assertEqual(result, "UTC") + + def test_dst(self): + from ..iso8601 import ZERO + inst = self._makeOne() + result = inst.dst(None) + self.assertEqual(result, ZERO) + +class Test_FixedOffset(unittest.TestCase): + def _makeOne(self): + from ..iso8601 import FixedOffset + return FixedOffset(1, 30, 'oneandahalf') + + def test_utcoffset(self): + inst = self._makeOne() + result = inst.utcoffset(None) + self.assertEqual(result, datetime.timedelta(hours=1, minutes=30)) + + def test_tzname(self): + inst = self._makeOne() + result = inst.tzname(None) + self.assertEqual(result, 'oneandahalf') + + def test_dst(self): + from ..iso8601 import ZERO + inst = self._makeOne() + result = inst.dst(None) + self.assertEqual(result, ZERO) + + def test___repr__(self): + inst = self._makeOne() + result = inst.__repr__() + self.assertEqual(result, "") + +class Test_parse_timezone(unittest.TestCase): + def _callFUT(self, tzstring): + from ..iso8601 import parse_timezone + return parse_timezone(tzstring) + + def test_default_Z(self): + from ..iso8601 import UTC + result = self._callFUT('Z') + self.assertEqual(result, UTC) + + def test_default_None(self): + from ..iso8601 import UTC + result = self._callFUT(None) + self.assertEqual(result, UTC) + + def test_positive(self): + tzstring = "+01:00" + result = self._callFUT(tzstring) + self.assertEqual(result.utcoffset(None), + datetime.timedelta(hours=1, minutes=0)) + + def test_negative(self): + tzstring = "-01:00" + result = self._callFUT(tzstring) + self.assertEqual(result.utcoffset(None), + datetime.timedelta(hours=-1, minutes=0)) + +class Test_parse_date(unittest.TestCase): + def _callFUT(self, datestring): + from ..iso8601 import parse_date + return parse_date(datestring) + + def test_notastring(self): + from ..iso8601 import ParseError + self.assertRaises(ParseError, self._callFUT, None) + + def test_cantparse(self): + from ..iso8601 import ParseError + self.assertRaises(ParseError, self._callFUT, 'garbage') + + def test_normal(self): + from ..iso8601 import UTC + result = self._callFUT("2007-01-25T12:00:00Z") + self.assertEqual(result, + datetime.datetime(2007, 1, 25, 12, 0, tzinfo=UTC)) + + def test_fraction(self): + from ..iso8601 import UTC + result = self._callFUT("2007-01-25T12:00:00.123Z") + self.assertEqual(result, + datetime.datetime(2007, 1, 25, 12, 0, 0, 123000, + tzinfo=UTC)) +