From f6be836f74b9f6d010a17acdc2b2ea3c58edeb0a Mon Sep 17 00:00:00 2001 From: Alex Marandon Date: Fri, 13 Dec 2013 17:36:28 +0100 Subject: [PATCH 01/99] Allow translation of messages returned by asdict Fix #152 --- colander/__init__.py | 4 +++- colander/tests/test_colander.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index bbebe4b..f1d2f00 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -165,7 +165,7 @@ class Invalid(Exception): return str(self.pos) return str(self.node.name) - def asdict(self): + def asdict(self, translate=None): """ Return a dictionary containing a basic (non-language-translated) error report for this exception""" paths = self.paths() @@ -177,6 +177,8 @@ class Invalid(Exception): exc.msg and msgs.extend(exc.messages()) keyname = exc._keyname() keyname and keyparts.append(keyname) + if translate: + msgs = [translate(msg) for msg in msgs] errors['.'.join(keyparts)] = '; '.join(interpolate(msgs)) return errors diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 7d60998..18c9810 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -3398,6 +3398,39 @@ class TestFunctional(object): errors = e.asdict() self.assertEqual(errors, expected) + def test_invalid_asdict_translation_callback(self): + from translationstring import TranslationString + + expected = { + 'schema.int': 'translated', + 'schema.ob': 'translated', + 'schema.seq.0.0': 'translated', + 'schema.seq.1.0': 'translated', + 'schema.seq.2.0': 'translated', + 'schema.seq.3.0': 'translated', + 'schema.seq2.0.key': 'translated', + 'schema.seq2.0.key2': 'translated', + 'schema.seq2.1.key': 'translated', + 'schema.seq2.1.key2': 'translated', + 'schema.tup.0': 'translated', + } + data = { + 'int': '20', + 'ob': 'no.way.this.exists', + 'seq': [('q', 's'), ('w', 's'), ('e', 's'), ('r', 's')], + 'seq2': [{'key': 't', 'key2': 'y'}, {'key':'u', 'key2':'i'}], + 'tup': ('s', 's'), + } + schema = self._makeSchema() + e = invalid_exc(schema.deserialize, data) + + def translation_function(string): + return TranslationString('translated') + + errors = e.asdict(translate=translation_function) + self.assertEqual(errors, expected) + + class TestImperative(unittest.TestCase, TestFunctional): def _makeSchema(self, name='schema'): From d8d2681c40acc5362e7038e715e49b68d65eeee3 Mon Sep 17 00:00:00 2001 From: Alex Marandon Date: Sat, 21 Dec 2013 20:55:41 +0100 Subject: [PATCH 02/99] Document translate argument of Invalid.asdict --- CHANGES.txt | 6 ++++++ CONTRIBUTORS.txt | 1 + colander/__init__.py | 7 ++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 63f1b89..abdecff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,12 @@ Bug Fixes - Un-break wrapping of callable instances as ``colander.deferred``. See https://github.com/Pylons/colander/issues/141. +Features +~~~~~~~~ + +- Allow localization of error messages returned by ``colander.Invalid.asdict`` + by adding an optional ``translate`` callable argument. + 1.0b1 (2013-09-01) ------------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index a1cf78f..065840e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -114,3 +114,4 @@ Contributors - Brian Sutherland, 2013/08/16 - Peter Lamut, 2013/08/16 - Veeti Paananen, 2013/08/20 +- Alex Marandon, 2013/12/21 diff --git a/colander/__init__.py b/colander/__init__.py index f1d2f00..a68913b 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -167,7 +167,12 @@ class Invalid(Exception): def asdict(self, translate=None): """ Return a dictionary containing a basic - (non-language-translated) error report for this exception""" + (non-language-translated) error report for this exception. + + If ``translate`` is supplied, it must be a callable taking a + translation string as its sole argument and returning a localized, + interpolated string. + """ paths = self.paths() errors = {} for path in paths: From f7e4a3fe7ddc8b64ed03e3485e0f4a5d41755c7f Mon Sep 17 00:00:00 2001 From: Nikola Kotur Date: Mon, 6 Jan 2014 17:14:39 +0100 Subject: [PATCH 03/99] Fix syntax errors in documentation. --- docs/basics.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/basics.rst b/docs/basics.rst index 83c862c..e6fcd66 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -999,7 +999,7 @@ schema, then the schema definition can be made more succinct using the @colander.instantiate() class friend(colander.TupleSchema): - rank = colander.SchemaNode(colander.Int(), + rank = colander.SchemaNode(colander.Int(), validator=colander.Range(0, 9999)) name = colander.SchemaNode(colander.String()) @@ -1008,7 +1008,7 @@ schema, then the schema definition can be made more succinct using the @colander.instantiate() class phone(colander.MappingSchema): - location = colander.SchemaNode(colander.String(), + location = colander.SchemaNode(colander.String(), validator=colander.OneOf(['home', 'work'])) number = colander.SchemaNode(colander.String()) @@ -1076,22 +1076,22 @@ We can imperatively construct a completely equivalent schema like so: import colander - friend = colander.SchemaNode(Tuple()) + friend = colander.SchemaNode(colander.Tuple()) friend.add(colander.SchemaNode(colander.Int(), validator=colander.Range(0, 9999), name='rank')) - friend.add(colander.SchemaNode(colander.String(), name='name') + friend.add(colander.SchemaNode(colander.String(), name='name')) - phone = colander.SchemaNode(Mapping()) + phone = colander.SchemaNode(colander.Mapping()) phone.add(colander.SchemaNode(colander.String(), validator=colander.OneOf(['home', 'work']), name='location')) phone.add(colander.SchemaNode(colander.String(), name='number')) - schema = colander.SchemaNode(Mapping()) + schema = colander.SchemaNode(colander.Mapping()) schema.add(colander.SchemaNode(colander.String(), name='name')) - schema.add(colander.SchemaNode(colander.Int(), name='age'), - validator=colander.Range(0, 200)) + schema.add(colander.SchemaNode(colander.Int(), name='age', + validator=colander.Range(0, 200))) schema.add(colander.SchemaNode(colander.Sequence(), friend, name='friends')) schema.add(colander.SchemaNode(colander.Sequence(), phone, name='phones')) @@ -1129,7 +1129,7 @@ For example, in a Python module, you might have code that looks like this: .. code-block:: python - from colander import MappingSchema + from colander import SchemaNode, MappingSchema from colander import Int class MySchema1(MappingSchema): From b5d6b8201dfeef41379e45637ba379915c915c55 Mon Sep 17 00:00:00 2001 From: Nikola Kotur Date: Tue, 7 Jan 2014 10:35:18 +0100 Subject: [PATCH 04/99] Documentation: correct subclassing SchemaNode examples. Fixes #149 and fixes #155. --- docs/basics.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/basics.rst b/docs/basics.rst index 83c862c..a7b978c 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -171,7 +171,7 @@ The imperative style that looks like this still works, of course: .. code-block:: python ranged_int = colander.SchemaNode( - typ=colander.Int(), + schema_type=colander.Int, validator=colander.Range(0, 10), default=10, title='Ranged Int' @@ -182,7 +182,7 @@ But in 1.0a1+, you can alternately now do something like this: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int validator = colander.Range(0, 10) default = 10 title = 'Ranged Int' @@ -195,7 +195,7 @@ the schemanode subclass instead of plain attributes: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -220,7 +220,7 @@ example this will *not* work: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -247,7 +247,7 @@ indeed work): .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -271,7 +271,7 @@ the bind parameters within values that are plain old methods: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -292,7 +292,7 @@ attributes of the schemanode that rely on binding variables: .. code-block:: python class UserIdSchemaNode(colander.SchemaNode): - typ = colander.String() + schema_type = colander.String title = 'User Id' def after_bind(self, node, kw): @@ -304,7 +304,7 @@ constructor: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' validator = colander.Range(0, 10) @@ -999,7 +999,7 @@ schema, then the schema definition can be made more succinct using the @colander.instantiate() class friend(colander.TupleSchema): - rank = colander.SchemaNode(colander.Int(), + rank = colander.SchemaNode(colander.Int(), validator=colander.Range(0, 9999)) name = colander.SchemaNode(colander.String()) @@ -1008,7 +1008,7 @@ schema, then the schema definition can be made more succinct using the @colander.instantiate() class phone(colander.MappingSchema): - location = colander.SchemaNode(colander.String(), + location = colander.SchemaNode(colander.String(), validator=colander.OneOf(['home', 'work'])) number = colander.SchemaNode(colander.String()) From 4f5b9e8a37197edf8baa62aad6b2fe525a7c3b25 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Thu, 23 Jan 2014 19:12:49 +0000 Subject: [PATCH 05/99] cut-n-paste documentation error --- colander/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 7b873af..2dcf209 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1633,7 +1633,7 @@ class Time(SchemaType): This type serializes python ``datetime.time`` objects to a `ISO8601 `_ string format. - The format includes the date only. + The format includes the time only. The constructor accepts no arguments. From 75bdc11d8ee92f4ef131d81b22ecc60b454da274 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Thu, 23 Jan 2014 20:04:52 +0000 Subject: [PATCH 06/99] need to test if we have a datetime.time first as bool(datetime.time(0)) evaluates to False, but should be considered legitimate --- colander/__init__.py | 5 ++--- colander/tests/test_colander.py | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 2dcf209..0ec8047 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1671,13 +1671,12 @@ class Time(SchemaType): err_template = _('Invalid time') def serialize(self, node, appstruct): - if not appstruct: - return null - if isinstance(appstruct, datetime.datetime): appstruct = appstruct.time() if not isinstance(appstruct, datetime.time): + if not appstruct: + return null raise Invalid(node, _('"${val}" is not a time object', mapping={'val':appstruct}) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 14160c7..eed429b 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2257,6 +2257,15 @@ class TestTime(unittest.TestCase): expected = time.isoformat().split('.')[0] self.assertEqual(result, expected) + def test_serialize_with_zero_time(self): + import datetime + typ = self._makeOne() + time = datetime.time(0) + node = DummySchemaNode(None) + result = typ.serialize(node, time) + expected = time.isoformat().split('.')[0] + self.assertEqual(result, expected) + def test_serialize_with_datetime(self): typ = self._makeOne() dt = self._dt() From ab93c5cd7f6f6b1204d10ac315c692563dc53339 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Tue, 28 Jan 2014 14:18:24 +0000 Subject: [PATCH 07/99] added change to CHANGES.txt --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index 2f43615..0a5fbfb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,6 +6,7 @@ Bug Fixes - Un-break wrapping of callable instances as ``colander.deferred``. See https://github.com/Pylons/colander/issues/141. +- Time of "00:00" no longer gives ``colander.Invalid``. Features ~~~~~~~~ From ccb87e5cd6b36e6463d78c9e9027c3b2d5dadcc7 Mon Sep 17 00:00:00 2001 From: Amos Latteier Date: Mon, 14 Apr 2014 15:04:41 -0400 Subject: [PATCH 08/99] Make deserialize with an unbound validator raise an exception. This should help avoid programming mistakes where you forget to bind a schema. --- colander/__init__.py | 13 ++++++++++++- colander/tests/test_colander.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 6f4185b..462f36b 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -57,6 +57,13 @@ def interpolate(msgs): else: yield s +class UnboundDeferredError(Exception): + """ + An exception raised by :meth:`SchemaNode.deserialize` when an attempt + is made to deserialize a node which has an unbound :class:`deferred` + validator. + """ + class Invalid(Exception): """ An exception raised by data types and validators indicating that @@ -1961,7 +1968,11 @@ class _SchemaNode(object): return appstruct if self.validator is not None: - if not isinstance(self.validator, deferred): # unbound + if isinstance(self.validator, deferred): # unbound + raise UnboundDeferredError( + "Schema node {node} has an unbound deferred validator" + .format(node=self)) + else: self.validator(self, appstruct) return appstruct diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 487eca1..5ea17d1 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2462,6 +2462,19 @@ class TestSchemaNode(unittest.TestCase): e = invalid_exc(node.deserialize, 1) self.assertEqual(e.msg, 'Wrong') + def test_deserialize_with_unbound_validator(self): + from colander import Invalid + from colander import deferred + from colander import UnboundDeferredError + typ = DummyType() + def validator(node, kw): + def _validate(node, value): + node.raise_invalid('Invalid') + return _validate + node = self._makeOne(typ, validator=deferred(validator)) + self.assertRaises(UnboundDeferredError, node.deserialize, None) + self.assertRaises(Invalid, node.bind(foo='foo').deserialize, None) + def test_deserialize_value_is_null_no_missing(self): from colander import null from colander import Invalid From 8383eb894c101ea89b02aa90ee257199369f0a6e Mon Sep 17 00:00:00 2001 From: Amos Latteier Date: Mon, 14 Apr 2014 15:28:49 -0400 Subject: [PATCH 09/99] Add documentation. --- CHANGES.txt | 9 +++++++++ docs/api.rst | 3 +++ docs/binding.rst | 3 ++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 02b4949..1908d9e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,6 +26,15 @@ Features - Allow localization of error messages returned by ``colander.Invalid.asdict`` by adding an optional ``translate`` callable argument. +Backwards Incompatibilities +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``SchemaNode.deserialize`` will now raise an + ``UnboundDeferredError`` if the node has an unbound deferred + validator. Previously, deferred validators were silently ignored. + See https://github.com/Pylons/colander/issues/47 + + 1.0b1 (2013-09-01) ------------------ diff --git a/docs/api.rst b/docs/api.rst index 8f972bd..51b6d75 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -48,6 +48,9 @@ Exceptions from a widget as the value which should be redisplayed when an error is shown. + .. autoclass:: UnboundDeferredError + + Validators ~~~~~~~~~~ diff --git a/docs/binding.rst b/docs/binding.rst index 61e5efa..94340c2 100644 --- a/docs/binding.rst +++ b/docs/binding.rst @@ -248,7 +248,8 @@ If you use a schema with deferred ``validator``, ``missing`` or ``default`` attributes, but you use it to perform serialization and deserialization without calling its ``bind`` method: -- If ``validator`` is deferred, no validation will be performed. +- If ``validator`` is deferred, :meth:`~colander.SchemaNode.deserialize` will + raise an :exc:`~colander.UnboundDeferredError`. - If ``missing`` is deferred, the field will be considered *required*. From 30ef63522c79a3318735c1c815e8c02be9673eae Mon Sep 17 00:00:00 2001 From: Robert Buchholz Date: Mon, 2 Jun 2014 14:50:57 +0200 Subject: [PATCH 10/99] Update german translation --- colander/locale/de/LC_MESSAGES/colander.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colander/locale/de/LC_MESSAGES/colander.po b/colander/locale/de/LC_MESSAGES/colander.po index b58eeca..b7663d1 100644 --- a/colander/locale/de/LC_MESSAGES/colander.po +++ b/colander/locale/de/LC_MESSAGES/colander.po @@ -31,11 +31,11 @@ msgstr "Ungültige E-Mail-Adresse" #: colander/__init__.py:315 msgid "${val} is less than minimum value ${min}" -msgstr "${val} ist kleiner als der erlaubter Mindestwert (${min})" +msgstr "${val} ist kleiner als der erlaubte Mindestwert (${min})" #: colander/__init__.py:316 msgid "${val} is greater than maximum value ${max}" -msgstr "${val} ist größer als der erlaubter Höchstwert (${max})" +msgstr "${val} ist größer als der erlaubte Höchstwert (${max})" #: colander/__init__.py:348 msgid "Shorter than minimum length ${min}" @@ -51,11 +51,11 @@ msgstr "\"${val}\" ist nicht in der Auswahl ${choices}" #: colander/__init__.py:377 msgid "One or more of the choices you made was not acceptable" -msgstr "" +msgstr "Mindestens eine Auswahl war nicht akzeptabel" #: colander/__init__.py:423 msgid "Must be a URL" -msgstr "" +msgstr "Muss eine URL sein" #: colander/__init__.py:519 msgid "\"${val}\" is not a mapping type: ${err}" @@ -99,7 +99,7 @@ msgstr "${val} ist keine Zeichenkette" #: colander/__init__.py:1279 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})" -msgstr "" +msgstr "\"${val}\" ist weder in (${false_choices}) noch in (${true_choices}) enthalten" #: colander/__init__.py:1339 colander/__init__.py:1356 #: colander/__init__.py:1366 From b72547afe0a416f0c9f54e16652bb6e598da994d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Messiant?= Date: Fri, 27 Jun 2014 11:50:57 +0200 Subject: [PATCH 11/99] Add myself to CONTRIBUTORS --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e1ef2e0..7033902 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -116,3 +116,4 @@ Contributors - Veeti Paananen, 2013/08/20 - Michael Howitz, 2013/12/05 - Alex Marandon, 2013/12/21 +- Cédric Messiant, 2014/06/27 From a7bee85504aeef269c919d80e35f8fcf5ad96760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Messiant?= Date: Fri, 27 Jun 2014 11:51:21 +0200 Subject: [PATCH 12/99] Update French translations --- colander/locale/fr/LC_MESSAGES/colander.mo | Bin 3315 -> 3470 bytes colander/locale/fr/LC_MESSAGES/colander.po | 31 ++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/colander/locale/fr/LC_MESSAGES/colander.mo b/colander/locale/fr/LC_MESSAGES/colander.mo index 164708b425d5789cec54d596189519a82e545f3d..5f0ee9f1a4ac39f45e46310988e08b119a51fc2b 100644 GIT binary patch delta 936 zcmX}qOH30%7{KvaS}mouJXH|X!DlO!w6&Ii1QkIDK4K~+96YeniCI%xvR$|t5)URG zG{y~4FM9DHCp8TRPekCDbIj~{LK+ZQ&Pf{TK( zpV~ofF@#89w1ywbD;&qy*ouE}2zLgA*o%|ciSu{_AEO?(;`kAKV%r$2~$U zh;bSR8KAHW?_wO6aTjjjKKzMMY}v-**oivs#T__|I+45|JD4NBjkWj&58x{5M89D- z>x=02t&Tp-^2Zdu;1m||3h^MbyYV58;R;Hu4GVDy6L=Lbp+3Rqn8ddzantW_s1u@( zIEi{gH!#ln;tq{d_!3X!ChC(&MT9tx*HG{90qPTZiZNWqMtqO`xQ=bu$g$|_>_YY+ z&f_rVaS$J22G?<6fJT_FkK#FGX;?EjpmIQ}x3CN{EKOV>aq7Nd8{o zsd$O_q1a8Mp4#AdkZ;C&EF|J(&KvRWmaEZ^!#hq8%~ZW}U1HUHH4{dSDcxeAqxv-< zji6)AsOqoaCu1a=Oy!c9UYSn!XLDVtbShPM!K^sR3EM1Hil$Sx`{mhpO9k7SmKRl} zVwojJ4ooZE(KF+BxUo>)IO$G>Ds7X=QB_j**2K$YRj?eH?m5wub=N{itG`3fjPP7Z zmapGbj&wpO2nQ-*sh9Ckm}4IMsHu=%8tYR0sR(kjiHMXR91n3}WI|IkfDmJH>t aMMj#HoG#l;nJt$Ja@MR!bIv^${qPrDv4gDu delta 843 zcmXZaKWI}y9KiA4tBHw8{MXplTD9lDv}qenQqh{me-%+{Cym9$;n_Sfkj9c18V8}9 z;v$xZi%SGS3?e8Q?5G3>!KENh>LM;8F5)0`@OzhcaQ)oxa=p9X@7;reKmAMJx&x1d zwu`=#9e&P;{1w{6V zEXfdqQ6?_pFxIgbpWtqMgE9P!J@_3r;y=_IhJu|qiARW!Vi+s97wf1m_87qYL9?KBox@F~<;m}Hmp#IvZdf&1|-Ucn!zD>k`7B!yQ|$9M4+aS)f0JIiZ4gDZFf*D#Bdlr4`H zJclokJk6dI&oq6$xi!g1+)Q;2+2W5i6hsE-Bw!9?AJcdO<_-9v`8=BlHv1FgV?HJK zkv=+~-ZUz2-U#oY+f3-c+sudxv|e@fqve%9_z4_SL_>ip@ VY*#I|o7K{dQoE_jj`u$H;Xn10Z`S|- diff --git a/colander/locale/fr/LC_MESSAGES/colander.po b/colander/locale/fr/LC_MESSAGES/colander.po index e30f1c0..3cba97b 100644 --- a/colander/locale/fr/LC_MESSAGES/colander.po +++ b/colander/locale/fr/LC_MESSAGES/colander.po @@ -8,14 +8,15 @@ msgstr "" "Project-Id-Version: colander 0.8\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2013-05-19 13:15+0200\n" -"PO-Revision-Date: 2011-10-02 21:38+0100\n" -"Last-Translator: Bard Stéphane \n" +"PO-Revision-Date: 2014-06-27 11:46+0100\n" +"Last-Translator: Cédric Messiant \n" "Language-Team: fr \n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"X-Generator: Poedit 1.5.4\n" #: colander/__init__.py:233 msgid "Invalid value" @@ -23,11 +24,11 @@ msgstr "Valeur incorrecte" #: colander/__init__.py:270 msgid "String does not match expected pattern" -msgstr "La chaîne de caractères n'a pas vérifié le modèle attendu" +msgstr "La chaîne de caractères ne correspond pas au modèle attendu" #: colander/__init__.py:287 msgid "Invalid email address" -msgstr "Invalide adresse email" +msgstr "Adresse email invalide" #: colander/__init__.py:315 msgid "${val} is less than minimum value ${min}" @@ -39,7 +40,7 @@ msgstr "${val} est plus grand que la valeur maximum autorisée (${max})" #: colander/__init__.py:348 msgid "Shorter than minimum length ${min}" -msgstr "La longueur est inférieur à la taille minimum autorisée (${min})" +msgstr "La longueur est inférieure à la taille minimum autorisée (${min})" #: colander/__init__.py:354 msgid "Longer than maximum length ${max}" @@ -73,11 +74,10 @@ msgstr "\"${val}\" n'est pas itérable" #: colander/__init__.py:664 msgid "" -"\"${val}\" has an incorrect number of elements (expected ${exp}, was " -"${was})" +"\"${val}\" has an incorrect number of elements (expected ${exp}, was ${was})" msgstr "" -"\"${val}\" possède un nombre incorrecte d'éléments (attendu ${exp}, " -"trouvé ${was})" +"\"${val}\" possède un nombre incorrect d'éléments (attendu ${exp}, trouvé " +"${was})" #: colander/__init__.py:803 msgid "${cstruct} is not iterable" @@ -97,11 +97,12 @@ msgstr "\"${val}\" n'est pas un nombre" #: colander/__init__.py:1268 msgid "${val} is not a string" -msgstr "${val} n'est pas une chaîne de carctères" +msgstr "${val} n'est pas une chaîne de caractères" #: colander/__init__.py:1279 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})" -msgstr "\"${val}\" ne fait partie ni de (${false_choices}) ni de (${true_choices})" +msgstr "" +"\"${val}\" ne fait partie ni de (${false_choices}) ni de (${true_choices})" #: colander/__init__.py:1339 colander/__init__.py:1356 #: colander/__init__.py:1366 @@ -133,16 +134,14 @@ msgid "\"${val}\" is not a date object" msgstr "\"${val}\" n'est pas un objet date" #: colander/__init__.py:1610 -#, fuzzy #| msgid "Invalid date" msgid "Invalid time" -msgstr "Date invalide" +msgstr "Heure invalide" #: colander/__init__.py:1621 -#, fuzzy #| msgid "\"${val}\" is not a datetime object" msgid "\"${val}\" is not a time object" -msgstr "\"${val}\" n'est pas un objet datetime" +msgstr "\"${val}\" n'est pas un objet time" #: colander/__init__.py:1878 colander/__init__.py:1880 msgid "Required" From 4b0e85de7789395a1622a954fdc530dc04adae2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Fern=C3=A1ndez=20Casta=C3=B1o?= Date: Thu, 21 Aug 2014 11:49:25 +0200 Subject: [PATCH 13/99] Update basics.rst --- docs/basics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basics.rst b/docs/basics.rst index 7c23e08..4e8bbcf 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -395,7 +395,7 @@ Earlier we defined a schema: Let's now use this schema to try to deserialize some concrete data structures. -Each of thse concrete data structures is called a :term:`cstruct`. +Each of these concrete data structures is called a :term:`cstruct`. "cstruct" is an abbreviation of "colander structure": you can think of a cstruct as a serialized representation of some application data. A "cstruct" is usually generated by the From 27b47cf23262fd9964bf9049ea06fc70f8d61d30 Mon Sep 17 00:00:00 2001 From: "OCHIAI, Gouji" Date: Fri, 22 Aug 2014 01:05:31 +0900 Subject: [PATCH 14/99] contributor's agreement. --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 7033902..fe72d82 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -117,3 +117,4 @@ Contributors - Michael Howitz, 2013/12/05 - Alex Marandon, 2013/12/21 - Cédric Messiant, 2014/06/27 +- Gouji Ochiai, 2014/08/21 From c2a8316680f7fa4f89af969b0052dac2f2e10056 Mon Sep 17 00:00:00 2001 From: "OCHIAI, Gouji" Date: Fri, 22 Aug 2014 01:28:51 +0900 Subject: [PATCH 15/99] add testcases --- colander/tests/test_colander.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 487eca1..4ff4939 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2726,6 +2726,22 @@ class TestSchemaNodeSubclassing(unittest.TestCase): result = node.deserialize(colander.null) self.assertEqual(result, 10) + def test_subclass_uses_title(self): + import colander + class MyNode(colander.SchemaNode): + schema_type = colander.Int + title = 'some title' + node = MyNode(name='my') + self.assertEqual(node.title, 'some title') + + def test_subclass_title_overwritten_by_constructor(self): + import colander + class MyNode(colander.SchemaNode): + schema_type = colander.Int + title = 'some title' + node = MyNode(name='my', title='other title') + self.assertEqual(node.title, 'other title') + def test_subclass_value_overridden_by_constructor(self): import colander class MyNode(colander.SchemaNode): From 99e6f5e7ccfa7a03ceec43530fff5936c341c13a Mon Sep 17 00:00:00 2001 From: "OCHIAI, Gouji" Date: Fri, 22 Aug 2014 01:31:53 +0900 Subject: [PATCH 16/99] fixes over writing title --- colander/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 6f4185b..82eb96b 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1815,7 +1815,7 @@ class _SchemaNode(object): missing_msg = _('Required') name = '' raw_title = _marker - title = '' + title = _marker description = '' widget = None after_bind = None @@ -1842,8 +1842,9 @@ class _SchemaNode(object): # bw compat forces us to manufacture a title if one is not supplied title = kw.get('title', _marker) if title is _marker: - name = kw.get('name', self.name) - kw['title'] = name.replace('_', ' ').title() + if self.title is _marker: + name = kw.get('name', self.name) + kw['title'] = name.replace('_', ' ').title() else: kw['raw_title'] = title From c5766396e220c111a6a399fc8be452bf76d8ba6a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 21 Aug 2014 13:56:00 -0500 Subject: [PATCH 17/99] update changelog --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 02b4949..fce58b7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,6 +17,11 @@ Bug Fixes returning the ``drop`` instance instead of omitting the value. https://github.com/Pylons/colander/issues/139 +- Fix an issue where the ``SchemaNode.title`` was clobbered by the ``name`` + when defined as a class attribute. + See https://github.com/Pylons/colander/pull/183 and + https://github.com/Pylons/colander/pull/185 + Features ~~~~~~~~ From 9af19552ade20b9b36abde15804652e1c7f9dd72 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 10 Sep 2014 18:54:12 +0000 Subject: [PATCH 18/99] remove local iso8601 and use pypi version When running some tests on another project I stumbled upon a failed test that revealed a bug in colander's iso8601. If you try to deserialize "2014-09-09T15:15:57.516967" into a datetime object you get `datetime.datetime(2014, 9, 9, 15, 15, 57, 516966)` (notice the microseconds are off by 1). I traced it down to this: ``` groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6) ``` `int(float("0.%s" % "516967") * 1e6)` is equal to 516966 due to rounding. I was going to fix it, but iso8601 is a currently maintained project on pypi that has this issue (and possibly others) already fixed. So, it makes much more sense to offload everything back to that project. The only difference (besides the bug fixes) is following: `iso8601.Utc` => `iso8601.iso8601.Utc` `iso8601.FixedOffset` => `iso8601.iso8601.FixedOffset` --- colander/iso8601.py | 152 +-------------------------------- colander/tests/test_iso8601.py | 11 ++- setup.py | 2 +- 3 files changed, 11 insertions(+), 154 deletions(-) diff --git a/colander/iso8601.py b/colander/iso8601.py index 0dcd9a6..1d72fec 100644 --- a/colander/iso8601.py +++ b/colander/iso8601.py @@ -1,152 +1,4 @@ -""" -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 - -from .compat import string_types +import iso8601 +from iso8601.iso8601 import (parse_date, ParseError, Utc, FixedOffset, UTC, ZERO) __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})" - 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}))?") - -class ParseError(Exception): - """Raised when there is a problem parsing a date string""" - -# Yoinked from python docs -ZERO = timedelta(0) -class Utc(tzinfo): - """UTC - - """ - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO -UTC = Utc() - -class FixedOffset(tzinfo): - """Fixed offset in hours and minutes from UTC - - """ - def __init__(self, offset_hours, offset_minutes, name): - self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) - self.__name = name - - def __getinitargs__(self): - # tzinfo.__reduce__ returns the type as the factory: supply - # defaults here, rather than in __init__. - return 0, 0, 'unknown' - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - def __repr__(self): - return "" % self.__name - -def parse_timezone(tzstring, default_timezone=UTC): - """Parses ISO 8601 time zone specs into tzinfo offsets - - """ - if tzstring == "Z": - return UTC - # This isn't strictly correct, but it's common to encounter dates without - # timezones so I'll assume the default (which defaults to UTC). - # Addresses issue 4. - if tzstring is None: - return default_timezone - m = TIMEZONE_REGEX.match(tzstring) - prefix = m.group('prefix') - hours = int(m.group('hours')) - minutes = m.group('minutes') - if minutes is None: - minutes = 0 - else: - minutes = int(minutes) - if prefix == "-": - hours = -hours - minutes = -minutes - return FixedOffset(hours, minutes, tzstring) - -def parse_date(datestring, default_timezone=UTC): - """Parses ISO 8601 dates into datetime objects - - The timezone is parsed from the date string. However it is quite common to - have dates without a timezone (not strictly correct). In this case the - default timezone specified in default_timezone is used. This is UTC by - default. - """ - if not isinstance(datestring, string_types): - raise ParseError("Expecting a string %r" % datestring) - m = ISO8601_REGEX.match(datestring) - if not m: - raise ParseError("Unable to parse date string %r" % datestring) - groups = m.groupdict() - tz = parse_timezone(groups["timezone"], default_timezone=default_timezone) - if (groups['year'] is None or - groups['month'] is None or - groups['day'] is None): - raise ParseError('Unable to parse date string %r' % datestring) - if groups["hour"] is None: - groups["hour"] = 0 - if groups["minute"] is None: - groups["minute"] = 0 - if groups["second"] is None: - groups["second"] = 0 - if groups["fraction"] is None: - groups["fraction"] = 0 - else: - groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6) - try: - return datetime( - int(groups["year"]), int(groups["month"]), int(groups["day"]), - int(groups["hour"]), int(groups["minute"]), int(groups["second"]), - int(groups["fraction"]), tz) - except ValueError as e: - raise ParseError(*e.args) diff --git a/colander/tests/test_iso8601.py b/colander/tests/test_iso8601.py index c579eee..45d4a2a 100644 --- a/colander/tests/test_iso8601.py +++ b/colander/tests/test_iso8601.py @@ -68,12 +68,17 @@ class Test_FixedOffset(unittest.TestCase): def test___repr__(self): inst = self._makeOne() result = inst.__repr__() - self.assertEqual(result, "") + self.assertEqual(result, "") class Test_parse_timezone(unittest.TestCase): def _callFUT(self, tzstring, **kw): - from ..iso8601 import parse_timezone - return parse_timezone(tzstring, **kw) + # mimic old parse_timezone() by returning a FixedOffset + from datetime import tzinfo + from ..iso8601 import (parse_date, FixedOffset) + if tzstring is None: + tzstring = '' + dt = parse_date("2006-10-11T00:14:33{}".format(tzstring), **kw) + return dt.tzinfo def test_default_Z(self): from ..iso8601 import UTC diff --git a/setup.py b/setup.py index 9a6c66c..1961c5a 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ except: README = '' CHANGES = '' -requires = ['translationstring'] +requires = ['translationstring', 'iso8601'] testing_extras = ['nose', 'coverage'] docs_extras = ['Sphinx'] From f1c682aa7bdf7fa87d606e9db240445f0b58c055 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 10 Sep 2014 18:54:53 +0000 Subject: [PATCH 19/99] remove license note about iso8601 --- LICENSE.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index c4a6ed2..5ced96e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -39,6 +39,3 @@ 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. From 138e2f7b5452e9a7f93e9dd3f5688a31ed02ccc8 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 10 Sep 2014 18:56:58 +0000 Subject: [PATCH 20/99] documented changes; added name to contibutors --- CHANGES.txt | 5 +++++ CONTRIBUTORS.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index fce58b7..da20d24 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Unreleased Bug Fixes ~~~~~~~~~ +- Removed forked iso8601 and change to dependency on pypi iso8601 + (due to float rounding bug on microsecond portion when parsing + iso8601 datetime string). Left an iso8601.py stub for backwards + compatibility. + - Time of "00:00" no longer gives ``colander.Invalid``. - Un-break wrapping of callable instances as ``colander.deferred``. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index fe72d82..6c6130b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -118,3 +118,4 @@ Contributors - Alex Marandon, 2013/12/21 - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 +- Tim Tisdall, 2014/09/10 From 7f1a5e9c015e4e543ad4de13a44fbe1c53dcb220 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 10 Sep 2014 19:12:42 +0000 Subject: [PATCH 21/99] py26 compatibility --- colander/iso8601.py | 1 + colander/tests/test_iso8601.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/colander/iso8601.py b/colander/iso8601.py index 1d72fec..2f7deaa 100644 --- a/colander/iso8601.py +++ b/colander/iso8601.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import iso8601 from iso8601.iso8601 import (parse_date, ParseError, Utc, FixedOffset, UTC, ZERO) diff --git a/colander/tests/test_iso8601.py b/colander/tests/test_iso8601.py index 45d4a2a..bd73ae2 100644 --- a/colander/tests/test_iso8601.py +++ b/colander/tests/test_iso8601.py @@ -77,7 +77,7 @@ class Test_parse_timezone(unittest.TestCase): from ..iso8601 import (parse_date, FixedOffset) if tzstring is None: tzstring = '' - dt = parse_date("2006-10-11T00:14:33{}".format(tzstring), **kw) + dt = parse_date("2006-10-11T00:14:33{0}".format(tzstring), **kw) return dt.tzinfo def test_default_Z(self): From b354a5b327f95f6ac24fd70d63b7c8d04590510b Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 10 Sep 2014 19:28:03 +0000 Subject: [PATCH 22/99] change to please deform deform imports ISO8601_REGEX, so we should make that available too --- colander/iso8601.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/iso8601.py b/colander/iso8601.py index 2f7deaa..f608c11 100644 --- a/colander/iso8601.py +++ b/colander/iso8601.py @@ -1,5 +1,5 @@ from __future__ import absolute_import import iso8601 -from iso8601.iso8601 import (parse_date, ParseError, Utc, FixedOffset, UTC, ZERO) +from iso8601.iso8601 import (parse_date, ParseError, Utc, FixedOffset, UTC, ZERO, ISO8601_REGEX) __all__ = ["parse_date", "ParseError", "Utc", "FixedOffset"] From b45d9be152d5ee69c0a50ce0949f7cbe928358e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20Command=C3=A9?= Date: Sat, 11 Oct 2014 14:15:49 +0200 Subject: [PATCH 23/99] Fix sequences flatten method --- colander/__init__.py | 2 +- colander/tests/test_colander.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 82eb96b..30d9490 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -466,7 +466,7 @@ class SchemaType(object): selfname = prefix else: selfname = '%s%s' % (prefix, node.name) - result[selfname] = appstruct + result[selfname.rstrip('.')] = appstruct return result def unflatten(self, node, paths, fstruct): diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 4ff4939..c922968 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -1309,6 +1309,16 @@ class TestSequence(unittest.TestCase): result = typ.flatten(node, [1, 2]) self.assertEqual(result, {'node.0': 1, 'node.1': 2}) + def test_flatten_with_integer(self): + from colander import Integer + node = DummySchemaNode(None, name='node') + node.children = [ + DummySchemaNode(Integer(), name='foo'), + ] + typ = self._makeOne() + result = typ.flatten(node, [1, 2]) + self.assertEqual(result, {'node.0': 1, 'node.1': 2}) + def test_flatten_listitem(self): node = DummySchemaNode(None, name='node') node.children = [ From 98642f126cab77c202c9845837f0899e4ee20ae0 Mon Sep 17 00:00:00 2001 From: Nando Florestan Date: Wed, 26 Nov 2014 09:11:10 -0200 Subject: [PATCH 24/99] Length validator can have its messages customized --- colander/__init__.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 6d56520..18277f4 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -317,27 +317,35 @@ class Range(object): max_err = _(self.max_err, mapping={'val':value, 'max':self.max}) raise Invalid(node, max_err) + class Length(object): - """ Validator which succeeds if the value passed to it has a + """Validator which succeeds if the value passed to it has a length between a minimum and maximum. The value is most often a - string.""" - def __init__(self, min=None, max=None): + string. The error message(s) may be customized. + """ + + def __init__(self, min=None, max=None, min_msg=None, max_msg=None): self.min = min self.max = max + self.min_msg = min_msg + self.max_msg = max_msg def __call__(self, node, value): if self.min is not None: if len(value) < self.min: - min_err = _('Shorter than minimum length ${min}', - mapping={'min':self.min}) + min_err = self.min_msg or _( + 'Shorter than minimum length ${min}', + mapping={'min': self.min}) raise Invalid(node, min_err) if self.max is not None: if len(value) > self.max: - max_err = _('Longer than maximum length ${max}', - mapping={'max':self.max}) + max_err = self.max_msg or _( + 'Longer than maximum length ${max}', + mapping={'max': self.max}) raise Invalid(node, max_err) + class OneOf(object): """ Validator which succeeds if the value passed to it is one of a fixed set of values """ @@ -1719,7 +1727,7 @@ def _Schema__new__(cls, *args, **kw): node.add(n) return node -Schema = _SchemaMeta('Schema', (object,), +Schema = _SchemaMeta('Schema', (object,), dict(schema_type=Mapping, node_type=SchemaNode, __new__=_Schema__new__)) From b6ca0f9816b5759ac75eeedadc769d8ffd761273 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:03:43 -0500 Subject: [PATCH 25/99] Add explicit support (and tests) for Python 3.4 and PyPy3. --- CHANGES.txt | 5 +++++ setup.py | 3 ++- tox.ini | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index da20d24..efa4a6d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,11 @@ Unreleased ---------- +Platform +-------- + +- Addd explicit support for Python 3.4 and PyPy3. + Bug Fixes ~~~~~~~~~ diff --git a/setup.py b/setup.py index 1961c5a..5389131 100644 --- a/setup.py +++ b/setup.py @@ -43,12 +43,13 @@ setup(name='colander', classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python", + "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/tox.ini b/tox.ini index 69295a2..8963fe4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py26,py27,py32,py33,pypy,cover,docs + py26,py27,py32,py33,py34,pypy,pypy3,cover,docs [testenv] commands = From 704f9b80113edc65648bb1d2413c91b7a431f841 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:04:05 -0500 Subject: [PATCH 26/99] No doctests present. --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8963fe4..82daf13 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,5 @@ basepython = python2.6 commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html -# sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest deps = Sphinx From c3e2bf25d9a7655ae30c74a76b017eef591857c1 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:04:26 -0500 Subject: [PATCH 27/99] Require preserving 100% coverage. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 82daf13..4010935 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ commands = basepython = python2.6 commands = - python setup.py nosetests --with-xunit --with-xcoverage + python setup.py nosetests --with-xunit --with-xcoverage --cover-min-percentage=100 deps = nosexcover From f2b23a7aca0e66272e1f6005340c4d0b5593d2bd Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:06:53 -0500 Subject: [PATCH 28/99] Add Travis CI integration. --- .travis.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5a205b2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# Wire up travis +language: python + +env: + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py32 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=pypy + - TOXENV=pypy3 + - TOXENV=cover + +install: + - travis_retry pip install tox + +script: + - travis_retry tox + +notifications: + email: + - pyramid-checkins@lists.repoze.org From a787bce3de9430153c459c9cbba0639ff361e801 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:11:14 -0500 Subject: [PATCH 29/99] Nest into Github better. --- CHANGES.txt => CHANGES.rst | 0 README.txt => README.rst | 0 setup.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename CHANGES.txt => CHANGES.rst (100%) rename README.txt => README.rst (100%) diff --git a/CHANGES.txt b/CHANGES.rst similarity index 100% rename from CHANGES.txt rename to CHANGES.rst diff --git a/README.txt b/README.rst similarity index 100% rename from README.txt rename to README.rst diff --git a/setup.py b/setup.py index 5389131..4d47ea1 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,8 @@ def read(fname): return fp.read() try: - README = read(os.path.join(here, 'README.txt')) - CHANGES = read(os.path.join(here, 'CHANGES.txt')) + README = read(os.path.join(here, 'README.rst')) + CHANGES = read(os.path.join(here, 'CHANGES.rst')) except: README = '' CHANGES = '' From 1fd406177dff69fcc62eb4076b2021273a5fa15a Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:12:11 -0500 Subject: [PATCH 30/99] Add Travis and RTD badges. --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 215ba5d..91ae97c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,15 @@ Colander ======== + +.. image:: https://travis-ci.org/Pylons/colander.svg?branch=master + :target: https://travis-ci.org/Pylons/colander + +.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master + :target: http://docs.pylonsproject.org/projects/pyramid/en/master/ + :alt: Documentation Status + + An extensible package which can be used to: - deserialize and validate a data structure composed of strings, From 8b3e8cb637341976a91d1b823ee1520ded835d3f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:16:27 -0500 Subject: [PATCH 31/99] Update list of supported Python versions. --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 91ae97c..ca7ab6f 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,8 @@ An extensible package which can be used to: - serialize an arbitrary data structure to a data structure composed of strings, mappings, and lists. -It runs on Python 2.6, 2.7, 3.2, and 3.3. +It runs on Python 2.6, 2.7, 3.2, 3.3, and 3.4, and on current PyPy +and PyPy3 versions. Please see http://docs.pylonsproject.org/projects/colander/en/latest/ for further documentation. From 13de90275b7f341f318fbfdf7713e119d7b44b38 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 11:38:35 -0500 Subject: [PATCH 32/99] Note first contrib from @mcdonc, add myself at first commit. --- CONTRIBUTORS.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 6c6130b..466fc7b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -101,7 +101,8 @@ and agrees to the terms above in the section within this document entitled Contributors ------------ -- Chris McDonough, 2011/02/16 +- Chris McDonough, 2010/03/11 +- Tres Seaver, 2010/04/05 - Chris Withers, 2011/05/45 - Mathieu Le Marec - Pasquet (kiorky), 2011/07/11 - Atsushi Odagiri, 2012/02/04 From da3d16af40d8e4b52e79b8812fac57a100c517d7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 12:09:16 -0500 Subject: [PATCH 33/99] Move changelog entry for new feature to correct section. --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index efa4a6d..84f5230 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -41,6 +41,9 @@ Features - Allow localization of error messages returned by ``colander.Invalid.asdict`` by adding an optional ``translate`` callable argument. +- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error + message to be used when the node is required and missing + 1.0b1 (2013-09-01) ------------------ @@ -96,9 +99,6 @@ Features - The ``typ`` of a ``SchemaNode`` can optionally be pased in as a keyword argument. See https://github.com/Pylons/colander/issues/90 -- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error - message to be used when the node is required and missing - 1.0a5 (2013-05-31) ------------------ From 58f591d33de298ddf5fd7cc48c7d2509dceb41bf Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 12:56:31 -0500 Subject: [PATCH 34/99] Fix RTD badge. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ca7ab6f..dba9a81 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Colander .. image:: https://travis-ci.org/Pylons/colander.svg?branch=master :target: https://travis-ci.org/Pylons/colander -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master - :target: http://docs.pylonsproject.org/projects/pyramid/en/master/ +.. image:: https://readthedocs.org/projects/colander/badge/?version=master + :target: http://docs.pylonsproject.org/projects/colander/en/master/ :alt: Documentation Status From a3fe918f4ebf9e477548e3a8c645001b1afae50c Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 12:57:26 -0500 Subject: [PATCH 35/99] Fix RTD badge HARDER. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dba9a81..ddc4d1a 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Colander :target: https://travis-ci.org/Pylons/colander .. image:: https://readthedocs.org/projects/colander/badge/?version=master - :target: http://docs.pylonsproject.org/projects/colander/en/master/ + :target: http://docs.pylonsproject.org/projects/colander/en/latest/ :alt: Documentation Status From 852786e74a62eb4ca96fd36ed1f2a18763cd15f3 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 12:58:10 -0500 Subject: [PATCH 36/99] Fix RTD badge MOAR HARDER. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ddc4d1a..71e181a 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Colander .. image:: https://travis-ci.org/Pylons/colander.svg?branch=master :target: https://travis-ci.org/Pylons/colander -.. image:: https://readthedocs.org/projects/colander/badge/?version=master +.. image:: https://readthedocs.org/projects/colander/badge/?version=latest :target: http://docs.pylonsproject.org/projects/colander/en/latest/ :alt: Documentation Status From 0e7a65623207fd3f762b66542525a6b06fac582f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 13:19:17 -0500 Subject: [PATCH 37/99] Un-re-break RTD links after MM's RTD config. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 71e181a..dba9a81 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Colander .. image:: https://travis-ci.org/Pylons/colander.svg?branch=master :target: https://travis-ci.org/Pylons/colander -.. image:: https://readthedocs.org/projects/colander/badge/?version=latest - :target: http://docs.pylonsproject.org/projects/colander/en/latest/ +.. image:: https://readthedocs.org/projects/colander/badge/?version=master + :target: http://docs.pylonsproject.org/projects/colander/en/master/ :alt: Documentation Status From 6f78d66c89b7eb1e779d96af9e27c1c183ba8af5 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 14:06:51 -0500 Subject: [PATCH 38/99] Sync w/ 1.0 release. --- CHANGES.rst | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 84f5230..05afe56 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,12 +6,27 @@ Platform - Addd explicit support for Python 3.4 and PyPy3. +Features +~~~~~~~~ + +- Add `Any` validator which succeeds if at least one of its subvalidators + succeeded. + +- Allow localization of error messages returned by ``colander.Invalid.asdict`` + by adding an optional ``translate`` callable argument. + +- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error + message to be used when the node is required and missing + +1.0 (2014-11-26) +---------------- + Bug Fixes ~~~~~~~~~ -- Removed forked iso8601 and change to dependency on pypi iso8601 +- Removed forked ``iso8601`` and change to dependency on PyPI ``iso8601`` (due to float rounding bug on microsecond portion when parsing - iso8601 datetime string). Left an iso8601.py stub for backwards + iso8601 datetime string). Left an ``iso8601.py`` stub for backwards compatibility. - Time of "00:00" no longer gives ``colander.Invalid``. @@ -32,17 +47,8 @@ Bug Fixes See https://github.com/Pylons/colander/pull/183 and https://github.com/Pylons/colander/pull/185 -Features -~~~~~~~~ +- Updated translations: ``fr``, ``de``, ``ja`` -- Add `Any` validator which succeeds if at least one of its subvalidators - succeeded. - -- Allow localization of error messages returned by ``colander.Invalid.asdict`` - by adding an optional ``translate`` callable argument. - -- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error - message to be used when the node is required and missing 1.0b1 (2013-09-01) ------------------ From 4ad4b6a7441888ada83073734137eb571bfaf8cf Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 26 Nov 2014 14:07:01 -0500 Subject: [PATCH 39/99] Mark dev status. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4d47ea1..130f79f 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ testing_extras = ['nose', 'coverage'] docs_extras = ['Sphinx'] setup(name='colander', - version='1.0b1', + version='1.1dev', description=('A simple schema-based serialization and deserialization ' 'library'), long_description=README + '\n\n' + CHANGES, From 69445070471f4d4f71244111a6364f334a835a5d Mon Sep 17 00:00:00 2001 From: Nando Florestan Date: Thu, 27 Nov 2014 10:56:32 -0200 Subject: [PATCH 40/99] Rename "min_msg" to "min_err" for coherence w/ Range --- colander/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index e3bf762..69502f8 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -375,23 +375,23 @@ class Length(object): string. The error message(s) may be customized. """ - def __init__(self, min=None, max=None, min_msg=None, max_msg=None): + def __init__(self, min=None, max=None, min_err=None, max_err=None): self.min = min self.max = max - self.min_msg = min_msg - self.max_msg = max_msg + self.min_err = min_err + self.max_err = max_err def __call__(self, node, value): if self.min is not None: if len(value) < self.min: - min_err = self.min_msg or _( + min_err = self.min_err or _( 'Shorter than minimum length ${min}', mapping={'min': self.min}) raise Invalid(node, min_err) if self.max is not None: if len(value) > self.max: - max_err = self.max_msg or _( + max_err = self.max_err or _( 'Longer than maximum length ${max}', mapping={'max': self.max}) raise Invalid(node, max_err) From 4e92d9467278f5dcd0707e55d6a8bb355a2f56c7 Mon Sep 17 00:00:00 2001 From: Nando Florestan Date: Thu, 27 Nov 2014 11:15:13 -0200 Subject: [PATCH 41/99] Error msgs passed to Length have interpolation too Also awesomefy the Length docstring and update tests --- colander/__init__.py | 41 +++++++++++++++++++++++---------- colander/tests/test_colander.py | 28 +++++++++++++++------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 69502f8..2abb42d 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -371,29 +371,46 @@ class Range(object): class Length(object): """Validator which succeeds if the value passed to it has a - length between a minimum and maximum. The value is most often a - string. The error message(s) may be customized. - """ + length between a minimum and maximum, expressed in the + optional ``min`` and ``max`` arguments. + The value can be any sequence, most often a string. + + If ``min`` is not specified, or is specified as ``None``, + no lower bound exists. If ``max`` is not specified, or + is specified as ``None``, no upper bound exists. + + The default error messages are "Shorter than minimum length ${min}" + and "Longer than maximum length ${max}". These can be customized: + + ``min_err`` is used to form the ``msg`` of the + :exc:`colander.Invalid` error when reporting a validation failure + caused by a value not meeting the minimum length. If ``min_err`` is + specified, it must be a string. The string may contain the + replacement target ``${min}``. + + ``max_err`` is used to form the ``msg`` of the + :exc:`colander.Invalid` error when reporting a validation failure + caused by a value exceeding the maximum length. If ``max_err`` is + specified, it must be a string. The string may contain the + replacement target ``${max}``. + """ + min_err = _('Shorter than minimum length ${min}') + max_err = _('Longer than maximum length ${max}') def __init__(self, min=None, max=None, min_err=None, max_err=None): self.min = min self.max = max - self.min_err = min_err - self.max_err = max_err + self.min_err = self.min_err if min_err is None else min_err + self.max_err = self.max_err if max_err is None else max_err def __call__(self, node, value): if self.min is not None: if len(value) < self.min: - min_err = self.min_err or _( - 'Shorter than minimum length ${min}', - mapping={'min': self.min}) + min_err = _(self.min_err, mapping={'min': self.min}) raise Invalid(node, min_err) - if self.max is not None: if len(value) > self.max: - max_err = self.max_err or _( - 'Longer than maximum length ${max}', - mapping={'max': self.max}) + max_err = _(self.max_err, mapping={'max': self.max}) raise Invalid(node, max_err) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 4ff4939..2aa8471 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -430,10 +430,11 @@ class TestEmail(unittest.TestCase): self.assertRaises(Invalid, validator, None, 'me@here..com') self.assertRaises(Invalid, validator, None, 'me@we-here-.com') + class TestLength(unittest.TestCase): - def _makeOne(self, min=None, max=None): + def _makeOne(self, **kw): from colander import Length - return Length(min=min, max=max) + return Length(**kw) def test_success_no_bounds(self): validator = self._makeOne() @@ -461,6 +462,17 @@ class TestLength(unittest.TestCase): e = invalid_exc(validator, None, 'ab') self.assertEqual(e.msg.interpolate(), 'Longer than maximum length 1') + def test_min_failure_msg_override(self): + validator = self._makeOne(min=1, min_err='Need at least ${min}, mate') + e = invalid_exc(validator, None, []) + self.assertEqual(e.msg.interpolate(), 'Need at least 1, mate') + + def test_max_failure_msg_override(self): + validator = self._makeOne(max=1, max_err='No more than ${max}, mate') + e = invalid_exc(validator, None, [1, 2]) + self.assertEqual(e.msg.interpolate(), 'No more than 1, mate') + + class TestOneOf(unittest.TestCase): def _makeOne(self, values): from colander import OneOf @@ -725,7 +737,7 @@ class TestMapping(unittest.TestCase): typ = self._makeOne() result = typ.serialize(node, {'a':drop}) self.assertEqual(result, {}) - + def test_flatten(self): node = DummySchemaNode(None, name='node') int1 = DummyType() @@ -3635,7 +3647,7 @@ class TestUltraDeclarative(unittest.TestCase, TestFunctional): return schema class TestDeclarativeWithInstantiate(unittest.TestCase, TestFunctional): - + def _makeSchema(self, name='schema'): import colander @@ -3649,20 +3661,20 @@ class TestDeclarativeWithInstantiate(unittest.TestCase, TestFunctional): ob = colander.SchemaNode(colander.GlobalObject(package=colander)) @colander.instantiate() class seq(colander.SequenceSchema): - + @colander.instantiate() class tup(colander.TupleSchema): tupint = colander.SchemaNode(colander.Int()) tupstring = colander.SchemaNode(colander.String()) - + @colander.instantiate() class tup(colander.TupleSchema): tupint = colander.SchemaNode(colander.Int()) tupstring = colander.SchemaNode(colander.String()) - + @colander.instantiate() class seq2(colander.SequenceSchema): - + @colander.instantiate() class mapping(colander.MappingSchema): key = colander.SchemaNode(colander.Int()) From d9098dd1334e64aee3856da8f0583bd8b4378dc3 Mon Sep 17 00:00:00 2001 From: Nando Florestan Date: Thu, 27 Nov 2014 11:24:36 -0200 Subject: [PATCH 42/99] Add entry to CHANGES and my name to CONTRIBUTORS --- CHANGES.rst | 10 +++++++--- CONTRIBUTORS.txt | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 05afe56..9c79ce1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Unreleased Platform -------- -- Addd explicit support for Python 3.4 and PyPy3. +- Add explicit support for Python 3.4 and PyPy3. Features ~~~~~~~~ @@ -18,6 +18,10 @@ Features - Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error message to be used when the node is required and missing +- Add ``min_err`` and ``max_err`` arguments to ``Length``, thus allowing + customization of its error messages. + + 1.0 (2014-11-26) ---------------- @@ -414,7 +418,7 @@ Features id='a2', ) c = colander.SchemaNode( - colander.String(), + colander.String(), id='c2', ) e = colander.SchemaNode( @@ -470,7 +474,7 @@ Features id='a2', ) c = colander.SchemaNode( - colander.String(), + colander.String(), id='c2', ) e = colander.SchemaNode( diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 466fc7b..46bdd17 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -120,3 +120,4 @@ Contributors - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 +- Nando Florestan, 2014-11-27 From 98e9bc775af469377942cf19af5186a8f9f31b2d Mon Sep 17 00:00:00 2001 From: Jaseem Abid Date: Mon, 16 Jun 2014 10:34:20 +0530 Subject: [PATCH 43/99] Allow interpolation of `missing_msg` string The missing message can be interpolated with the properties `title` as well as `name` --- colander/__init__.py | 7 +++++-- colander/tests/test_colander.py | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 82eb96b..02e64c9 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1812,7 +1812,7 @@ class _SchemaNode(object): validator = None default = null missing = required - missing_msg = _('Required') + missing_msg = 'Required' name = '' raw_title = _marker title = _marker @@ -1955,7 +1955,10 @@ class _SchemaNode(object): if appstruct is null: appstruct = self.missing if appstruct is required: - raise Invalid(self, self.missing_msg) + raise Invalid(self, _(self.missing_msg, + mapping={'title': self.title, + 'name':self.name})) + if isinstance(appstruct, deferred): # unbound schema with deferreds raise Invalid(self, self.missing_msg) # We never deserialize or validate the missing value diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 4ff4939..567312c 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2483,6 +2483,14 @@ class TestSchemaNode(unittest.TestCase): e = invalid_exc(node.deserialize, null) self.assertEqual(e.msg, 'Missing') + def test_deserialize_value_is_null_with_interpolated_missing_msg(self): + from colander import null + typ = DummyType() + node = self._makeOne(typ, missing_msg='Missing attribute ${title}', + name='name_a') + e = invalid_exc(node.deserialize, null) + self.assertEqual(e.msg.interpolate(), 'Missing attribute Name A') + def test_deserialize_noargs_uses_default(self): typ = DummyType() node = self._makeOne(typ) From eb7626e1e0d3f09231703f044c0afaac78a31ec0 Mon Sep 17 00:00:00 2001 From: Jaseem Abid Date: Thu, 27 Nov 2014 20:10:27 +0530 Subject: [PATCH 44/99] Fix test name --- colander/tests/test_colander.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 567312c..0a45ed9 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2483,7 +2483,7 @@ class TestSchemaNode(unittest.TestCase): e = invalid_exc(node.deserialize, null) self.assertEqual(e.msg, 'Missing') - def test_deserialize_value_is_null_with_interpolated_missing_msg(self): + def test_deserialize_value_with_interpolated_missing_msg(self): from colander import null typ = DummyType() node = self._makeOne(typ, missing_msg='Missing attribute ${title}', From 2b38a4d39ad6de2b8ea0eb5aa296798c80bb4ca3 Mon Sep 17 00:00:00 2001 From: Jaseem Abid Date: Thu, 27 Nov 2014 20:17:35 +0530 Subject: [PATCH 45/99] Update changes and contributors files --- CHANGES.rst | 2 ++ CONTRIBUTORS.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 05afe56..ec55409 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -105,6 +105,8 @@ Features - The ``typ`` of a ``SchemaNode`` can optionally be pased in as a keyword argument. See https://github.com/Pylons/colander/issues/90 +- Allow interpolation of `missing_msg` with properties `title` and `name` + 1.0a5 (2013-05-31) ------------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 466fc7b..7a31d8d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -117,6 +117,7 @@ Contributors - Veeti Paananen, 2013/08/20 - Michael Howitz, 2013/12/05 - Alex Marandon, 2013/12/21 +- Jaseem Abid, 2014/06/16 - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 From 186fde756806594ca7426ad73bd5a3e31386d5cf Mon Sep 17 00:00:00 2001 From: Amos Latteier Date: Sun, 30 Nov 2014 10:21:47 -0500 Subject: [PATCH 46/99] Remove unneeded else. Update contributors. --- CONTRIBUTORS.txt | 1 + colander/__init__.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 466fc7b..4233f59 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -120,3 +120,4 @@ Contributors - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 +- Amos Latteier, 2014/11/30 diff --git a/colander/__init__.py b/colander/__init__.py index 8a9201d..84e6107 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1973,8 +1973,7 @@ class _SchemaNode(object): raise UnboundDeferredError( "Schema node {node} has an unbound deferred validator" .format(node=self)) - else: - self.validator(self, appstruct) + self.validator(self, appstruct) return appstruct def add(self, node): From 29c893088b743b9f5d2ebe15e1cc9e42f23d852a Mon Sep 17 00:00:00 2001 From: Romain Commande Date: Mon, 8 Dec 2014 18:16:42 +0100 Subject: [PATCH 47/99] Documented changes and added name to contributors --- CHANGES.txt | 3 +++ CONTRIBUTORS.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index da20d24..fd37870 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -27,6 +27,9 @@ Bug Fixes See https://github.com/Pylons/colander/pull/183 and https://github.com/Pylons/colander/pull/185 +- Fix an issue where the ``flatten()'' method produces an invalid name (ex: "answer.0.") for the type "Sequence" + https://github.com/Pylons/colander/issues/179 + Features ~~~~~~~~ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 6c6130b..bdd654f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -119,3 +119,4 @@ Contributors - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 +- Romain Commandé, 2014/10/11 From 048b76c143d9888d0e7fae45f0f685f53e58c73b Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Thu, 11 Dec 2014 10:58:21 +0100 Subject: [PATCH 48/99] Add a UUID string validator The `uuid` validator uses a regex validator to match on all UUID strings allowed by the `uuid.UUID` class. --- colander/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index be973bb..235503a 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -300,9 +300,9 @@ class Regex(object): validation succeeds; otherwise, :exc:`colander.Invalid` is raised with the ``msg`` error message. """ - def __init__(self, regex, msg=None): + def __init__(self, regex, msg=None, flags=0): if isinstance(regex, string_types): - self.match_object = re.compile(regex) + self.match_object = re.compile(regex, flags) else: self.match_object = regex if msg is None: @@ -465,6 +465,11 @@ URL_REGEX = r"""(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9 url = Regex(URL_REGEX, _('Must be a URL')) + +UUID_REGEX = r"""^(?:urn:uuid:)?\{?[a-f0-9]{8}(?:-?[a-f0-9]{4}){3}-?[a-f0-9]{12}\}?$""" +uuid = Regex(UUID_REGEX, _('Invalid UUID string'), 2) # 2 = re.IGNORECASE + + class SchemaType(object): """ Base class for all schema types """ def flatten(self, node, appstruct, prefix='', listitem=False): From 369e9dc2ef273def6a742ea35fddceedd3d29bb8 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Thu, 11 Dec 2014 11:01:58 +0100 Subject: [PATCH 49/99] Sign the contributors agreement Signed-off-by: Jimmy Thrasibule --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index a2709d0..d208a1d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -122,3 +122,4 @@ Contributors - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 - Amos Latteier, 2014/11/30 +- Jimmy Thrasibule, 2014/12/11 From 442014665227c736bffa8b68e40e1caadf1c4c3d Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 22:09:14 +0100 Subject: [PATCH 50/99] Fix UUID validator implementation - Document the new `flags` parameter now accepted by the Regex validator. - Make use of the correct regex flag. --- colander/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 235503a..d0cd3fa 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -293,6 +293,9 @@ class Regex(object): error message to be used; otherwise, defaults to 'String does not match expected pattern'. + The ``regex`` expression behaviour can be modified by specifying + any ``flags`` value taken by ``re.compile``. + The ``regex`` argument may also be a pattern object (the result of ``re.compile``) instead of a string. @@ -467,7 +470,7 @@ url = Regex(URL_REGEX, _('Must be a URL')) UUID_REGEX = r"""^(?:urn:uuid:)?\{?[a-f0-9]{8}(?:-?[a-f0-9]{4}){3}-?[a-f0-9]{12}\}?$""" -uuid = Regex(UUID_REGEX, _('Invalid UUID string'), 2) # 2 = re.IGNORECASE +uuid = Regex(UUID_REGEX, _('Invalid UUID string'), re.IGNORECASE) class SchemaType(object): From 3741e6296093b9ca43e6230b9a88883c5603fded Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 22:50:04 +0100 Subject: [PATCH 51/99] Add unit tests for UUID validator --- colander/tests/test_colander.py | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 961ff5d..cd2976f 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -538,6 +538,57 @@ class Test_url_validator(unittest.TestCase): from colander import Invalid self.assertRaises(Invalid, self._callFUT, val) +class TestUUID(unittest.TestCase): + def _callFUT(self, val): + from colander import uuid + return uuid(None, val) + + def test_success_hexadecimal(self): + val = '123e4567e89b12d3a456426655440000' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_with_dashes(self): + val = '123e4567-e89b-12d3-a456-426655440000' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_upper_case(self): + val = '123E4567-E89B-12D3-A456-426655440000' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_with_braces(self): + val = '{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_with_urn_ns(self): + val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_failure_random_string(self): + val = 'not-a-uuid' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_not_hexadecimal(self): + val = '123zzzzz-uuuu-zzzz-uuuu-42665544zzzz' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_invalid_length(self): + # Correct UUID: 8-4-4-4-12 + val = '88888888-333-4444-333-cccccccccccc' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_with_invalid_urn_ns(self): + val = 'urn:abcd:{123e4567-e89b-12d3-a456-426655440000}' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + class TestSchemaType(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import SchemaType From 6c873ba762e94c82cd8ee389e357f8d4df508633 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 23:17:24 +0100 Subject: [PATCH 52/99] Add the `uuid` validator to the API documentation --- docs/api.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 51b6d75..15439f5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -78,6 +78,11 @@ Validators A validator which ensures the value is a URL (via regex). + .. attribute:: uuid + + A UUID hexadecimal string validator via regular expression + using :class:`colander.Regex`. + Types ~~~~~ From 412d2a79bd88cc41437d9fb216a1eafa3e2c9af5 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 23:25:13 +0100 Subject: [PATCH 53/99] Include the right CHANGES file in documentation --- docs/changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes.rst b/docs/changes.rst index abb1f10..d9e113e 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1 +1 @@ -.. include:: ../CHANGES.txt +.. include:: ../CHANGES.rst From b706ea144cab2068be2f047593cc12fb47b9d683 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 23:49:01 +0100 Subject: [PATCH 54/99] Fix test unit errors Replace assertIsNone by assertEqual. --- colander/tests/test_colander.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index cd2976f..7918e45 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -546,27 +546,27 @@ class TestUUID(unittest.TestCase): def test_success_hexadecimal(self): val = '123e4567e89b12d3a456426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_dashes(self): val = '123e4567-e89b-12d3-a456-426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_upper_case(self): val = '123E4567-E89B-12D3-A456-426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_braces(self): val = '{123e4567-e89b-12d3-a456-426655440000}' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_urn_ns(self): val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_failure_random_string(self): val = 'not-a-uuid' From e07092050c04ec72a3a8aed33462a3c4b5ac2ced Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 18 Dec 2014 21:23:36 -0500 Subject: [PATCH 55/99] Speed up Travis start via 'sudo: false'. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5a205b2..cb98fdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ # Wire up travis language: python +sudo: false env: - TOXENV=py26 From c1b4f71a07e9b53d7f74afecd7a1d0fd9b4fcd2d Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 14 Jan 2015 22:52:26 +0200 Subject: [PATCH 56/99] make version PEP440 compliant --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 130f79f..81a6314 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ testing_extras = ['nose', 'coverage'] docs_extras = ['Sphinx'] setup(name='colander', - version='1.1dev', + version='1.1.dev0', description=('A simple schema-based serialization and deserialization ' 'library'), long_description=README + '\n\n' + CHANGES, From afd3c0f6a9ccfc34720a0e71a69786c6f503191c Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Thu, 29 Jan 2015 22:08:53 +0200 Subject: [PATCH 57/99] fix badly named test The test says it's serializing and then calls deserialize() --- colander/tests/test_colander.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 7918e45..64e1cf9 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -1550,7 +1550,7 @@ class TestInteger(unittest.TestCase): result = typ.serialize(node, val) self.assertEqual(result, colander.null) - def test_serialize_emptystring(self): + def test_deserialize_emptystring(self): import colander val = '' node = DummySchemaNode(None) From 19e4416138d191a1c3d20c832abf5fd1c5bfd692 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 4 Feb 2015 10:35:46 -0500 Subject: [PATCH 58/99] Wrap long lines. --- colander/tests/test_colander.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 64e1cf9..d7be160 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -110,9 +110,10 @@ class TestInvalid(unittest.TestCase): exc1.add(exc2, 2) exc2.add(exc3, 3) d = exc1.asdict() - self.assertEqual(d, - {'node1.node2.3': 'exc1; exc2; validator1; validator2', - 'node1.node3': 'exc1; message1'}) + self.assertEqual( + d, + {'node1.node2.3': 'exc1; exc2; validator1; validator2', + 'node1.node3': 'exc1; message1'}) def test_asdict_with_all_validator_functional(self): # see https://github.com/Pylons/colander/issues/2 @@ -153,7 +154,8 @@ class TestInvalid(unittest.TestCase): result = str(exc1) self.assertEqual( result, - "{'node1.node2.3': 'exc1; exc2; exc3', 'node1.node4': 'exc1; exc4'}" + "{'node1.node2.3': 'exc1; exc2; exc3', " + "'node1.node4': 'exc1; exc4'}" ) def test___setitem__fails(self): @@ -425,7 +427,8 @@ class TestEmail(unittest.TestCase): validator = self._makeOne() from colander import Invalid self.assertRaises(Invalid, validator, None, 'me@here.') - self.assertRaises(Invalid, validator, None, 'name@here.tldiswaytoolooooooooong') + self.assertRaises(Invalid, + validator, None, 'name@here.tldiswaytoolooooooooong') self.assertRaises(Invalid, validator, None, '@here.us') self.assertRaises(Invalid, validator, None, 'me@here..com') self.assertRaises(Invalid, validator, None, 'me@we-here-.com') @@ -2393,8 +2396,9 @@ class TestSchemaNode(unittest.TestCase): def test_ctor_no_title(self): child = DummySchemaNode(None, name='fred') - node = self._makeOne(None, child, validator=1, default=2, name='name_a', - missing='missing') + node = self._makeOne( + None, child, validator=1, default=2, + name='name_a', missing='missing') self.assertEqual(node.typ, None) self.assertEqual(node.children, [child]) self.assertEqual(node.validator, 1) @@ -3512,7 +3516,8 @@ class TestFunctional(object): def test_invalid_asdict(self): expected = { 'schema.int': '20 is greater than maximum value 10', - 'schema.ob': 'The dotted name "no.way.this.exists" cannot be imported', + 'schema.ob': 'The dotted name "no.way.this.exists" ' + 'cannot be imported', 'schema.seq.0.0': '"q" is not a number', 'schema.seq.1.0': '"w" is not a number', 'schema.seq.2.0': '"e" is not a number', From 69e94759491b23b6c27f333f9479f4d84f9a2519 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 4 Feb 2015 10:38:43 -0500 Subject: [PATCH 59/99] Simplify defaults. --- colander/__init__.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 3229830..06f4648 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -356,26 +356,26 @@ class Range(object): provided, it defaults to ``'${val} is greater than maximum value ${max}'``. """ - min_err = _('${val} is less than minimum value ${min}') - max_err = _('${val} is greater than maximum value ${max}') + _MIN_ERR = _('${val} is less than minimum value ${min}') + _MAX_ERR = _('${val} is greater than maximum value ${max}') - def __init__(self, min=None, max=None, min_err=None, max_err=None): + def __init__(self, min=None, max=None, min_err=_MIN_ERR, max_err=_MAX_ERR): self.min = min self.max = max - if min_err is not None: - self.min_err = min_err - if max_err is not None: - self.max_err = max_err + self.min_err = min_err + self.max_err = max_err def __call__(self, node, value): if self.min is not None: if value < self.min: - min_err = _(self.min_err, mapping={'val':value, 'min':self.min}) + min_err = _( + self.min_err, mapping={'val':value, 'min':self.min}) raise Invalid(node, min_err) if self.max is not None: if value > self.max: - max_err = _(self.max_err, mapping={'val':value, 'max':self.max}) + max_err = _( + self.max_err, mapping={'val':value, 'max':self.max}) raise Invalid(node, max_err) @@ -404,14 +404,14 @@ class Length(object): specified, it must be a string. The string may contain the replacement target ``${max}``. """ - min_err = _('Shorter than minimum length ${min}') - max_err = _('Longer than maximum length ${max}') + _MIN_ERR = _('Shorter than minimum length ${min}') + _MAX_ERR = _('Longer than maximum length ${max}') - def __init__(self, min=None, max=None, min_err=None, max_err=None): + def __init__(self, min=None, max=None, min_err=_MIN_ERR, max_err=_MAX_ERR): self.min = min self.max = max - self.min_err = self.min_err if min_err is None else min_err - self.max_err = self.max_err if max_err is None else max_err + self.min_err = min_err + self.max_err = max_err def __call__(self, node, value): if self.min is not None: From 6f9518109e9f5ef61a5f00d2579dbb199176cdde Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 4 Feb 2015 10:39:09 -0500 Subject: [PATCH 60/99] Normalize date format. --- CONTRIBUTORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e214e3d..2635aa3 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -121,6 +121,6 @@ Contributors - Cédric Messiant, 2014/06/27 - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 -- Nando Florestan, 2014-11-27 +- Nando Florestan, 2014/11/27 - Amos Latteier, 2014/11/30 - Jimmy Thrasibule, 2014/12/11 From d1ec2ca0f11605fd4ce24b3c4551a71cb4b91444 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 4 Feb 2015 10:39:40 -0500 Subject: [PATCH 61/99] Push-down. --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ec4bc17..10947d3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,9 @@ Platform Features ~~~~~~~~ +- Add ``min_err`` and ``max_err`` arguments to ``Length``, thus allowing + customization of its error messages. + - Add `Any` validator which succeeds if at least one of its subvalidators succeeded. @@ -18,9 +21,6 @@ Features - Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error message to be used when the node is required and missing -- Add ``min_err`` and ``max_err`` arguments to ``Length``, thus allowing - customization of its error messages. - 1.0 (2014-11-26) ---------------- From b1273ed6936d23e60ae89e7f804a6d208737c36e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 4 Feb 2015 11:23:41 -0500 Subject: [PATCH 62/99] Silence setuptools chatter. Prefer Python 2.7 over 2.6 for coverage, docs. --- tox.ini | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 4010935..4c01db3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,20 +4,22 @@ envlist = [testenv] commands = - python setup.py dev - python -Wd setup.py test -q + python setup.py -q dev + python -Wd setup.py -q test -q [testenv:cover] basepython = - python2.6 + python2.7 commands = - python setup.py nosetests --with-xunit --with-xcoverage --cover-min-percentage=100 + nosetests --with-xunit --with-xcoverage --cover-min-percentage=100 deps = + nose + coverage nosexcover [testenv:docs] basepython = - python2.6 + python2.7 commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html deps = From 0afcf9b60679cd868df70ae8226e3ea1486fb75a Mon Sep 17 00:00:00 2001 From: "Juan E. Pemberthy" Date: Thu, 19 Feb 2015 11:09:07 -0800 Subject: [PATCH 63/99] Update __init__.py Fix typo. --- colander/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index c1b7508..1de747d 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1893,7 +1893,7 @@ class _SchemaNode(object): @staticmethod def schema_type(): raise NotImplementedError( - 'Schema node construction without a typ argument or ' + 'Schema node construction without a type argument or ' 'a schema_type() callable present on the node class ' ) From ff665931df4227715b095a7d3176d4060636bba3 Mon Sep 17 00:00:00 2001 From: Daniel Dourvaris Date: Wed, 4 Mar 2015 10:17:41 +0200 Subject: [PATCH 64/99] Added greek translations. --- colander/locale/el/LC_MESSAGES/colander.mo | Bin 0 -> 3674 bytes colander/locale/el/LC_MESSAGES/colander.po | 156 +++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 colander/locale/el/LC_MESSAGES/colander.mo create mode 100644 colander/locale/el/LC_MESSAGES/colander.po diff --git a/colander/locale/el/LC_MESSAGES/colander.mo b/colander/locale/el/LC_MESSAGES/colander.mo new file mode 100644 index 0000000000000000000000000000000000000000..44a781f3252a983a0ddcfa0a231f3338e7353262 GIT binary patch literal 3674 zcmbW2U2GIp6vu}Tzt)dLRALnMP>K}jl-(8xE|iqgf|XXVEhd_n=62_HJ2HD`xied+ z%_f$QAPJc8z=Pt0Ci>QoY+zHDLi%Q6jI(b(5sZoP#rW=n5B~4W?CvaGHi0JF-`u(9 zo_p^9oO|Z}hV|Dq{I%e>3BUF=YE=6>uvXLF!>}8C75oD11-}E|0RIFxgKO7m+S_0& zsO*k~G(csS2X}xs!uLOcYTfVPYv7vonzjYp1nvZTz&6kVH-gu}H^Dh@1NaO09C#mm z1^hc4uX#e#p2zqFQ2E~!jt_!+F&+e;2G4^n;1y8Cy$H60zlGy?4dFP@V5rumk)T+yicVM$@)~pM<;wD!T%B1iS}s1-HaBtqU9g zw}E+ZKllUqF8Bwi>~|n2HGdl11%3r8J$?q?0`GxJ$N#_=L4ruHgPq_p@O_ZLufdPO zW$Z>KX@)jEU!`R(UMsCXG<%RgO%TOEzxtRTH+rsU{F{MK%qO zh$feE^Q07pJk`ZPcCnFAug5t#T$%{sGj3E59ok@NT5_l=$(=+cWY}3Vlgkhy&T(vq zaUNT#YEj!*+;KufQ`8%pB5J5MW)#X*TOWP>KqzWhJ=}l@Bvbj;Dg&NjrbWy!B%-Lk zRmxWfe%RWeEz%Dj$l@aHPF7AdlyiJx7@Zs*)Q&50C1q^si;PmGFcO-!bI`=#e$D&Q zr&2uY`Z*frr*kH9s*OzAvZi1tOl?D@%!pq&WY|2&Rff5#NvhtgEOX&NXrtKLuS~AT{R&8f!Ck3voofayubc#VAb4o>WU!)>^a)rCKo1B)6DrPVuUD z--JwDe#f?^xH=b|!DY2`E@fHj6iajMgtXO#(+3RwLoOZD7D-Ck783?UPISb_6R}~g zV$z44v}x$QxwNB?+DYmkIy5lYbEvOxxPN3McH;P`epuBS9O!*WY?3wKM)sJPt#Bm`|B%qnh0wJ>I3qcT*ysjKi)Ic7x1u^-;-$gG1ReNqtP1+@d}^ zC#RT1xgIc>=)ryKNXvsT7wH_%qmOcyNus~efx*EZ%j~da`e5vYm6OcUkJvKfBuQlb zq0`;DpMuBkHbI-aDX~2^G%(a(69VUt9kzvwR;iErUB%^R-CbFWnPNZ6h)IXL-8pwc z-&eC!F;8%*_oMF`XedeAH*UJIqg>$nBei<(bdq}6IATn6bj51jz?&~$@`_%Gyk&3B z`_Wq|pCj)saMiox70a{U3YE{7FL*0n35N6df7!cBZQcTGi&#@cAp||V>S&6YXO%3fYT@~& zUnQ!btDmT3n1jmNT7FfDV@1JR4$99j_b@nB^8^E>PAEyG*Eevq46$yOuc#Vc?L^&^ zIozIzo5;7Va+XL~^?9yrx2inr>!I1#&%05(+)I^<4K6{nMI<&le+x$`mapP|W43%A zMd5!Yqc-I)amcz=kshJWv7&k3JdU&kl?uu?3i5)tfTv>loaTLthk2yh?^Ax7YhtO- zt#>_0zF)uodKk|k`pcD4gSV3EY(-3zkd75(bzZ#)MO}bFLG>uVGV!)pzJyD3Mf0v< zMG(>=Y?koD;ch&7x}$AWMGyJDR~1&5>uNlR1v`ZW1F9aq7`Q-IR9!+MoIu^mNBYEj e*Kl$O6WrC{BOfHM!G~VeZTL+HitzD1_x}S44cYVn literal 0 HcmV?d00001 diff --git a/colander/locale/el/LC_MESSAGES/colander.po b/colander/locale/el/LC_MESSAGES/colander.po new file mode 100644 index 0000000..f5d9782 --- /dev/null +++ b/colander/locale/el/LC_MESSAGES/colander.po @@ -0,0 +1,156 @@ +# Greek translations for colander. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the colander project. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: colander 1.0b1\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2013-11-16 14:23+0900\n" +"PO-Revision-Date: 2015-03-04 10:03+0200\n" +"Last-Translator: Daniel Dourvaris \n" +"Language-Team: el \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 1.3\n" + +#: colander/__init__.py:240 +msgid "Invalid value" +msgstr "Λάθος στοιχείο" + +#: colander/__init__.py:283 +msgid "String does not match expected pattern" +msgstr "Το κείμενο δέν αντιστοιχεί στην μακέτα" + +#: colander/__init__.py:302 +msgid "Invalid email address" +msgstr "Λάθος διεύθηνση email" + +#: colander/__init__.py:330 +msgid "${val} is less than minimum value ${min}" +msgstr "${val} είναι λιγότερο απο το ελάχιστο ${min}" + +#: colander/__init__.py:331 +msgid "${val} is greater than maximum value ${max}" +msgstr "${val} είναι μεγαλύτερο απο το μέγιστο ${max}" + +#: colander/__init__.py:363 +msgid "Shorter than minimum length ${min}" +msgstr "Λιγότερο απο το ελάχιστο ${min} στοιχεία" + +#: colander/__init__.py:369 +msgid "Longer than maximum length ${max}" +msgstr "Μεγαλύτερο απο το μέγιστο ${max} χαρακτήρες" + +#: colander/__init__.py:382 +msgid "\"${val}\" is not one of ${choices}" +msgstr "\"${val}\" δέν είναι ενα απο ${choices}" + +#: colander/__init__.py:392 +msgid "One or more of the choices you made was not acceptable" +msgstr "Ενα η παραπάνω απο τις επιλογές δέν ήταν αποδεχτό" + +#: colander/__init__.py:414 colander/__init__.py:419 +msgid "\"${val}\" is not a valid credit card number" +msgstr "\"${val}'" δέν είναι έγκυρη πιστοτική κάρτα" + +#: colander/__init__.py:440 +msgid "Must be a URL" +msgstr "Πρέπει να είναι URL" + +#: colander/__init__.py:536 +msgid "\"${val}\" is not a mapping type: ${err}" +msgstr "\"${val}\" δέν είναι τύπου mapping: ${err}" + +#: colander/__init__.py:578 +msgid "Unrecognized keys in mapping: \"${val}\"" +msgstr "Αγνωστα keys στο mapping: \"${val}\"" + +#: colander/__init__.py:674 colander/__init__.py:905 +msgid "\"${val}\" is not iterable" +msgstr "\"${val}\" δέν είναι λίστα" + +#: colander/__init__.py:682 +msgid "" +"\"${val}\" has an incorrect number of elements (expected ${exp}, was " +"${was})" +msgstr "" +"\"${val}\" έχει λάθος νούμερο στοιχέιων (ήθελε ${exp}, έχει " +"${was})" + +#: colander/__init__.py:821 colander/__init__.py:852 +msgid "${cstruct} is not iterable" +msgstr "${cstruct} δέν είναι λίστα" + +#: colander/__init__.py:1124 +msgid "${val} cannot be serialized: ${err}" +msgstr "${val} δέν μπορεί να γίνει serialize:${err}" + +#: colander/__init__.py:1142 +msgid "${val} is not a string: ${err}" +msgstr "${val} δέν είναι κείμενο: ${err}" + +#: colander/__init__.py:1162 colander/__init__.py:1173 +msgid "\"${val}\" is not a number" +msgstr "\"${val}\" δέν είναι νούμερο" + +#: colander/__init__.py:1317 +msgid "${val} is not a string" +msgstr "${val} δέν είναι κείμενο" + +#: colander/__init__.py:1328 +msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})" +msgstr "\"${val}\" ούτε στο (${false_choices}) είναι, ούτε στο (${true_choices})" + +#: colander/__init__.py:1388 colander/__init__.py:1405 +#: colander/__init__.py:1415 +msgid "relative name \"${val}\" irresolveable without package" +msgstr "" + +#: colander/__init__.py:1445 +msgid "\"${val}\" has no __name__" +msgstr "\"${val}\" δέν έχει __name__" + +#: colander/__init__.py:1454 +msgid "\"${val}\" is not a string" +msgstr "\"${val}\" δέν είναι κείμενο" + +#: colander/__init__.py:1463 +msgid "The dotted name \"${name}\" cannot be imported" +msgstr "" + +#: colander/__init__.py:1511 colander/__init__.py:1587 +msgid "Invalid date" +msgstr "Λάθος ημερομηνία" + +#: colander/__init__.py:1527 +msgid "\"${val}\" is not a datetime object" +msgstr "\"${val}\" δέν είναι τύπου datetime" + +#: colander/__init__.py:1598 +msgid "\"${val}\" is not a date object" +msgstr "\"${val}\" δέν είναι τύπου date" + +#: colander/__init__.py:1659 +msgid "Invalid time" +msgstr "Λάθος ώρα" + +#: colander/__init__.py:1670 +msgid "\"${val}\" is not a time object" +msgstr "\"${val}\" δέν είναι τύπου time" + +#: colander/__init__.py:1795 +msgid "Required" +msgstr "Απαραίτητο" + +#: colander/tests/test_colander.py:295 colander/tests/test_colander.py:302 +msgid "fail ${val}" +msgstr "λάθος ${val}" + +#: colander/tests/test_colander.py:469 +msgid "${val}: ${choices}" +msgstr "${val}: ${choices}" + From 40a29fdedf981665163e485a1c6a62059795391e Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 29 Apr 2015 16:56:30 +0000 Subject: [PATCH 65/99] unify `required`, `null`, and `drop` When I was trying to introspect schemas I printed out "missing: ", "missing: " and "missing: ". The first is supposed to be a colander.required but unfortunately it's just a generic object placeholder. This PR is to unify colander.required, colander.drop and colander.null. --- colander/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 1de747d..9355146 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -22,7 +22,12 @@ from . import iso8601 _ = translationstring.TranslationStringFactory('colander') -required = object() +class _required(object): + """ Represents a required value in colander-related operations. """ + def __repr__(self): + return '' + +required = _required() _marker = required # bw compat class _null(object): @@ -46,7 +51,8 @@ class _drop(object): Represents a value that should be dropped if it is missing during deserialization. """ - pass + def __repr__(self): + return '' drop = _drop() From 05d6f77195c33e873b959bfa71f08ec49e6bddb3 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 29 Apr 2015 13:29:48 -0400 Subject: [PATCH 66/99] Revert "Update __init__.py" --- colander/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 1de747d..c1b7508 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1893,7 +1893,7 @@ class _SchemaNode(object): @staticmethod def schema_type(): raise NotImplementedError( - 'Schema node construction without a type argument or ' + 'Schema node construction without a typ argument or ' 'a schema_type() callable present on the node class ' ) From 7e5e0e6b113d0c0f46965246378fbafecc572dd1 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 29 Apr 2015 18:29:09 +0000 Subject: [PATCH 67/99] fix coverage --- colander/tests/test_colander.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 8c342c8..6d05d71 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -3783,6 +3783,16 @@ class Test_null(unittest.TestCase): import pickle self.assertTrue(pickle.loads(pickle.dumps(null)) is null) +class Test_required(unittest.TestCase): + def test___repr__(self): + from colander import required + self.assertEqual(repr(required), '') + +class Test_drop(unittest.TestCase): + def test___repr__(self): + from colander import drop + self.assertEqual(repr(drop), '') + class Dummy(object): pass From a32ef67f277c1d5d43c432221b0c24fce5075821 Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Wed, 29 Apr 2015 20:03:47 +0000 Subject: [PATCH 68/99] added backticks to clarify it's not a typo --- colander/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index c1b7508..f6226b7 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1893,7 +1893,7 @@ class _SchemaNode(object): @staticmethod def schema_type(): raise NotImplementedError( - 'Schema node construction without a typ argument or ' + 'Schema node construction without a `typ` argument or ' 'a schema_type() callable present on the node class ' ) From 5c41323dbb4cee8edf535076b186690e3a92095d Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Tue, 5 May 2015 18:06:12 +0000 Subject: [PATCH 69/99] make default UTC directly The current docs show `colander.DateTime(default_tzinfo=)` due to using the `_marker`. `iso8601` has a `UTC` object that is an already instatiated version of `iso8601.Utc()` and outputs its repr as "". This should change the docs to print out: `colander.DateTime(default_tzinfo=)`. --- colander/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index 01e724d..8580e23 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1577,9 +1577,7 @@ class DateTime(SchemaType): """ err_template = _('Invalid date') - def __init__(self, default_tzinfo=_marker): - if default_tzinfo is _marker: - default_tzinfo = iso8601.Utc() + def __init__(self, default_tzinfo=iso8601.UTC): self.default_tzinfo = default_tzinfo def serialize(self, node, appstruct): From ba7ccbdc955af7f512b0363213b9d62e0382537c Mon Sep 17 00:00:00 2001 From: Tim Tisdall Date: Tue, 5 May 2015 19:00:38 +0000 Subject: [PATCH 70/99] fix pdf rendering by removing unused .gif It seems LaTeX doesn't support GIF files and that's what readthedocs are using to generate the PDF versions of the docs. So, removed the reference to the `.gif` file and deleted the unused cruft. --- docs/.static/logo_hi.gif | Bin 3924 -> 0 bytes docs/.static/repoze.css | 33 --------------------------------- docs/conf.py | 2 +- 3 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 docs/.static/logo_hi.gif delete mode 100644 docs/.static/repoze.css diff --git a/docs/.static/logo_hi.gif b/docs/.static/logo_hi.gif deleted file mode 100644 index 178e49f67b79e740fbd6916b4434a922df97a9a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3924 zcmWlYc{r4b1IOPv&4qU|xl+beN;FBO^2<91##Q55vbDyMh_yqq?5dq%%s8TPGzqJb zBZ@UrBGEWvO6+72QjIfN)zBeU&2RVf$M@gw^L(D?l%#CGbWeHM$85(Hm>FIHBaBy~ZR;$$z1W9X4*49?X2JZ(42WOJ}xi5{Fl-0`I%?O0=~O<@80C( zq~g(?%F4C5qcp-`BeabBTNBqb%i`+Jgm?rhWTx`2QHcX#*Bwj1T8x#EJHv5_Yp9v;KP z!?)^h49FXwDej#=pZuWZdRtptOw5llKc0HrEe#1i!45k9>Ek<354VY7SyfqnU|?YW zMSfF*`$lG|5;u6v{Wq4I2{>YQc{u-ADx&Gb22RC{@u#fI~6R}(cbPh27}hv*myGZ zhcrI_QI|9-D(ch6rNzYsA0O|CQz6dIjKIKv!h&=c7neKrS6y9Q{lD`)dermv^zhxg zcQ5A)4jnq==;(O(@Zs^H_La}e&xhK-ef#=+_`&GYR^@o_^PvagJi*In-4i1n;=Ht# z&r36}{`7En{bz0E_0&*PQ`6gpS1+D*{r!5PyZz?e)L%3j_4V{nkE{-Y9RHr5__+M{ z*RShqtDn|ZKXU3OE3=W8xHirelHflqOswWQ_g{5#7OR66_~zAiznt4zWAP0v+WpR>|Y=D zeKzgsK~;IH`*rCW{-*o4i<*~veG|6H^RTZbheY}oc~~DO2Jmc?)@6w zd^K$^&F^wvoy<9*yr5S+EP)w8J}_bTDX)8d+F`|lVmvB3!gahWeQUzH^x8{n;^dy2F4x_bU=;2mU4%rD~c_Fp~Q+0vT zsXs!F?=mN6R?opFZjXhNv@99@7@K?g&|%n25&%c_2Y4dvrkba%_#4(98b2h>Xzpir zrnr$Jd?m7q{Cgd*X-W8O3AitIi%P$opJ04diVYOu6^+;uN>h(IUgRZF?&(g*?=(98{gIHOtD(~nb5KUSWAvlk3ljkspnpGD-#-eSOfw_4wSLh|B zD|DlpGdlO*mfH$kH|VIQLceF zvL3+F_N%T*HAYktqjhWw*m5e5FlcHx26m(OC&j6!QPxN1Ls-`1w`Cu!y7z@)Z5c;Y zT=17&dm{0>?QB=5qZ8AQ=Q7MlnN`J`!?K8`GfVF=2KG8~=v1OTfG9BWeY7>E339Pk z^UE?bb>bnxn1x^r_@8kELmo!?g2NLdWBi z+Qu>-@OLqYb5rsE;<6*j|6jZ3rMqG05AF{Q?oQp z^mf7_4JE02PyR0`oj7i5{c!LZDmPV0h(pIHW8qWWuGtOVJgv7NqAY@E`#qTHew@rW zJtaWC9Ie7QD0R^XoOT3~cslloY&8ceT{h9;*zk$`Wh5+yi=(~e60}!=$_>(csfp9c zS&}q7_L>^?VOW%p;sI#Yyfz|UMx6GMTU@x3`adHc2%>l>c`#z9Ia^~TA|Kmamcsiu z$+eg-hi0hT99$6?3uYNq4Vi5RsFnB*jWQY}pv;6a4JFeVwb!|ZU#7@Dl&e+>mDV;a*#q}%P4Xh1 zqKyht98O3;_$qhDX*<{^J2)15tkdHd=lOs_cK7)FL)~{bxmzjICNB$3c18n1Df*!# zTS6M1lPI!gjTk3L2=G!!0h7Vd*HY3=eSNk5%;SU^7Czs8%}-Nv-V`PO;)5WJXYsc% z+Uqf}bDKK_{vMgyp(WiFtPY;=0##tr98N9W(e)Eh-hj^K6TH0ADeJ(+DmH42u>d;F zbRmfx!Z=&T>r^Ig>jag$DHN5 zXzA*k7)#%!etqg`Z{%Bg!wR~MbXJrFA zyLyiVyN0V-fIULFX44*nb-{ZoNC$sGRcLeyiZ{3!8i`hFeOO3$PSrRL)3Gm80@%!S zlbb{?hxfhKZocsmNGzaTAan|h`#{uYwPO25l^lyU+IK0j7Hynz2;55v(Gh!K9^9OK zp9Yk$5XN$iMCS@CBPHqg(XK7G6{rK05Lo^)^%8}$-7eZ}qZj?5juF?;nlVk>dbtdy z55D0=!?qwdrWsXnQa53gh;uAp%ToaEGBJXAAS#0=NK#zn)9<+}tq$W7bmO)ZNoX@& z@3ung?oAWaS&_+3n`%*zB7zWjME++`Fi?dv$8UrNbF5fATUCL#rP6pX7P6=)$x8Y z`-_QwRibZ2Lh%lo$+W0Y)+eR;B5|hH=yiJPqVd zAYfr+fjBXvFfpUyB-qR$q*1i7QW#c-&{Y8z5Fh{?3>(n18u%bkD^LVBaPaMH43oRt z@2t?aK=>&Q9~+#-kZ`n25L>uNrVJ?$t*WeEeT;87!JP{;@WL+d-|jC#;3T+6 z7OqZ}t<=w%EX3ajaw1hQHCMP(fr?aR&syfF>~r4hUtD75d}LnyoOn@HcyVnM*P_Dw zJfmHlNMx{(^}VsL8@X}~EK`ybDG`ANxf>4Tk}{GB65_H*bjLr}b_f|J$!U@0njXkA zuMz3h5M#MGiwu__1wpGNk7AH-l$2)}j51{7Y}FOWT4}z+SpMPFdlh{94&zzw?{-LT~54m`R5wVQ~>aAAu=fh@C(S6hR(3k^@GtjK@Be@0ZBpJ zkn5St2&Mi8=h9r<$Qi^2R5V%RZ8N-w3~H$GOG<38N*g0V(-qmqoLI4!ff!;@$!Qyw z!tN&+r)!{-Tv#{`y8f8KV-~tziCZOW0!~Cz8MYLJSxOMOEX1_rSAG?CT>>wmz@8W& z0SNQtLvcD0BZKgS-AclwGqD{4!pQhG0C5H+^g;kZ1{|OudMSwc?f4u8A$R`w^|7np NR<8m$A`Au``ab|A+A075 diff --git a/docs/.static/repoze.css b/docs/.static/repoze.css deleted file mode 100644 index 1fbae93..0000000 --- a/docs/.static/repoze.css +++ /dev/null @@ -1,33 +0,0 @@ -@import url('default.css'); -body { - background-color: #006339; -} - -div.document { - background-color: #dad3bd; -} - -div.sphinxsidebar h3, h4, h5, a { - color: #127c56 !important; -} - -div.related { - color: #dad3bd !important; - background-color: #00744a; -} - -div.related a { - color: #dad3bd !important; -} - -/* override the justify text align of the default */ - -div.body p { - text-align: left !important; -} - -/* fix google chrome
 tag renderings */
