Merge branch 'master' into pr/168
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,7 +8,8 @@
|
||||
*.swp
|
||||
.coverage
|
||||
.tox/
|
||||
nosetests.xml
|
||||
coverage-*.xml
|
||||
nosetests*.xml
|
||||
env26/
|
||||
env25/
|
||||
env24/
|
||||
|
||||
39
.travis.yml
Normal file
39
.travis.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
# Wire up travis
|
||||
language: python
|
||||
sudo: false
|
||||
|
||||
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
|
||||
|
||||
script:
|
||||
- travis_retry tox
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- pyramid-checkins@lists.repoze.org
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#pyramid"
|
||||
@@ -1,34 +1,103 @@
|
||||
Unreleased
|
||||
----------
|
||||
unreleased
|
||||
==========
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
- Un-break wrapping of callable instances as ``colander.deferred``.
|
||||
See https://github.com/Pylons/colander/issues/141.
|
||||
- Set the max length TLD to 22 in ``Email`` validator based on the
|
||||
current list of valid TLDs.
|
||||
See https://github.com/Pylons/colander/issues/159
|
||||
- Fix an issue where ``drop`` was not recognized as a default and was
|
||||
returning the ``drop`` instance instead of omitting the value.
|
||||
https://github.com/Pylons/colander/issues/139
|
||||
- We implicitly assign the schema type to MappingSchema, SequenceSchema, and
|
||||
TupleSchema with types of Mapping, Sequence, and Tuple explicitly. This is
|
||||
a minor backwards incompatibility, because we no longer allow any of those
|
||||
schema variants to accept the implied type as a first argument.
|
||||
schema variants to accept the implied type as a first argument.
|
||||
i.e. MappingSchema(Mapping()) will no longer work.
|
||||
|
||||
1.1 (2016-01-15)
|
||||
================
|
||||
|
||||
Platform
|
||||
--------
|
||||
|
||||
- Add explicit support for Python 3.4, Python 3.5 and PyPy3.
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
- Add `Any` validator which succeeds if at least one of its subvalidators
|
||||
succeeded.
|
||||
- Add ``min_err`` and ``max_err`` arguments to ``Length``, allowing
|
||||
customization of its error messages.
|
||||
|
||||
1.0b1 (2013-09-01)
|
||||
------------------
|
||||
- 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``, 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.
|
||||
|
||||
- Add ``normalize`` option to ``Decimal``, stripping the rightmost
|
||||
trailing zeros.
|
||||
|
||||
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
|
||||
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 <https://github.com/Pylons/colander/pull/235>`_
|
||||
|
||||
- ``title`` was being overwritten when made a child through defining a schema
|
||||
as a class. See `#239 <https://github.com/Pylons/colander/pull/239>`_
|
||||
|
||||
|
||||
1.0 (2014-11-26)
|
||||
================
|
||||
|
||||
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``.
|
||||
See https://github.com/Pylons/colander/issues/141.
|
||||
|
||||
- Set the max length TLD to 22 in ``Email`` validator based on the
|
||||
current list of valid TLDs.
|
||||
See https://github.com/Pylons/colander/issues/159
|
||||
|
||||
- Fix an issue where ``drop`` was not recognized as a default and was
|
||||
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
|
||||
|
||||
- Updated translations: ``fr``, ``de``, ``ja``
|
||||
|
||||
|
||||
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)
|
||||
==================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- In 1.0a1, there was a change merged from
|
||||
https://github.com/Pylons/colander/pull/73 which made it possible to supply
|
||||
@@ -58,7 +127,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.
|
||||
@@ -79,18 +148,17 @@ 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
|
||||
- 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).
|
||||
|
||||
@@ -99,10 +167,10 @@ Features
|
||||
https://github.com/Pylons/colander/pull/108.
|
||||
|
||||
1.0a3 (2013-05-16)
|
||||
------------------
|
||||
==================
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
- Support spec-mandated truncations of ISO-8601 timezones.
|
||||
|
||||
@@ -111,7 +179,7 @@ Features
|
||||
- Allow specifying custom representations of values for boolean fields.
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
---------
|
||||
|
||||
- Ensure that ``colander.iso8601.FixedOffset`` instances can be unpickled.
|
||||
|
||||
@@ -122,10 +190,10 @@ Bug Fixes
|
||||
|
||||
|
||||
1.0a2 (2013-01-30)
|
||||
------------------
|
||||
==================
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
- Add ``colander.ContainsOnly`` and ``colander.url`` validators.
|
||||
|
||||
@@ -133,10 +201,10 @@ Features
|
||||
mappings and sequences more succinctly.
|
||||
|
||||
1.0a1 (2013-01-10)
|
||||
------------------
|
||||
==================
|
||||
|
||||
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.
|
||||
@@ -152,7 +220,7 @@ Bug Fixes
|
||||
https://github.com/Pylons/colander/pull/96).
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
- Add ``colander.Set`` type, ported from ``deform.Set``
|
||||
|
||||
@@ -391,7 +459,7 @@ Features
|
||||
id='a2',
|
||||
)
|
||||
c = colander.SchemaNode(
|
||||
colander.String(),
|
||||
colander.String(),
|
||||
id='c2',
|
||||
)
|
||||
e = colander.SchemaNode(
|
||||
@@ -447,7 +515,7 @@ Features
|
||||
id='a2',
|
||||
)
|
||||
c = colander.SchemaNode(
|
||||
colander.String(),
|
||||
colander.String(),
|
||||
id='c2',
|
||||
)
|
||||
e = colander.SchemaNode(
|
||||
@@ -476,17 +544,17 @@ 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:
|
||||
name`` when constructing a SchemaNode.
|
||||
|
||||
0.9.9 (2012-09-24)
|
||||
------------------
|
||||
==================
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
- Allow the use of ``missing=None`` for Number. See
|
||||
https://github.com/Pylons/colander/pull/59 .
|
||||
@@ -525,7 +593,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
|
||||
@@ -534,13 +602,13 @@ Backwards Incompatibilities
|
||||
the child list.
|
||||
|
||||
Documentation
|
||||
~~~~~~~~~~~~~
|
||||
-------------
|
||||
|
||||
- A "Schema Inheritance" section was added to the Basics chapter
|
||||
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
|
||||
@@ -560,7 +628,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
|
||||
@@ -587,7 +655,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.
|
||||
|
||||
@@ -600,7 +668,7 @@ Documentation
|
||||
LICENSE.txt.
|
||||
|
||||
0.9.5 (2012-01-13)
|
||||
------------------
|
||||
==================
|
||||
|
||||
- Added Czech translation.
|
||||
|
||||
@@ -611,7 +679,7 @@ Documentation
|
||||
- Documentation added about flatten and unflatten.
|
||||
|
||||
0.9.4 (2011-10-14)
|
||||
------------------
|
||||
==================
|
||||
|
||||
- ``flatten`` now only includes leaf nodes in the flattened dict.
|
||||
|
||||
@@ -629,7 +697,7 @@ Documentation
|
||||
- Add Swedish, French, Chinese translations.
|
||||
|
||||
0.9.3 (2011-06-23)
|
||||
------------------
|
||||
==================
|
||||
|
||||
- Add ``Time`` type.
|
||||
|
||||
@@ -650,7 +718,7 @@ Documentation
|
||||
documentation.
|
||||
|
||||
0.9.2 (2011-03-28)
|
||||
------------------
|
||||
==================
|
||||
|
||||
- Added Polish translation, thanks to Jedrzej Nowak.
|
||||
|
||||
@@ -674,7 +742,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
|
||||
@@ -684,7 +752,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
|
||||
@@ -708,7 +776,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).
|
||||
@@ -724,7 +792,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
|
||||
@@ -739,7 +807,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.
|
||||
@@ -749,7 +817,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.
|
||||
@@ -757,13 +825,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.
|
||||
@@ -779,7 +847,7 @@ Bug Fixes
|
||||
``colander.SchemaNode``.
|
||||
|
||||
Backwards Incompatiblities / New Features
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-----------------------------------------
|
||||
|
||||
- ``missing`` constructor arg to SchemaNode: signifies
|
||||
*deserialization* default, disambiguated from ``default`` which acted
|
||||
@@ -845,7 +913,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
|
||||
@@ -864,13 +932,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.
|
||||
@@ -893,7 +961,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).
|
||||
|
||||
@@ -911,7 +979,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.
|
||||
@@ -935,20 +1003,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.
|
||||
@@ -965,7 +1033,7 @@ Backwards Incompatiblities / New Features
|
||||
- Add ``colander.Length`` validator class.
|
||||
|
||||
0.2 (2010-03-23)
|
||||
----------------
|
||||
================
|
||||
|
||||
- Make nodetype overrideable.
|
||||
|
||||
@@ -995,6 +1063,6 @@ Backwards Incompatiblities / New Features
|
||||
|
||||
|
||||
0.1 (2010-03-14)
|
||||
----------------
|
||||
================
|
||||
|
||||
- Initial release.
|
||||
@@ -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
|
||||
@@ -115,4 +116,14 @@ Contributors
|
||||
- Peter Lamut, 2013/08/16
|
||||
- Veeti Paananen, 2013/08/20
|
||||
- Michael Howitz, 2013/12/05
|
||||
- Alex Marandon, 2013/12/21
|
||||
- Joe Dallago, 2014/2/10
|
||||
- Jaseem Abid, 2014/06/16
|
||||
- Cédric Messiant, 2014/06/27
|
||||
- Gouji Ochiai, 2014/08/21
|
||||
- Tim Tisdall, 2014/09/10
|
||||
- Romain Commandé, 2014/10/11
|
||||
- Nando Florestan, 2014/11/27
|
||||
- Amos Latteier, 2014/11/30
|
||||
- Jimmy Thrasibule, 2014/12/11
|
||||
- Tinne Cahy, 2015/12/22
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/colander/badge/?version=master
|
||||
:target: http://docs.pylonsproject.org/projects/colander/en/master/
|
||||
:alt: Documentation Status
|
||||
|
||||
|
||||
An extensible package which can be used to:
|
||||
|
||||
- deserialize and validate a data structure composed of strings,
|
||||
@@ -9,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, 3.4, and 3.5, and on current PyPy
|
||||
and PyPy3 versions.
|
||||
|
||||
Please see http://docs.pylonsproject.org/projects/colander/en/latest/
|
||||
for further documentation.
|
||||
@@ -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 '<colander.required>'
|
||||
|
||||
required = _required()
|
||||
_marker = required # bw compat
|
||||
|
||||
class _null(object):
|
||||
@@ -42,11 +47,12 @@ class _null(object):
|
||||
null = _null()
|
||||
|
||||
class _drop(object):
|
||||
""" 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`.
|
||||
"""
|
||||
Represents a value that should be dropped if it is missing during
|
||||
deserialization.
|
||||
"""
|
||||
pass
|
||||
def __repr__(self):
|
||||
return '<colander.drop>'
|
||||
|
||||
drop = _drop()
|
||||
|
||||
@@ -57,6 +63,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
|
||||
@@ -165,9 +178,14 @@ 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"""
|
||||
(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:
|
||||
@@ -177,6 +195,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
|
||||
|
||||
@@ -279,6 +299,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.
|
||||
|
||||
@@ -286,9 +309,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:
|
||||
@@ -307,7 +330,7 @@ class Email(Regex):
|
||||
the error message to be used when raising :exc:`colander.Invalid`;
|
||||
otherwise, defaults to 'Invalid email address'.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, msg=None):
|
||||
email_regex = text_(EMAIL_RE)
|
||||
if msg is None:
|
||||
@@ -339,49 +362,74 @@ 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)
|
||||
|
||||
|
||||
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."""
|
||||
def __init__(self, min=None, max=None):
|
||||
"""Validator which succeeds if the value passed to it has a
|
||||
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=_MIN_ERR, max_err=_MAX_ERR):
|
||||
self.min = min
|
||||
self.max = max
|
||||
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 = _('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 = _('Longer than maximum length ${max}',
|
||||
mapping={'max':self.max})
|
||||
max_err = _(self.max_err, 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 """
|
||||
@@ -395,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``.
|
||||
@@ -451,6 +526,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'), re.IGNORECASE)
|
||||
|
||||
|
||||
class SchemaType(object):
|
||||
""" Base class for all schema types """
|
||||
def flatten(self, node, appstruct, prefix='', listitem=False):
|
||||
@@ -459,7 +539,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):
|
||||
@@ -910,7 +990,7 @@ class Sequence(Positional, SchemaType):
|
||||
|
||||
def _validate(self, node, value, accept_scalar):
|
||||
if (hasattr(value, '__iter__') and
|
||||
not hasattr(value, 'get') and
|
||||
not hasattr(value, 'get') and
|
||||
not isinstance(value, string_types)):
|
||||
return list(value)
|
||||
if accept_scalar:
|
||||
@@ -1095,7 +1175,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
|
||||
@@ -1132,6 +1212,8 @@ class String(SchemaType):
|
||||
result = text_type(appstruct)
|
||||
else:
|
||||
result = text_type(appstruct)
|
||||
if self.encoding:
|
||||
result = result.encode(self.encoding)
|
||||
return result
|
||||
except Exception as e:
|
||||
raise Invalid(node,
|
||||
@@ -1223,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))
|
||||
@@ -1249,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):
|
||||
@@ -1264,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.
|
||||
@@ -1293,7 +1379,7 @@ class Boolean(SchemaType):
|
||||
are considered ``True``, and an Invalid exception would be raised
|
||||
for values outside of both :attr:`false_choices` and :attr:`true_choices`.
|
||||
|
||||
Serialization will produce :attr:`true_val` or :attr:`false_val`
|
||||
Serialization will produce :attr:`true_val` or :attr:`false_val`
|
||||
based on the value.
|
||||
|
||||
If the :attr:`colander.null` value is passed to the serialize
|
||||
@@ -1340,8 +1426,8 @@ class Boolean(SchemaType):
|
||||
else:
|
||||
raise Invalid(node,
|
||||
_('"${val}" is neither in (${false_choices}) '
|
||||
'nor in (${true_choices})',
|
||||
mapping={'val':cstruct,
|
||||
'nor in (${true_choices})',
|
||||
mapping={'val':cstruct,
|
||||
'false_choices': self.false_reprs,
|
||||
'true_choices': self.true_reprs })
|
||||
)
|
||||
@@ -1524,9 +1610,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):
|
||||
@@ -1635,7 +1719,7 @@ class Time(SchemaType):
|
||||
|
||||
This type serializes python ``datetime.time`` objects to a
|
||||
`ISO8601 <http://en.wikipedia.org/wiki/ISO_8601>`_ string format.
|
||||
The format includes the date only.
|
||||
The format includes the time only.
|
||||
|
||||
The constructor accepts no arguments.
|
||||
|
||||
@@ -1673,13 +1757,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})
|
||||
@@ -1735,7 +1818,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
|
||||
@@ -1806,10 +1891,10 @@ class _SchemaNode(object):
|
||||
validator = None
|
||||
default = null
|
||||
missing = required
|
||||
missing_msg = _('Required')
|
||||
missing_msg = 'Required'
|
||||
name = ''
|
||||
raw_title = _marker
|
||||
title = ''
|
||||
raw_title = _marker # only changes if title is explicitly set
|
||||
title = _marker
|
||||
description = ''
|
||||
widget = None
|
||||
after_bind = None
|
||||
@@ -1834,7 +1919,7 @@ 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:
|
||||
name = kw.get('name', self.name)
|
||||
kw['title'] = name.replace('_', ' ').title()
|
||||
@@ -1846,7 +1931,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 '
|
||||
)
|
||||
|
||||
@@ -1948,15 +2033,21 @@ 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
|
||||
return appstruct
|
||||
|
||||
if self.validator is not None:
|
||||
if not isinstance(self.validator, deferred): # unbound
|
||||
self.validator(self, appstruct)
|
||||
if isinstance(self.validator, deferred): # unbound
|
||||
raise UnboundDeferredError(
|
||||
"Schema node {node} has an unbound deferred validator"
|
||||
.format(node=self))
|
||||
self.validator(self, appstruct)
|
||||
return appstruct
|
||||
|
||||
def add(self, node):
|
||||
@@ -2214,9 +2305,9 @@ class instantiate(object):
|
||||
All parameters passed to the decorator and passed along to the
|
||||
:class:`SchemaNode` during instantiation.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self,*args,**kw):
|
||||
self.args,self.kw = args,kw
|
||||
|
||||
|
||||
def __call__(self,class_):
|
||||
return class_(*self.args,**self.kw)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,152 +1,5 @@
|
||||
"""
|
||||
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=<iso8601.iso8601.Utc ...>)
|
||||
>>>
|
||||
"""
|
||||
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
import re
|
||||
|
||||
from .compat import string_types
|
||||
from __future__ import absolute_import
|
||||
import iso8601
|
||||
from iso8601.iso8601 import (parse_date, ParseError, Utc, FixedOffset, UTC, ZERO, ISO8601_REGEX)
|
||||
|
||||
__all__ = ["parse_date", "ParseError", "Utc", "FixedOffset"]
|
||||
|
||||
# Adapted from http://delete.me.uk/2005/03/iso8601.html
|
||||
ISO8601_REGEX = re.compile(
|
||||
r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})"
|
||||
r"((?P<separator>.)(?P<hour>[0-9]{2})(:(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?)?"
|
||||
r"(?P<timezone>Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?"
|
||||
)
|
||||
TIMEZONE_REGEX = re.compile(
|
||||
"(?P<prefix>[+-])(?P<hours>[0-9]{2})(:?(?P<minutes>[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 "<FixedOffset %r>" % 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
colander/locale/el/LC_MESSAGES/colander.mo
Normal file
BIN
colander/locale/el/LC_MESSAGES/colander.mo
Normal file
Binary file not shown.
156
colander/locale/el/LC_MESSAGES/colander.po
Normal file
156
colander/locale/el/LC_MESSAGES/colander.po
Normal file
@@ -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 <EMAIL@ADDRESS>, 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 <dan@car.gr>\n"
|
||||
"Language-Team: el <LL@li.org>\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}"
|
||||
|
||||
Binary file not shown.
@@ -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 <stephane.bard@gmail.com>\n"
|
||||
"PO-Revision-Date: 2014-06-27 11:46+0100\n"
|
||||
"Last-Translator: Cédric Messiant <cedric.messiant@gmail.com>\n"
|
||||
"Language-Team: fr <LL@li.org>\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"
|
||||
|
||||
Binary file not shown.
@@ -7,141 +7,152 @@ msgid ""
|
||||
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"
|
||||
"POT-Creation-Date: 2013-11-16 14:23+0900\n"
|
||||
"PO-Revision-Date: 2011-06-03 00:22+0200\n"
|
||||
"Last-Translator: Wichert Akkerman <wichert@wiggy.net>\n"
|
||||
"Last-Translator: Tinne Cahy <cahytinne@gmail.com>\n"
|
||||
"Language-Team: nl <LL@li.org>\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 kredietkaartnummer"
|
||||
|
||||
#: 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 niet aanwezig in (${false_choices}) 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}."
|
||||
|
||||
@@ -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,15 +427,17 @@ 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')
|
||||
|
||||
|
||||
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 +465,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
|
||||
@@ -475,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
|
||||
@@ -538,6 +569,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.assertEqual(result, None)
|
||||
|
||||
def test_success_with_dashes(self):
|
||||
val = '123e4567-e89b-12d3-a456-426655440000'
|
||||
result = self._callFUT(val)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
def test_success_upper_case(self):
|
||||
val = '123E4567-E89B-12D3-A456-426655440000'
|
||||
result = self._callFUT(val)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
def test_success_with_braces(self):
|
||||
val = '{123e4567-e89b-12d3-a456-426655440000}'
|
||||
result = self._callFUT(val)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
def test_success_with_urn_ns(self):
|
||||
val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}'
|
||||
result = self._callFUT(val)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
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
|
||||
@@ -725,7 +807,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()
|
||||
@@ -1309,6 +1391,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 = [
|
||||
@@ -1481,6 +1573,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 = text_type('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
|
||||
@@ -1499,7 +1598,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)
|
||||
@@ -1592,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
|
||||
@@ -1626,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)
|
||||
@@ -1649,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)
|
||||
@@ -2257,6 +2373,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()
|
||||
@@ -2333,8 +2458,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)
|
||||
@@ -2453,6 +2579,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
|
||||
@@ -2474,6 +2613,14 @@ class TestSchemaNode(unittest.TestCase):
|
||||
e = invalid_exc(node.deserialize, null)
|
||||
self.assertEqual(e.msg, 'Missing')
|
||||
|
||||
def test_deserialize_value_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)
|
||||
@@ -2717,6 +2864,32 @@ 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_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):
|
||||
@@ -3436,7 +3609,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',
|
||||
@@ -3458,6 +3632,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'):
|
||||
@@ -3598,7 +3805,7 @@ class TestUltraDeclarative(unittest.TestCase, TestFunctional):
|
||||
return schema
|
||||
|
||||
class TestDeclarativeWithInstantiate(unittest.TestCase, TestFunctional):
|
||||
|
||||
|
||||
def _makeSchema(self, name='schema'):
|
||||
|
||||
import colander
|
||||
@@ -3612,20 +3819,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())
|
||||
@@ -3647,6 +3854,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), '<colander.required>')
|
||||
|
||||
class Test_drop(unittest.TestCase):
|
||||
def test___repr__(self):
|
||||
from colander import drop
|
||||
self.assertEqual(repr(drop), '<colander.drop>')
|
||||
|
||||
class Dummy(object):
|
||||
pass
|
||||
|
||||
|
||||
2
colander/tests/test_interfaces.py
Normal file
2
colander/tests/test_interfaces.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def test_interfaces():
|
||||
from colander import interfaces
|
||||
@@ -68,12 +68,17 @@ class Test_FixedOffset(unittest.TestCase):
|
||||
def test___repr__(self):
|
||||
inst = self._makeOne()
|
||||
result = inst.__repr__()
|
||||
self.assertEqual(result, "<FixedOffset 'oneandahalf'>")
|
||||
self.assertEqual(result, "<FixedOffset 'oneandahalf' datetime.timedelta(0, 5400)>")
|
||||
|
||||
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{0}".format(tzstring), **kw)
|
||||
return dt.tzinfo
|
||||
|
||||
def test_default_Z(self):
|
||||
from ..iso8601 import UTC
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -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 <pre> tag renderings */
|
||||
|
||||
pre {
|
||||
line-height: normal !important;
|
||||
}
|
||||
26
docs/api.rst
26
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
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -61,6 +64,8 @@ Validators
|
||||
|
||||
.. autoclass:: OneOf
|
||||
|
||||
.. autoclass:: NoneOf
|
||||
|
||||
.. autoclass:: ContainsOnly
|
||||
|
||||
.. autoclass:: Function
|
||||
@@ -75,6 +80,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
|
||||
~~~~~
|
||||
|
||||
@@ -139,16 +149,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`.
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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*.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
.. include:: ../CHANGES.txt
|
||||
.. include:: ../CHANGES.rst
|
||||
|
||||
57
docs/conf.py
57
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 <pylons-discuss@googlegroups.com>'
|
||||
thisyear = datetime.datetime.now().year
|
||||
copyright = '2012-%s, Agendaless Consulting <pylons-discuss@googlegroups.com>' % 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,13 +164,13 @@ 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'),
|
||||
]
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
18
setup.py
18
setup.py
@@ -24,31 +24,37 @@ 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 = ''
|
||||
|
||||
requires = ['translationstring']
|
||||
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.0b1',
|
||||
version='1.1',
|
||||
description=('A simple schema-based serialization and deserialization '
|
||||
'library'),
|
||||
long_description=README + '\n\n' + CHANGES,
|
||||
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 :: 3.5",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
],
|
||||
|
||||
69
tox.ini
69
tox.ini
@@ -1,25 +1,62 @@
|
||||
[tox]
|
||||
envlist =
|
||||
py26,py27,py32,py33,pypy,cover,docs
|
||||
py26,py27,py32,py33,py34,py35,pypy,pypy3,
|
||||
docs,
|
||||
{py2,py3}-cover,coverage
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
python setup.py dev
|
||||
python -Wd setup.py 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.6
|
||||
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 =
|
||||
python setup.py nosetests --with-xunit --with-xcoverage
|
||||
deps =
|
||||
nosexcover
|
||||
pip install colander[testing]
|
||||
nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:}
|
||||
|
||||
[testenv:docs]
|
||||
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
|
||||
basepython = python3.5
|
||||
whitelist_externals = make
|
||||
commands =
|
||||
pip install colander[docs]
|
||||
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 =
|
||||
Sphinx
|
||||
coverage
|
||||
setenv =
|
||||
COVERAGE_FILE=.coverage
|
||||
|
||||
Reference in New Issue
Block a user