-
-pre {
-   line-height: normal !important;
-}
diff --git a/docs/conf.py b/docs/conf.py
index d53d0a1..c4222a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -199,7 +199,7 @@ latex_documents = [
 
 # The name of an image file (relative to this directory) to place at the
 # top of the title page.
-latex_logo = '.static/logo_hi.gif'
+#latex_logo = '.static/logo_hi.gif'
 
 # For "manual" documents, if this is true, then toplevel headings are
 # parts, not chapters.

From b79d3a06b376588fe1e4c3326fd39e296a689d44 Mon Sep 17 00:00:00 2001
From: Steve Piercy 
Date: Wed, 6 May 2015 00:35:52 -0700
Subject: [PATCH 71/99] - use package pylons-sphinx-themes instead of git
 submodule to build docs - in docs/conf.py, use dynamic values in subs to save
 manual updates - Fixes #221

---
 docs/conf.py | 55 +++++++++++++---------------------------------------
 rtd.txt      |  1 +
 setup.py     |  6 +++++-
 tox.ini      |  5 ++---
 4 files changed, 21 insertions(+), 46 deletions(-)
 create mode 100644 rtd.txt

diff --git a/docs/conf.py b/docs/conf.py
index c4222a0..8b1991e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,7 +12,9 @@
 # All configuration values have a default value; values that are commented
 # out serve to show the default value.
 
-import sys, os
+import sys, os, datetime
+import pkg_resources
+import pylons_sphinx_themes
 
 # General configuration
 # ---------------------
@@ -32,13 +34,14 @@ master_doc = 'index'
 
 # General substitutions.
 project = 'colander'
-copyright = '2012, Agendaless Consulting '
+thisyear = datetime.datetime.now().year
+copyright = '2012-%s, Agendaless Consulting ' % thisyear
 
 # The default replacements for |version| and |release|, also used in various
 # other places throughout the built documents.
 #
 # The short X.Y version.
-version = '1.0b1'
+version = pkg_resources.get_distribution('colander').version
 # The full version, including alpha/beta/rc tags.
 release = version
 
@@ -49,7 +52,7 @@ release = version
 today_fmt = '%B %d, %Y'
 
 # List of documents that shouldn't be included in the build.
-unused_docs = ['_themes/README']
+#unused_docs = ['_themes/README']
 
 # List of directories, relative to source directories, that shouldn't be
 # searched for source files.
@@ -73,43 +76,11 @@ unused_docs = ['_themes/README']
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
-# Add and use Pylons theme
-if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers
-    from subprocess import call, Popen, PIPE
-
-    p = Popen('which git', shell=True, stdout=PIPE)
-    git = p.stdout.read().strip()
-    cwd = os.getcwd()
-    _themes = os.path.join(cwd, '_themes')
-
-    if not os.path.isdir(_themes):
-        call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git',
-                '_themes'])
-    else:
-        os.chdir(_themes)
-        call([git, 'checkout', 'master'])
-        call([git, 'pull'])
-        os.chdir(cwd)
-
-    sys.path.append(os.path.abspath('_themes'))
-
-    parent = os.path.dirname(os.path.dirname(__file__))
-    sys.path.append(os.path.abspath(parent))
-    wd = os.getcwd()
-    os.chdir(parent)
-    os.system('%s setup.py test -q' % sys.executable)
-    os.chdir(wd)
-
-    for item in os.listdir(parent):
-        if item.endswith('.egg'):
-            sys.path.append(os.path.join(parent, item))
-
 # Options for HTML output
 # -----------------------
-
-sys.path.append(os.path.abspath('_themes'))
-html_theme_path = ['_themes']
-html_theme = 'pylons'
+# sys.path.append(os.path.abspath('_themes'))
+html_theme = 'pyramid'
+html_theme_path = pylons_sphinx_themes.get_html_themes_path()
 html_theme_options = dict(github_url='https://github.com/Pylons/colander')
 
 # The style sheet to use for HTML and HTML Help pages. A file of that name
@@ -146,7 +117,7 @@ html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+html_use_smartypants = False
 
 # Custom sidebar templates, maps document names to template names.
 #html_sidebars = {}
@@ -177,7 +148,7 @@ html_last_updated_fmt = '%b %d, %Y'
 #html_file_suffix = ''
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'atemplatedoc'
+htmlhelp_basename = 'colanderdoc'
 
 
 # Options for LaTeX output
@@ -193,7 +164,7 @@ htmlhelp_basename = 'atemplatedoc'
 # (source start file, target name, title,
 #  author, document class [howto/manual]).
 latex_documents = [
-  ('index', 'atemplate.tex', 'colander Documentation',
+  ('index', 'colander.tex', 'colander Documentation',
    'Pylons Developers', 'manual'),
 ]
 
diff --git a/rtd.txt b/rtd.txt
new file mode 100644
index 0000000..142b6ca
--- /dev/null
+++ b/rtd.txt
@@ -0,0 +1 @@
+-e .[docs]
diff --git a/setup.py b/setup.py
index 81a6314..bab2fb4 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,11 @@ except:
 requires = ['translationstring', 'iso8601']
 
 testing_extras = ['nose', 'coverage']
-docs_extras = ['Sphinx']
+docs_extras = [
+    'Sphinx >= 1.3.1',
+    'docutils',
+    'pylons-sphinx-themes',
+]
 
 setup(name='colander',
       version='1.1.dev0',
diff --git a/tox.ini b/tox.ini
index 4c01db3..f5a94e4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -20,7 +20,6 @@ deps =
 [testenv:docs]
 basepython =
     python2.7
-commands = 
+commands =
+    pip install colander[docs]
     sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
-deps =
-    Sphinx

From 83fa3edddc18c641cf501479dabd739596b8fb2a Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Wed, 6 May 2015 14:48:46 +0000
Subject: [PATCH 72/99] remove duplication of documentation

---
 colander/__init__.py |  6 +++---
 docs/api.rst         | 16 ++++++----------
 2 files changed, 9 insertions(+), 13 deletions(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index 9355146..26299f6 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -47,9 +47,9 @@ class _null(object):
 null = _null()
 
 class _drop(object):
-    """
-    Represents a value that should be dropped if it is missing during
-    deserialization.
+    """ Represents a value that will be dropped from the schema if it
+    is missing during *deserialization*.  Passed as a value to the
+    `missing` keyword argument of :class:`SchemaNode`.
     """
     def __repr__(self):
         return ''
diff --git a/docs/api.rst b/docs/api.rst
index 15439f5..e3584c8 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -147,16 +147,12 @@ Schema-Related
 
   .. autoclass:: instantiate
 
-  .. attribute:: null
+  .. autodata:: null
+     :annotation:
 
-     Represents a null value in colander-related operations.
+  .. autodata:: required
+     :annotation:
 
-  .. attribute:: required
+  .. autodata:: drop
+     :annotation:
 
-     Represents a required value in colander-related operations.
-
-  .. attribute:: drop
-
-     Represents a value that will be dropped from the schema if it is missing
-     during *deserialization*.  Passed as a value to the `missing` keyword
-     argument of :class:`SchemaNode`.

From 1cddeb278c3eb960837289a80c7ce96c927555aa Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Thu, 4 Jun 2015 20:14:28 +0000
Subject: [PATCH 73/99] update list of supported python versions

---
 docs/index.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/index.rst b/docs/index.rst
index 1ac8051..d899227 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,7 +6,8 @@ Colander
 
 Colander is useful as a system for validating and deserializing data obtained
 via XML, JSON, an HTML form post or any other equally simple data
-serialization.  It runs on Python 2.6, 2.7 and 3.2.  Colander can be used to:
+serialization.  It runs on Python 2.6, 2.7, 3.2, 3.3, 3.4, pypy and pypy3.
+Colander can be used to:
 
 - Define a data schema.
 

From 4851bbb1416f56ce90fcd56782e07632f1cb61e4 Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Wed, 10 Jun 2015 17:22:48 +0000
Subject: [PATCH 74/99] make docs align with code

---
 colander/__init__.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index d2a518b..c574f76 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1785,7 +1785,9 @@ class _SchemaNode(object):
     - ``typ``: The 'type' for this node.  It should be an
       instance of a class that implements the
       :class:`colander.interfaces.Type` interface.  If ``typ`` is not passed,
-      it defaults to ``colander.Mapping()``.
+      a call to the ``schema_type()`` method on this class is made to
+      get a default type.  (When subclassing, ``schema_type()`` should
+      be overridden to provide a reasonable default type).
 
     - ``*children``: a sequence of subnodes.  If the subnodes of this
       node are not known at construction time, they can later be added

From a3ca8fecaa92f996788ceba1de68c4ba492d9a43 Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Tue, 16 Jun 2015 19:50:04 +0000
Subject: [PATCH 75/99] spelling

---
 colander/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index d2a518b..1bd8f87 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1148,7 +1148,7 @@ class String(SchemaType):
 
        - A non-Unicode input value to ``serialize`` is converted to a
          Unicode using the encoding (``unicode(value, encoding)``);
-         subsequently the Unicode object is reeencoded to a ``str``
+         subsequently the Unicode object is re-encoded to a ``str``
          object using the encoding and returned.
 
        - A Unicode input value to ``deserialize`` is returned

From e1e876212caa5b56d8ce6fef378a9b31bffd6e3d Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Tue, 16 Jun 2015 20:12:41 +0000
Subject: [PATCH 76/99] fix String encoding

fixes #233
---
 colander/__init__.py            | 5 ++++-
 colander/tests/test_colander.py | 7 +++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index d2a518b..2aa8995 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1184,7 +1184,10 @@ class String(SchemaType):
                 else:
                     result = text_type(appstruct)
             else:
-                result = text_type(appstruct)
+                if self.encoding:
+                    result = text_type(appstruct).encode(self.encoding)
+                else:
+                    result = text_type(appstruct)
             return result
         except Exception as e:
             raise Invalid(node,
diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index 6d05d71..644a04b 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -1557,6 +1557,13 @@ class TestString(unittest.TestCase):
         e = invalid_exc(typ.serialize, node, not_utf8)
         self.assertTrue('cannot be serialized' in e.msg)
 
+    def test_serialize_encoding_with_non_string_type(self):
+        utf8 = '123'.encode('utf-8')
+        node = DummySchemaNode(None)
+        typ = self._makeOne('utf-8')
+        result = typ.serialize(node, 123)
+        self.assertEqual(result, utf8)
+
 class TestInteger(unittest.TestCase):
     def _makeOne(self):
         from colander import Integer

From be63b5533cdde8fd3e825779bac688663857b0a0 Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Mon, 22 Jun 2015 14:28:12 +0000
Subject: [PATCH 77/99] pulled `text_type()` out of `if` statement

---
 colander/__init__.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index 2aa8995..d514ca2 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1184,10 +1184,9 @@ class String(SchemaType):
                 else:
                     result = text_type(appstruct)
             else:
+                result = text_type(appstruct)
                 if self.encoding:
-                    result = text_type(appstruct).encode(self.encoding)
-                else:
-                    result = text_type(appstruct)
+                    result = result.encode(self.encoding)
             return result
         except Exception as e:
             raise Invalid(node,

From a941784b5ad168597fb352ee2b241281f1f83d26 Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Tue, 23 Jun 2015 13:29:24 +0000
Subject: [PATCH 78/99] be more explicit about string type

---
 colander/tests/test_colander.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index 644a04b..0dd99aa 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -1558,7 +1558,7 @@ class TestString(unittest.TestCase):
         self.assertTrue('cannot be serialized' in e.msg)
 
     def test_serialize_encoding_with_non_string_type(self):
-        utf8 = '123'.encode('utf-8')
+        utf8 = text_type('123').encode('utf-8')
         node = DummySchemaNode(None)
         typ = self._makeOne('utf-8')
         result = typ.serialize(node, 123)

From 7b8e44b78c7b2fca3635f6044d04bc20e2f80f0d Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Tue, 23 Jun 2015 17:10:13 +0000
Subject: [PATCH 79/99] noted string encoding fix in CHANGES

---
 CHANGES.rst | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGES.rst b/CHANGES.rst
index c7aaf66..57d74ec 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -27,6 +27,9 @@ Bug Fixes
 - Fix an issue where the ``flatten()`` method produces an invalid name
   (ex: "answer.0.") for the type "Sequence".  See
   https://github.com/Pylons/colander/issues/179
+- Fixed issue with ``String`` not being properly encoded when non-string
+  values were passed into ``serialize()``
+  See `#235 `_
 
 
 1.0 (2014-11-26)

From ce2890816507585530d7ba3547ed5216f8d534ec Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Fri, 10 Jul 2015 17:32:05 +0000
Subject: [PATCH 80/99] issue with `title` being overwritten when child

PR #183 and #185 tried to fix this but missed one aspect when
a schemanode is added as a child via class definitions.
---
 colander/__init__.py            |  9 ++++-----
 colander/tests/test_colander.py | 10 ++++++++++
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/colander/__init__.py b/colander/__init__.py
index a9d4be5..20695ea 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1862,7 +1862,7 @@ class _SchemaNode(object):
     missing = required
     missing_msg = 'Required'
     name = ''
-    raw_title = _marker
+    raw_title = _marker  # only changes if title is explicitly set
     title = _marker
     description = ''
     widget = None
@@ -1888,11 +1888,10 @@ class _SchemaNode(object):
             self.typ = self.schema_type()
 
         # bw compat forces us to manufacture a title if one is not supplied
-        title = kw.get('title', _marker)
+        title = kw.get('title', self.title)
         if title is _marker:
-            if self.title is _marker:
-                name = kw.get('name', self.name)
-                kw['title'] = name.replace('_', ' ').title()
+            name = kw.get('name', self.name)
+            kw['title'] = name.replace('_', ' ').title()
         else:
             kw['raw_title'] = title
 
diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index 0dd99aa..4fff3b3 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -2847,6 +2847,16 @@ class TestSchemaNodeSubclassing(unittest.TestCase):
         node = MyNode(name='my', title='other title')
         self.assertEqual(node.title, 'other title')
 
+    def test_subelement_title_not_overwritten(self):
+        import colander
+        class SampleNode(colander.SchemaNode):
+            schema_type = colander.String
+            title = 'Some Title'
+        class SampleSchema(colander.Schema):
+            node = SampleNode()
+        schema = SampleSchema()
+        self.assertEqual('Some Title', schema.children[0].title)
+
     def test_subclass_value_overridden_by_constructor(self):
         import colander
         class MyNode(colander.SchemaNode):

From 2c0d663141ef9351b3ccb03cd1463c8b9952f3d8 Mon Sep 17 00:00:00 2001
From: Tim Tisdall 
Date: Mon, 13 Jul 2015 14:27:54 +0000
Subject: [PATCH 81/99] note in CHANGES for #239

---
 CHANGES.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGES.rst b/CHANGES.rst
index 57d74ec..0b3c91b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -30,6 +30,8 @@ Bug Fixes
 - Fixed issue with ``String`` not being properly encoded when non-string
   values were passed into ``serialize()``
   See `#235 `_
+- ``title`` was being overwritten when made a child through defining a schema
+  as a class.  See `#239 `_
 
 
 1.0 (2014-11-26)

From 7000b312a1a165010306d26311dccb45c5a9ea47 Mon Sep 17 00:00:00 2001
From: Tres Seaver 
Date: Tue, 6 Oct 2015 23:05:38 -0400
Subject: [PATCH 82/99] Garden changelog.

---
 CHANGES.rst | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 0b3c91b..62d2ee9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,27 +9,31 @@ Platform
 Features
 ~~~~~~~~
 
-- Add ``min_err`` and ``max_err`` arguments to ``Length``, thus allowing
+- Add ``min_err`` and ``max_err`` arguments to ``Length``, allowing
   customization of its error messages.
 
-- Add `Any` validator which succeeds if at least one of its subvalidators
-  succeeded.
+- Add ``colander.Any`` validator: succeeds if at least one of its
+  subvalidators succeeded.
 
 - Allow localization of error messages returned by ``colander.Invalid.asdict``
   by adding an optional ``translate`` callable argument.
 
-- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error
-  message to be used when the node is required and missing
+- Add a ``missing_msg`` argument to ``SchemaNode``, allowing customization
+  of the error message used when the node is required and missing.
 
 Bug Fixes
 ---------
 
+- Update translations: ``de``, ``ja``, ``fr``.
+
 - Fix an issue where the ``flatten()`` method produces an invalid name
-  (ex: "answer.0.") for the type "Sequence".  See
+  (ex: "answer.0.") for the type ``Sequence``.  See
   https://github.com/Pylons/colander/issues/179
+
 - Fixed issue with ``String`` not being properly encoded when non-string
   values were passed into ``serialize()``
   See `#235 `_
+
 - ``title`` was being overwritten when made a child through defining a schema
   as a class.  See `#239 `_
 

From 6fec48187c835fc6ba4d7e20ee1a1608616510e7 Mon Sep 17 00:00:00 2001
From: Mathieu Bridon 
Date: Sat, 16 May 2015 18:03:01 +0200
Subject: [PATCH 83/99] New NoneOf validator

This is the opposite of OneOf: it ensures that the supplied value is
none of the choices.
---
 CHANGES.rst                     |  2 ++
 colander/__init__.py            | 27 +++++++++++++++++++++++++++
 colander/tests/test_colander.py | 16 ++++++++++++++++
 docs/api.rst                    |  2 ++
 4 files changed, 47 insertions(+)

diff --git a/CHANGES.rst b/CHANGES.rst
index 62d2ee9..63a0a77 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -21,6 +21,8 @@ Features
 - Add a ``missing_msg`` argument to ``SchemaNode``, allowing customization
   of the error message used when the node is required and missing.
 
+- Add `NoneOf` validator wich succeeds if the value is none of the choices.
+
 Bug Fixes
 ---------
 
diff --git a/colander/__init__.py b/colander/__init__.py
index 20695ea..7e1e686 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -443,6 +443,33 @@ class OneOf(object):
                     mapping={'val':value, 'choices':choices})
             raise Invalid(node, err)
 
+
+class NoneOf(object):
+    """ Validator which succeeds if the value passed to it is none of a
+    fixed set of values.
+
+    ``msg_err`` is used to form the ``msg`` of the :exc:`colander.Invalid`
+    error when reporting a validation failure.  If ``msg_err`` is specified,
+    it must be a string.  The string may contain the replacement targets
+    ``${choices}`` and ``${val}``, representing the set of forbidden values
+    and the provided value respectively.
+    """
+    _MSG_ERR = _('"${val}" must not be one of ${choices}')
+
+    def __init__(self, choices, msg_err=_MSG_ERR):
+        self.forbidden = choices
+        self.msg_err = msg_err
+
+    def __call__(self, node, value):
+        if value not in self.forbidden:
+            return
+
+        choices = ', '.join(['%s' % x for x in self.forbidden])
+        err = _(self.msg_err, mapping={'val': value, 'choices': choices})
+
+        raise Invalid(node, err)
+
+
 class ContainsOnly(object):
     """ Validator which succeeds if the value passed to is a sequence and each
     element in the sequence is also in the sequence passed as ``choices``.
diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index 4fff3b3..aba64e9 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -490,6 +490,22 @@ class TestOneOf(unittest.TestCase):
         e = invalid_exc(validator, None, None)
         self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2')
 
+
+class TestNoneOf(unittest.TestCase):
+    def _makeOne(self, values):
+        from colander import NoneOf
+        return NoneOf(values)
+
+    def test_success(self):
+        validator = self._makeOne([1, 2])
+        self.assertEqual(validator(None, 3), None)
+
+    def test_failure(self):
+        validator = self._makeOne([1, 2])
+        e = invalid_exc(validator, None, 2)
+        self.assertEqual(e.msg.interpolate(), '"2" must not be one of 1, 2')
+
+
 class TestContainsOnly(unittest.TestCase):
     def _makeOne(self, values):
         from colander import ContainsOnly
diff --git a/docs/api.rst b/docs/api.rst
index e3584c8..3ecc9cc 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -64,6 +64,8 @@ Validators
 
   .. autoclass:: OneOf
 
+  .. autoclass:: NoneOf
+
   .. autoclass:: ContainsOnly
 
   .. autoclass:: Function

From 15bd7d30e095c94bcfdeeedd1d9fc85635be5efd Mon Sep 17 00:00:00 2001
From: Hong Minhee 
Date: Wed, 4 Nov 2015 13:54:22 +0900
Subject: [PATCH 84/99] Add normalize option to Decimal type

---
 CHANGES.rst                     |  3 +++
 colander/__init__.py            | 16 ++++++++++------
 colander/tests/test_colander.py | 21 +++++++++++++++++++--
 3 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 63a0a77..a67898a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,6 +23,9 @@ Features
 
 - Add `NoneOf` validator wich succeeds if the value is none of the choices.
 
+- Add ``normalize`` option to ``Decimal``, stripping the rightmost
+  trailing zeros.
+
 Bug Fixes
 ---------
 
diff --git a/colander/__init__.py b/colander/__init__.py
index 7e1e686..35f0715 100644
--- a/colander/__init__.py
+++ b/colander/__init__.py
@@ -1305,24 +1305,27 @@ class Decimal(Number):
     method of this class, the :attr:`colander.null` value will be
     returned.
 
-    The Decimal constructor takes two optional arguments, ``quant`` and
-    ``rounding``.  If supplied, ``quant`` should be a string,
+    The Decimal constructor takes three optional arguments, ``quant``,
+    ``rounding`` and ``normalize``.  If supplied, ``quant`` should be a string,
     (e.g. ``1.00``).  If supplied, ``rounding`` should be one of the Python
     ``decimal`` module rounding options (e.g. ``decimal.ROUND_UP``,
     ``decimal.ROUND_DOWN``, etc).  The serialized and deserialized result
     will be quantized and rounded via
     ``result.quantize(decimal.Decimal(quant), rounding)``.  ``rounding`` is
-    ignored if ``quant`` is not supplied.
+    ignored if ``quant`` is not supplied.  If ``normalize`` is ``True``,
+    the serialized and deserialized result will be normalized by stripping
+    the rightmost trailing zeros.
 
     The subnodes of the :class:`colander.SchemaNode` that wraps
     this type are ignored.
     """
-    def __init__(self, quant=None, rounding=None):
+    def __init__(self, quant=None, rounding=None, normalize=False):
         if quant is None:
             self.quant = None
         else:
             self.quant = decimal.Decimal(quant)
         self.rounding = rounding
+        self.normalize = normalize
 
     def num(self, val):
         result = decimal.Decimal(str(val))
@@ -1331,6 +1334,8 @@ class Decimal(Number):
                 result = result.quantize(self.quant)
             else:
                 result = result.quantize(self.quant, self.rounding)
+        if self.normalize:
+            result = result.normalize()
         return result
 
 class Money(Decimal):
@@ -1346,8 +1351,7 @@ class Money(Decimal):
     this type are ignored.
     """
     def __init__(self):
-        self.quant = decimal.Decimal('.01')
-        self.rounding = decimal.ROUND_UP
+        super(Money, self).__init__(decimal.Decimal('.01'), decimal.ROUND_UP)
 
 class Boolean(SchemaType):
     """ A type representing a boolean object.
diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py
index aba64e9..a35ecd0 100644
--- a/colander/tests/test_colander.py
+++ b/colander/tests/test_colander.py
@@ -1691,9 +1691,9 @@ class TestFloat(unittest.TestCase):
         self.assertEqual(result, '1.0')
 
 class TestDecimal(unittest.TestCase):
-    def _makeOne(self, quant=None, rounding=None):
+    def _makeOne(self, quant=None, rounding=None, normalize=False):
         from colander import Decimal
-        return Decimal(quant, rounding)
+        return Decimal(quant, rounding, normalize)
 
     def test_serialize_null(self):
         import colander
@@ -1725,6 +1725,14 @@ class TestDecimal(unittest.TestCase):
         result = typ.serialize(node, val)
         self.assertEqual(result, '0.01')
 
+    def test_serialize_normalize(self):
+        from decimal import Decimal
+        val = Decimal('1.00')
+        node = DummySchemaNode(None)
+        typ = self._makeOne(normalize=True)
+        result = typ.serialize(node, val)
+        self.assertEqual(result, '1')
+
     def test_deserialize_fails(self):
         val = 'P'
         node = DummySchemaNode(None)
@@ -1748,6 +1756,15 @@ class TestDecimal(unittest.TestCase):
         result = typ.deserialize(node, val)
         self.assertEqual(result, decimal.Decimal('1.01'))
 
+    def test_deserialize_with_normalize(self):
+        from decimal import Decimal
+        val = '1.00'
+        node = DummySchemaNode(None)
+        typ = self._makeOne(normalize=True)
+        result = typ.deserialize(node, val)
+        self.assertEqual(result, Decimal('1'))
+        self.assertEqual(str(result), '1')
+
     def test_serialize_fails(self):
         val = 'P'
         node = DummySchemaNode(None)

From 336e8b1ba969766729d822f2d3f43f501f3fbd58 Mon Sep 17 00:00:00 2001
From: Tinne Cahy 
Date: Mon, 21 Dec 2015 11:08:02 +0100
Subject: [PATCH 85/99] update translations nl

---
 colander/locale/nl/LC_MESSAGES/colander.mo | Bin 2840 -> 3091 bytes
 colander/locale/nl/LC_MESSAGES/colander.po | 107 ++++++++++++---------
 2 files changed, 59 insertions(+), 48 deletions(-)

diff --git a/colander/locale/nl/LC_MESSAGES/colander.mo b/colander/locale/nl/LC_MESSAGES/colander.mo
index caf830b0177d2fb412943d2225a450cf355cd870..640ee0b63484dd0a8ca066866ceae0b2347faa64 100644
GIT binary patch
delta 989
zcmYk)Pe@cj9KiAM->$BIN;CguHm!fq%y82z7YVUiE$mPzMIB_?on4>0Z^cz3MTHJo
zhaecDycwP9Vh|m=NggWbP~8hU6cj>)og$*|>~7MJH}CW2y?OI~^Lul@!c;ANEX%zs
zh&oy=ttd-~7W|DiR9>)mU
zg*?O-e1YA#iEVseH2H)m$04*YI#`A`(5`p|cj8*+`8t-;|A4*t9hpnC7pC72BBd1<
z(Eg%%v=vxE4zbQDgl}+(1*ayXs@q|cU{PXM=1w)*1pWnu5qoI%qZBLkdqA)?QY*FB
zHf-`Gv>moXkY}n=Hi=3aTcJLcA-^KEY3#QH@COy(OsNC3$
zis_&<&pk(ShKEAtho|1$@fo
zoUl$Vv`8l@-T#M~#caR%>&br{>3cUtmfY{#|mDbOXz8-wJV3u56U@b%oQ8A
zHXgY$5tVVRr{t`1WkkzyJu|11GU8}Cre$2I@oAkJE?tKSNM}~5smK*h=A`d>&7|YT
Sg3_NZiC;vO+OE%A-@sqF9-XHE

delta 663
zcmYMxKTH#07{~F~Ug6rJ4XLFPBIN7|QA$Vg$EPclsGS@tgPi0qV|G$n7+YOsT7=_uoJp7g7H;FHzsXHs){#Rs8IO`_AIS
zxK(!USx)WKd*6~F>GoIQ)Mzq2Zly0~(xZvgu)B1TsB13_s8F3X0|BmKbz!>Qep
aN5M6ThFX$oyCSKM$Dxe%?^N=&\n"
+"Last-Translator: Tinne Cahy \n"
 "Language-Team: nl \n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
 "Generated-By: Babel 1.3\n"
 
-#: colander/__init__.py:233
+#: colander/__init__.py:240
 msgid "Invalid value"
 msgstr "Ongeldige waarde"
 
-#: colander/__init__.py:270
+#: colander/__init__.py:283
 msgid "String does not match expected pattern"
 msgstr "Tekst is niet in het juiste formaat"
 
-#: colander/__init__.py:287
+#: colander/__init__.py:302
 msgid "Invalid email address"
 msgstr "Ongeldig e-mail adres"
 
-#: colander/__init__.py:315
+#: colander/__init__.py:330
 msgid "${val} is less than minimum value ${min}"
 msgstr "${val} is minder dan de minimum waarde ${min}"
 
-#: colander/__init__.py:316
+#: colander/__init__.py:331
 msgid "${val} is greater than maximum value ${max}"
-msgstr "${val} is groter dan de maximale toegestaane waarde ${max}"
+msgstr "${val} is groter dan de maximale toegestane waarde ${max}"
 
-#: colander/__init__.py:348
+#: colander/__init__.py:363
 msgid "Shorter than minimum length ${min}"
 msgstr "De minimale lengte is ${min}"
 
-#: colander/__init__.py:354
+#: colander/__init__.py:369
 msgid "Longer than maximum length ${max}"
 msgstr "De maximale lengte is ${max}"
 
-#: colander/__init__.py:367
+#: colander/__init__.py:382
 msgid "\"${val}\" is not one of ${choices}"
-msgstr "\"${val}\" is geen toegestaande waarde. Kies één van ${choices}."
+msgstr "\"${val}\" is geen toegestane waarde. Kies één van ${choices}."
 
-#: colander/__init__.py:377
+#: colander/__init__.py:392
 msgid "One or more of the choices you made was not acceptable"
-msgstr ""
+msgstr "Één of meer van de keuzes die je maakte zijn niet aanvaardbaar"
 
-#: colander/__init__.py:423
+#: colander/__init__.py:414 colander/__init__.py:419
+#, fuzzy
+msgid "\"${val}\" is not a valid credit card number"
+msgstr "\"${val}\" is geen geldige kredietkaarnummer"
+
+#: colander/__init__.py:440
 msgid "Must be a URL"
-msgstr ""
+msgstr "Moet een URL zijn"
 
-#: colander/__init__.py:519
+#: colander/__init__.py:536
 msgid "\"${val}\" is not a mapping type: ${err}"
-msgstr "\"${val}\" is geen map type: ${err}"
+msgstr "\"${val}\" is geen mapping type: ${err}"
 
-#: colander/__init__.py:560
+#: colander/__init__.py:578
 msgid "Unrecognized keys in mapping: \"${val}\""
-msgstr "Onbekende waardes in map: \"${val}\""
+msgstr "Onbekende waardes in mapping: \"${val}\""
 
-#: colander/__init__.py:656 colander/__init__.py:856
+#: colander/__init__.py:674 colander/__init__.py:905
 msgid "\"${val}\" is not iterable"
 msgstr "\"${val}\" is niet itereerbaar"
 
-#: colander/__init__.py:664
+#: colander/__init__.py:682
 msgid ""
-"\"${val}\" has an incorrect number of elements (expected ${exp}, was "
-"${was})"
+"\"${val}\" has an incorrect number of elements (expected ${exp}, was ${was})"
 msgstr ""
-"\"${val}\" bevat niet het juiste aantal element (${was} in plaats van "
-"${exp})"
+"\"${val}\" bevat niet het juiste aantal elementen (${was} in plaats van ${exp})"
 
-#: colander/__init__.py:803
+#: colander/__init__.py:821 colander/__init__.py:852
 msgid "${cstruct} is not iterable"
 msgstr "${cstruct} is niet itereerbaar"
 
-#: colander/__init__.py:1075
+#: colander/__init__.py:1124
 msgid "${val} cannot be serialized: ${err}"
 msgstr "${val} kan niet worden opgeslagen: ${err}"
 
-#: colander/__init__.py:1093
+#: colander/__init__.py:1142
 msgid "${val} is not a string: ${err}"
 msgstr "${val} is geen tekst: ${err}"
 
-#: colander/__init__.py:1113 colander/__init__.py:1124
+#: colander/__init__.py:1162 colander/__init__.py:1173
 msgid "\"${val}\" is not a number"
 msgstr "\"${val}\" is geen getal"
 
-#: colander/__init__.py:1268
+#: colander/__init__.py:1317
 msgid "${val} is not a string"
 msgstr "${val} is geen tekst"
 
-#: colander/__init__.py:1279
+#: colander/__init__.py:1328
 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})"
-msgstr ""
+msgstr "\"${val}\" is noch aanwezig in (${false_choices}) is noch aanwezig in (${true_choices})"
 
-#: colander/__init__.py:1339 colander/__init__.py:1356
-#: colander/__init__.py:1366
+#: colander/__init__.py:1388 colander/__init__.py:1405
+#: colander/__init__.py:1415
 msgid "relative name \"${val}\" irresolveable without package"
 msgstr "relatieve aanduiding \"${val}\" kan niet worden opgezocht zonder package"
 
-#: colander/__init__.py:1396
+#: colander/__init__.py:1445
 msgid "\"${val}\" has no __name__"
 msgstr "\"${val}\" heeft geen __name__"
 
-#: colander/__init__.py:1405
+#: colander/__init__.py:1454
 msgid "\"${val}\" is not a string"
 msgstr "\"${val}\" is geen tekst"
 
-#: colander/__init__.py:1414
+#: colander/__init__.py:1463
 msgid "The dotted name \"${name}\" cannot be imported"
 msgstr "Kan \"${name}\" niet importeren"
 
-#: colander/__init__.py:1462 colander/__init__.py:1538
+#: colander/__init__.py:1511 colander/__init__.py:1587
 msgid "Invalid date"
 msgstr "Geen geldige datum"
 
-#: colander/__init__.py:1478
+#: colander/__init__.py:1527
 msgid "\"${val}\" is not a datetime object"
 msgstr "\"${val}\" is geen datetime object"
 
-#: colander/__init__.py:1549
+#: colander/__init__.py:1598
 msgid "\"${val}\" is not a date object"
 msgstr "\"${val}\" is geen date object"
 
-#: colander/__init__.py:1610
+#: colander/__init__.py:1659
 #, fuzzy
-#| msgid "Invalid date"
 msgid "Invalid time"
 msgstr "Geen geldige datum"
 
-#: colander/__init__.py:1621
+#: colander/__init__.py:1670
 #, fuzzy
-#| msgid "\"${val}\" is not a datetime object"
 msgid "\"${val}\" is not a time object"
 msgstr "\"${val}\" is geen datetime object"
 
-#: colander/__init__.py:1878 colander/__init__.py:1880
+#: colander/__init__.py:1795
 msgid "Required"
 msgstr "Verplicht"
+
+#: colander/tests/test_colander.py:295 colander/tests/test_colander.py:302
+msgid "fail ${val}"
+msgstr "faal ${val}"
+
+#: colander/tests/test_colander.py:469
+#, fuzzy
+msgid "${val}: ${choices}"
+msgstr "\"${val}\" is geen toegestane waarde. Kies één van ${choices}."

From 6a22575270d6a3fe8a4eae89f2123905a729ab80 Mon Sep 17 00:00:00 2001
From: Tinne Cahy 
Date: Mon, 21 Dec 2015 14:27:11 +0100
Subject: [PATCH 86/99] update translation nl ("\"${val}\" is niet aanwezig in
 (${false_choices}) noch aanwezig in (${true_choices})")

---
 colander/locale/nl/LC_MESSAGES/colander.mo | Bin 3091 -> 3088 bytes
 colander/locale/nl/LC_MESSAGES/colander.po |   2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/colander/locale/nl/LC_MESSAGES/colander.mo b/colander/locale/nl/LC_MESSAGES/colander.mo
index 640ee0b63484dd0a8ca066866ceae0b2347faa64..7d906057910d55683065cf77a37b774a21f0699b 100644
GIT binary patch
delta 240
zcmXZUF$)247{~GFy3*fS5Z4Xp>QILNEQ<`XpJbJRG8q_bk~bh%H+umFx%o1i?B0Pg
zSZ%)7si)`j{OWn0z&SXVy0IOK$V7=$@QN-zuz?@6(axr7O!oTN#VOYCge|;b2Vbbs
zvqVbhV{$jgBJQz_p(SI1RUt^Uq
O7qfM+RBm)sJM#zn+#F*7

delta 243
zcmXZUuL{Bd5XbSG1DUg^s7z29r~`wrszH|Y1^g2Y8eCdfGzbQx2usU8fWoj3v&rro
z2nLhQ@0f7h=YDW^MeuJNA79ftq_

diff --git a/colander/locale/nl/LC_MESSAGES/colander.po b/colander/locale/nl/LC_MESSAGES/colander.po
index 9396f12..fca82ff 100644
--- a/colander/locale/nl/LC_MESSAGES/colander.po
+++ b/colander/locale/nl/LC_MESSAGES/colander.po
@@ -103,7 +103,7 @@ msgstr "${val} is geen tekst"
 
 #: colander/__init__.py:1328
 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})"
-msgstr "\"${val}\" is noch aanwezig in (${false_choices}) is noch aanwezig in (${true_choices})"
+msgstr "\"${val}\" is niet aanwezig in (${false_choices}) noch aanwezig in (${true_choices})"
 
 #: colander/__init__.py:1388 colander/__init__.py:1405
 #: colander/__init__.py:1415

From 361fb01e5a1b226bda667007ead21e92077f47da Mon Sep 17 00:00:00 2001
From: Tinne Cahy 
Date: Tue, 22 Dec 2015 09:24:16 +0100
Subject: [PATCH 87/99] fix typo Dutch translations

---
 colander/locale/nl/LC_MESSAGES/colander.po | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/colander/locale/nl/LC_MESSAGES/colander.po b/colander/locale/nl/LC_MESSAGES/colander.po
index fca82ff..0152f5c 100644
--- a/colander/locale/nl/LC_MESSAGES/colander.po
+++ b/colander/locale/nl/LC_MESSAGES/colander.po
@@ -57,7 +57,7 @@ msgstr "Één of meer van de keuzes die je maakte zijn niet aanvaardbaar"
 #: colander/__init__.py:414 colander/__init__.py:419
 #, fuzzy
 msgid "\"${val}\" is not a valid credit card number"
-msgstr "\"${val}\" is geen geldige kredietkaarnummer"
+msgstr "\"${val}\" is geen geldige kredietkaartnummer"
 
 #: colander/__init__.py:440
 msgid "Must be a URL"

From 1f01eb607aec8412060e63769a382da721deb0e7 Mon Sep 17 00:00:00 2001
From: Tinne Cahy 
Date: Tue, 22 Dec 2015 09:27:28 +0100
Subject: [PATCH 88/99] sign contributors.txt

---
 CONTRIBUTORS.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 81031f0..23a287e 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -125,3 +125,4 @@ Contributors
 - Nando Florestan, 2014/11/27
 - Amos Latteier, 2014/11/30
 - Jimmy Thrasibule, 2014/12/11
+- Tinne Cahy, 2015/12/22

From 05f6d07868708acad06eaa7f04a97b245f4fce16 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 19:57:29 -0600
Subject: [PATCH 89/99] reformat changelog

---
 CHANGES.rst | 66 ++++++++++++++++++++++++++---------------------------
 1 file changed, 33 insertions(+), 33 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index a67898a..777b961 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,5 +1,5 @@
-Unreleased
-----------
+unreleased
+===========
 
 Platform
 --------
@@ -44,7 +44,7 @@ Bug Fixes
 
 
 1.0 (2014-11-26)
-----------------
+================
 
 Bug Fixes
 ~~~~~~~~~
@@ -85,7 +85,7 @@ Backwards Incompatibilities
 
 
 1.0b1 (2013-09-01)
-------------------
+==================
 
 Bug Fixes
 ~~~~~~~~~
@@ -142,14 +142,14 @@ Features
 - Allow interpolation of `missing_msg` with properties `title` and `name`
 
 1.0a5 (2013-05-31)
-------------------
+==================
 
 - Fix bug introduced by supporting spec-mandated truncations of ISO-8601
   timezones.  A TypeError would be raised instead of Invalid.  See
   https://github.com/Pylons/colander/issues/111.
 
 1.0a4 (2013-05-21)
-------------------
+==================
 
 - Loosen Email validator regex (permit apostrophes, bang, etc in localpart).
 
@@ -158,7 +158,7 @@ Features
   https://github.com/Pylons/colander/pull/108.
 
 1.0a3 (2013-05-16)
-------------------
+==================
 
 Features
 ~~~~~~~~
@@ -181,7 +181,7 @@ Bug Fixes
 
 
 1.0a2 (2013-01-30)
-------------------
+==================
 
 Features
 ~~~~~~~~
@@ -192,7 +192,7 @@ Features
   mappings and sequences more succinctly.
 
 1.0a1 (2013-01-10)
-------------------
+==================
 
 Bug Fixes
 ~~~~~~~~~
@@ -542,7 +542,7 @@ Backwards Incompatibilities
   name`` when constructing a SchemaNode.
 
 0.9.9 (2012-09-24)
-------------------
+==================
 
 Features
 ~~~~~~~~
@@ -599,7 +599,7 @@ Documentation
   documentation.
 
 0.9.8 (2012-04-27)
-------------------
+==================
 
 - False evaluating values are now serialized to colander.null for
   String, Date, and Time.  This resolves the issue where a None value
@@ -619,7 +619,7 @@ Documentation
 - Add ``dev`` and ``docs`` setup.py aliases (e.g. ``python setup.py dev``).
 
 0.9.7 (2012-03-20)
-------------------
+==================
 
 - Using ``schema.flatten(...)`` against a mapping schema node without a name
   produced incorrectly dot-prefixed keys.  See
@@ -646,7 +646,7 @@ Documentation
   error message.  See https://github.com/Pylons/colander/pull/41
 
 0.9.6 (2012-02-14)
-------------------
+==================
 
 - No longer runs on Python 2.4 or 2.5.  Python 2.6+ is now required.
 
@@ -659,7 +659,7 @@ Documentation
   LICENSE.txt.
 
 0.9.5 (2012-01-13)
-------------------
+==================
 
 - Added Czech translation.
 
@@ -670,7 +670,7 @@ Documentation
 - Documentation added about flatten and unflatten.
 
 0.9.4 (2011-10-14)
-------------------
+==================
 
 - ``flatten`` now only includes leaf nodes in the flattened dict.
 
@@ -688,7 +688,7 @@ Documentation
 - Add Swedish, French, Chinese translations.
 
 0.9.3 (2011-06-23)
-------------------
+==================
 
 - Add ``Time`` type.
 
@@ -709,7 +709,7 @@ Documentation
   documentation.
 
 0.9.2 (2011-03-28)
-------------------
+==================
 
 - Added Polish translation, thanks to Jedrzej Nowak.
 
@@ -733,7 +733,7 @@ Documentation
   cstruct during ``deserialize``.
 
 0.9.1 (2010-12-02)
-------------------
+==================
 
 - When ``colander.null`` was unpickled, the reference created during
   unpickling was *not* a reference to the singleton but rather a new instance
@@ -743,7 +743,7 @@ Documentation
   pickled is unpickled as the singleton itself.
 
 0.9  (2010-11-28)
------------------
+=================
 
 - SchemaNode constructor now accepts arbitrary keyword arguments.  It
   sets any unknown values within the ``**kw`` sequence as attributes
@@ -767,7 +767,7 @@ Documentation
   ``colander.SchemaType`` to get a default implementation.
 
 0.8  (2010/09/08)
------------------
+=================
 
 - Docstring fixes to ``colander.SchemaNode`` (``missing`` is not the
   ``null`` value when required, it's a special marker value).
@@ -783,7 +783,7 @@ Documentation
   been properly documented.
 
 0.7.3 (2010/09/02)
-------------------
+==================
 
 - The title of a schema node now defaults to a titleization of the
   ``name``.  Underscores in the ``name`` are replaced with empty
@@ -798,7 +798,7 @@ Documentation
   single-element list containing the ``msg`` value is returned.
 
 0.7.2 (2010/08/30)
-------------------
+==================
 
 - Add an ``colander.SchemaNode.__iter__`` method, which iterates over
   the children nodes of a schema node.
@@ -808,7 +808,7 @@ Documentation
   internally).
 
 0.7.1 (2010/06/12)
-------------------
+==================
 
 - Make it possible to use ``colander.null`` as a ``missing`` argument
   to ``colander.SchemaNode`` for roundtripping purposes.
@@ -904,7 +904,7 @@ Backwards Incompatiblities / New Features
   no longer a keyword argument that has a default.
 
 0.6.2 (2010-05-08)
-------------------
+==================
 
 - The default ``encoding`` parameter value to the ``colander.String``
   type is still ``None``, however its meaning has changed.  An
@@ -923,13 +923,13 @@ Backwards Incompatiblities / New Features
   a bool value when a default value was found for the schema node.
 
 0.6.1 (2010-05-04)
-------------------
+==================
 
 - Add a Decimal type (number type which uses ``decimal.Decimal`` as a
   deserialization target).
 
 0.6.0 (2010-05-02)
-------------------
+==================
 
 - (Hopefully) fix intermittent datetime-granularity-related test
   failures.
@@ -952,7 +952,7 @@ Backwards Incompatiblities / New Features
   to its interface within the API documentation.
 
 0.5.2 (2010-04-09)
-------------------
+==================
 
 - Add Email and Regex validators (courtesy Steve Howe).
 
@@ -970,7 +970,7 @@ Backwards Incompatiblities / New Features
   add something like it back later if we need it.
 
 0.5.1 (2010-04-02)
-------------------
+==================
 
 - The constructor arguments to a the ``colander.Schema`` class are now
   sent to the constructed SchemaNode rather than to the type it represents.
@@ -994,20 +994,20 @@ Backwards Incompatiblities / New Features
   the type).
 
 0.5 (2010-03-31)
-----------------
+================
 
 - 0.4 was mispackaged (CHANGES.txt missing); no code changes from 0.4
   however.
 
 0.4 (2010-03-30)
-----------------
+================
 
 - Add ``colander.DateTime`` and ``colander.Date`` data types.
 
 - Depend on the ``iso8601`` package for date support.
 
 0.3 (2010-03-29)
-----------------
+================
 
 - Subnodes of a schema node are now kept in the ``children`` attribute
   rather than the ``nodes`` attribute.
@@ -1024,7 +1024,7 @@ Backwards Incompatiblities / New Features
 - Add ``colander.Length`` validator class.
 
 0.2 (2010-03-23)
-----------------
+================
 
 - Make nodetype overrideable.
 
@@ -1054,6 +1054,6 @@ Backwards Incompatiblities / New Features
 
 
 0.1 (2010-03-14)
-----------------
+================
 
 - Initial release.

From ca5de5cd72707e1819950078adf73c9f214eba84 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:08:14 -0600
Subject: [PATCH 90/99] test on py35

---
 CHANGES.rst | 4 ++--
 tox.ini     | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 777b961..6d7e8d9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,10 +1,10 @@
 unreleased
-===========
+==========
 
 Platform
 --------
 
-- Add explicit support for Python 3.4 and PyPy3.
+- Add explicit support for Python 3.4, Python 3.5 and PyPy3.
 
 Features
 ~~~~~~~~
diff --git a/tox.ini b/tox.ini
index f5a94e4..69fd2cb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py26,py27,py32,py33,py34,pypy,pypy3,cover,docs
+    py26,py27,py32,py33,py34,py35,pypy,pypy3,cover,docs
 
 [testenv]
 commands =

From da7c26e651dd96b767c90aafa33da19bc8295d2f Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:13:23 -0600
Subject: [PATCH 91/99] reformat sublevels in changes

---
 CHANGES.rst | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 6d7e8d9..d23a2c8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,7 +7,7 @@ Platform
 - Add explicit support for Python 3.4, Python 3.5 and PyPy3.
 
 Features
-~~~~~~~~
+--------
 
 - Add ``min_err`` and ``max_err`` arguments to ``Length``, allowing
   customization of its error messages.
@@ -47,7 +47,7 @@ Bug Fixes
 ================
 
 Bug Fixes
-~~~~~~~~~
+---------
 
 - Removed forked ``iso8601`` and change to dependency on PyPI ``iso8601``
   (due to float rounding bug on microsecond portion when parsing
@@ -76,7 +76,7 @@ Bug Fixes
 
 
 Backwards Incompatibilities
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------
 
 - ``SchemaNode.deserialize`` will now raise an
   ``UnboundDeferredError`` if the node has an unbound deferred
@@ -88,7 +88,7 @@ Backwards Incompatibilities
 ==================
 
 Bug Fixes
-~~~~~~~~~
+---------
 
 - In 1.0a1, there was a change merged from
   https://github.com/Pylons/colander/pull/73 which made it possible to supply
@@ -118,7 +118,7 @@ Bug Fixes
   See https://github.com/Pylons/colander/issues/100
 
 Features
-~~~~~~~~
+--------
 
 - Add ``colander.List`` type, modeled on ``deform.List``:  this type
   preserves ordering, and allows duplicates.
@@ -161,7 +161,7 @@ Features
 ==================
 
 Features
-~~~~~~~~
+--------
 
 - Support spec-mandated truncations of ISO-8601 timezones.
 
@@ -170,7 +170,7 @@ Features
 - Allow specifying custom representations of values for boolean fields.
 
 Bug Fixes
-~~~~~~~~~
+---------
 
 - Ensure that ``colander.iso8601.FixedOffset`` instances can be unpickled.
 
@@ -184,7 +184,7 @@ Bug Fixes
 ==================
 
 Features
-~~~~~~~~
+--------
 
 - Add ``colander.ContainsOnly`` and ``colander.url`` validators.
 
@@ -195,7 +195,7 @@ Features
 ==================
 
 Bug Fixes
-~~~~~~~~~
+---------
 
 - Work around a regression in Python 3.3 for ``colander.Decimal`` when it's
   used with a ``quant`` argument but without a ``rounding`` argument.
@@ -211,7 +211,7 @@ Bug Fixes
   https://github.com/Pylons/colander/pull/96).
 
 Features
-~~~~~~~~
+--------
 
 - Add ``colander.Set`` type, ported from ``deform.Set``
 
@@ -535,7 +535,7 @@ Features
   MRO deepest-first ordering (``One``, then ``Two``, then ``Three``).
 
 Backwards Incompatibilities
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------
 
 - Passing non-SchemaNode derivative instances as ``*children`` into a
   SchemaNode constructor is no longer supported.  Symptom: ``AttributeError:
@@ -545,7 +545,7 @@ Backwards Incompatibilities
 ==================
 
 Features
-~~~~~~~~
+--------
 
 - Allow the use of ``missing=None`` for Number.  See
   https://github.com/Pylons/colander/pull/59 .
@@ -584,7 +584,7 @@ Features
   custom type).
 
 Backwards Incompatibilities
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------
 
 - The inheritance changes required a minor backwards incompatibility: calling
   ``__setitem__`` on a SchemaNode will no longer raise ``KeyError`` when
@@ -593,7 +593,7 @@ Backwards Incompatibilities
   the child list.
 
 Documentation
-~~~~~~~~~~~~~
+-------------
 
 - A "Schema Inheritance" section was added to the Basics chapter
   documentation.
@@ -816,13 +816,13 @@ Documentation
 - Make it possible to pickle ``colander.null``.
 
 0.7.0
------
+=====
 
 A release centered around normalizing the treatment of default and
 missing values.
 
 Bug Fixes
-~~~~~~~~~
+---------
 
 - Allow ``colander.Regex`` validator to accept a pattern object
   instead of just a string.
@@ -838,7 +838,7 @@ Bug Fixes
   ``colander.SchemaNode``.
 
 Backwards Incompatiblities / New Features
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------------------
 
 - ``missing`` constructor arg to SchemaNode: signifies
   *deserialization* default, disambiguated from ``default`` which acted

From b1b6509cb6c0db34b7d08c7ee70575b889861aa1 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:13:43 -0600
Subject: [PATCH 92/99] add py35 to travis

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index cb98fdd..2b458ff 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,7 @@ env:
   - TOXENV=py32
   - TOXENV=py33
   - TOXENV=py34
+  - TOXENV=py35
   - TOXENV=pypy
   - TOXENV=pypy3
   - TOXENV=cover

From d59c27652c8bf43b02dd6cda34b965cff0efc076 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:16:18 -0600
Subject: [PATCH 93/99] update travis/tox to support py35 and coverage on
 py2+py3

---
 .travis.yml | 35 ++++++++++++++++++++--------
 tox.ini     | 67 +++++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 77 insertions(+), 25 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 2b458ff..5171704 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,16 +2,28 @@
 language: python
 sudo: false
 
-env:
-  - TOXENV=py26
-  - TOXENV=py27
-  - TOXENV=py32
-  - TOXENV=py33
-  - TOXENV=py34
-  - TOXENV=py35
-  - TOXENV=pypy
-  - TOXENV=pypy3
-  - TOXENV=cover
+matrix:
+    include:
+        - python: 2.6
+          env: TOXENV=py26
+        - python: 2.7
+          env: TOXENV=py27
+        - python: 3.2
+          env: TOXENV=py32
+        - python: 3.3
+          env: TOXENV=py33
+        - python: 3.4
+          env: TOXENV=py34
+        - python: 3.5
+          env: TOXENV=py35
+        - python: pypy
+          env: TOXENV=pypy
+        - python: pypy3
+          env: TOXENV=pypy3
+        - python: 3.5
+          env: TOXENV=py2-cover,py3-cover,coverage
+        - python: 3.5
+          env: TOXENV=docs
 
 install:
   - travis_retry pip install tox
@@ -22,3 +34,6 @@ script:
 notifications:
   email:
     - pyramid-checkins@lists.repoze.org
+  irc:
+    channels:
+      - "chat.freenode.net#pyramid"
diff --git a/tox.ini b/tox.ini
index 69fd2cb..8508bb7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,25 +1,62 @@
 [tox]
 envlist =
-    py26,py27,py32,py33,py34,py35,pypy,pypy3,cover,docs
+    py26,py27,py32,py33,py34,py35,pypy,pypy3,
+    docs,
+    {py2,py3}-cover,coverage
 
 [testenv]
-commands =
-    python setup.py -q dev
-    python -Wd setup.py -q test -q
-
-[testenv:cover]
+# Most of these are defaults but if you specify any you can't fall back
+# to defaults for others.
 basepython =
-    python2.7
+    py26: python2.6
+    py27: python2.7
+    py32: python3.2
+    py33: python3.3
+    py34: python3.4
+    py35: python3.5
+    pypy: pypy
+    pypy3: pypy3
+    py2: python2.7
+    py3: python3.5
+
 commands =
-    nosetests --with-xunit --with-xcoverage --cover-min-percentage=100
-deps =
-    nose
-    coverage
-    nosexcover
+    pip install colander[testing]
+    nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
 
 [testenv:docs]
-basepython =
-    python2.7
+basepython = python3.5
+whitelist_externals = make
 commands =
     pip install colander[docs]
-    sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
+    make -C docs html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E"
+
+# we separate coverage into its own testenv because a) "last run wins" wrt
+# cobertura jenkins reporting and b) pypy and jython can't handle any
+# combination of versions of coverage and nosexcover that i can find.
+[testenv:py2-cover]
+commands =
+    pip install colander[testing]
+    coverage run --source=colander {envbindir}/nosetests
+    coverage xml -o coverage-py2.xml
+setenv =
+    COVERAGE_FILE=.coverage.py2
+
+[testenv:py3-cover]
+commands =
+    pip install colander[testing]
+    coverage run --source=colander {envbindir}/nosetests
+    coverage xml -o coverage-py3.xml
+setenv =
+    COVERAGE_FILE=.coverage.py3
+
+[testenv:coverage]
+basepython = python3.5
+commands =
+    coverage erase
+    coverage combine
+    coverage xml
+    coverage report --show-missing --fail-under=100
+deps =
+    coverage
+setenv =
+    COVERAGE_FILE=.coverage

From 57c377a0971b37626c02569432cb122df148e2d6 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:17:56 -0600
Subject: [PATCH 94/99] switch compat to special-case py2

---
 colander/compat.py | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/colander/compat.py b/colander/compat.py
index 5e6014b..4e7e598 100644
--- a/colander/compat.py
+++ b/colander/compat.py
@@ -1,13 +1,14 @@
 import sys
 
+PY2 = sys.version_info[0] == 2
 PY3 = sys.version_info[0] == 3
 
-if PY3: # pragma: no cover
-    string_types = str,
-    text_type = str
-else: # pragma: no cover
+if PY2:
     string_types = basestring,
     text_type = unicode
+else:
+    string_types = str,
+    text_type = str
 
 def text_(s, encoding='latin-1', errors='strict'):
     """ If ``s`` is an instance of ``bytes``, return ``s.decode(encoding,
@@ -16,14 +17,14 @@ def text_(s, encoding='latin-1', errors='strict'):
         return s.decode(encoding, errors)
     return s # pragma: no cover
 
-if PY3: # pragma: no cover
+if PY2:
+    def is_nonstr_iter(v):
+        return hasattr(v, '__iter__')
+else:
     def is_nonstr_iter(v):
         if isinstance(v, str):
             return False
         return hasattr(v, '__iter__')
-else: # pragma: no cover
-    def is_nonstr_iter(v):
-        return hasattr(v, '__iter__')
 
 try:
     xrange = xrange

From 1e5e8fe7ea08fffdb93494a2127f3d5b92de295d Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:20:17 -0600
Subject: [PATCH 95/99] add minor coverage checks for interfaces.py

---
 colander/tests/test_interfaces.py | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 colander/tests/test_interfaces.py

diff --git a/colander/tests/test_interfaces.py b/colander/tests/test_interfaces.py
new file mode 100644
index 0000000..2cca492
--- /dev/null
+++ b/colander/tests/test_interfaces.py
@@ -0,0 +1,2 @@
+def test_interfaces():
+    from colander import interfaces

From 7b1c6f633b07d0f9667a902c40f07efdad528007 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:22:53 -0600
Subject: [PATCH 96/99] prep 1.1

---
 CHANGES.rst | 4 ++--
 setup.py    | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index d23a2c8..5962a6f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,5 +1,5 @@
-unreleased
-==========
+1.1 (2016-01-15)
+================
 
 Platform
 --------
diff --git a/setup.py b/setup.py
index bab2fb4..0e19fe4 100644
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ docs_extras = [
 ]
 
 setup(name='colander',
-      version='1.1.dev0',
+      version='1.1',
       description=('A simple schema-based serialization and deserialization '
                    'library'),
       long_description=README + '\n\n' +  CHANGES,
@@ -54,6 +54,7 @@ setup(name='colander',
         "Programming Language :: Python :: 3.2",
         "Programming Language :: Python :: 3.3",
         "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
         ],

From 5aec411854585f40f99b32f87cb5eac6c0a0b874 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:25:39 -0600
Subject: [PATCH 97/99] prepare universal wheel

---
 setup.cfg | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/setup.cfg b/setup.cfg
index 50ce659..1a97358 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,6 @@
+[wheel]
+universal = 1
+
 [easy_install]
 zip_ok = false
 

From 4e40a8236b4fe7bdd829f55ceccfde84674b919c Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:28:34 -0600
Subject: [PATCH 98/99] fix readme

---
 README.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.rst b/README.rst
index dba9a81..82e26d6 100644
--- a/README.rst
+++ b/README.rst
@@ -18,7 +18,7 @@ An extensible package which can be used to:
 - serialize an arbitrary data structure to a data structure composed
   of strings, mappings, and lists.
 
-It runs on Python 2.6, 2.7, 3.2, 3.3, and 3.4, and on current PyPy
+It runs on Python 2.6, 2.7, 3.2, 3.3, 3.4, and 3.5, and on current PyPy
 and PyPy3 versions.
 
 Please see http://docs.pylonsproject.org/projects/colander/en/latest/

From d9a4d4a13c137add38c61bdbb102d00149585f31 Mon Sep 17 00:00:00 2001
From: Michael Merickel 
Date: Fri, 15 Jan 2016 20:28:57 -0600
Subject: [PATCH 99/99] update gitignore

---
 .gitignore | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index e8e56fc..54a4a8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,8 @@
 *.swp
 .coverage
 .tox/
-nosetests.xml
+coverage-*.xml
+nosetests*.xml
 env26/
 env25/
 env24/