diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5a205b2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# Wire up travis +language: python + +env: + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py32 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=pypy + - TOXENV=pypy3 + - TOXENV=cover + +install: + - travis_retry pip install tox + +script: + - travis_retry tox + +notifications: + email: + - pyramid-checkins@lists.repoze.org diff --git a/CHANGES.txt b/CHANGES.rst similarity index 98% rename from CHANGES.txt rename to CHANGES.rst index 1908d9e..2e6ea1c 100644 --- a/CHANGES.txt +++ b/CHANGES.rst @@ -1,9 +1,34 @@ Unreleased ---------- +Platform +-------- + +- Addd explicit support for Python 3.4 and PyPy3. + +Features +~~~~~~~~ + +- Add `Any` validator which succeeds if at least one of its subvalidators + succeeded. + +- Allow localization of error messages returned by ``colander.Invalid.asdict`` + by adding an optional ``translate`` callable argument. + +- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error + message to be used when the node is required and missing + +1.0 (2014-11-26) +---------------- + Bug Fixes ~~~~~~~~~ +- Removed forked ``iso8601`` and change to dependency on PyPI ``iso8601`` + (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``. @@ -17,14 +42,13 @@ Bug Fixes returning the ``drop`` instance instead of omitting the value. https://github.com/Pylons/colander/issues/139 -Features -~~~~~~~~ +- 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 -- Add `Any` validator which succeeds if at least one of its subvalidators - succeeded. +- Updated translations: ``fr``, ``de``, ``ja`` -- Allow localization of error messages returned by ``colander.Invalid.asdict`` - by adding an optional ``translate`` callable argument. Backwards Incompatibilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -90,9 +114,6 @@ Features - The ``typ`` of a ``SchemaNode`` can optionally be pased in as a keyword argument. See https://github.com/Pylons/colander/issues/90 -- Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error - message to be used when the node is required and missing - 1.0a5 (2013-05-31) ------------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e1ef2e0..466fc7b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -101,7 +101,8 @@ and agrees to the terms above in the section within this document entitled Contributors ------------ -- Chris McDonough, 2011/02/16 +- Chris McDonough, 2010/03/11 +- Tres Seaver, 2010/04/05 - Chris Withers, 2011/05/45 - Mathieu Le Marec - Pasquet (kiorky), 2011/07/11 - Atsushi Odagiri, 2012/02/04 @@ -116,3 +117,6 @@ Contributors - Veeti Paananen, 2013/08/20 - Michael Howitz, 2013/12/05 - Alex Marandon, 2013/12/21 +- Cédric Messiant, 2014/06/27 +- Gouji Ochiai, 2014/08/21 +- Tim Tisdall, 2014/09/10 diff --git a/LICENSE.txt b/LICENSE.txt index c4a6ed2..5ced96e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -39,6 +39,3 @@ License THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -This package uses code from the "pyiso8601" package by Michael Twomey, -licensed under the MIT license. See the source file named "iso8601.py" for -copyright information and license text. diff --git a/README.txt b/README.rst similarity index 51% rename from README.txt rename to README.rst index 215ba5d..dba9a81 100644 --- a/README.txt +++ b/README.rst @@ -1,6 +1,15 @@ Colander ======== + +.. image:: https://travis-ci.org/Pylons/colander.svg?branch=master + :target: https://travis-ci.org/Pylons/colander + +.. image:: https://readthedocs.org/projects/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, and 3.4, and on current PyPy +and PyPy3 versions. Please see http://docs.pylonsproject.org/projects/colander/en/latest/ for further documentation. diff --git a/colander/__init__.py b/colander/__init__.py index 462f36b..8a9201d 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1822,7 +1822,7 @@ class _SchemaNode(object): missing_msg = _('Required') name = '' raw_title = _marker - title = '' + title = _marker description = '' widget = None after_bind = None @@ -1849,8 +1849,9 @@ class _SchemaNode(object): # bw compat forces us to manufacture a title if one is not supplied title = kw.get('title', _marker) if title is _marker: - name = kw.get('name', self.name) - kw['title'] = name.replace('_', ' ').title() + if self.title is _marker: + name = kw.get('name', self.name) + kw['title'] = name.replace('_', ' ').title() else: kw['raw_title'] = title diff --git a/colander/iso8601.py b/colander/iso8601.py index 0dcd9a6..f608c11 100644 --- a/colander/iso8601.py +++ b/colander/iso8601.py @@ -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=) ->>> -""" - - -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[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" - r"((?P.)(?P[0-9]{2})(:(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?)?" - r"(?PZ|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?" -) -TIMEZONE_REGEX = re.compile( - "(?P[+-])(?P[0-9]{2})(:?(?P[0-9]{2}))?") - -class ParseError(Exception): - """Raised when there is a problem parsing a date string""" - -# Yoinked from python docs -ZERO = timedelta(0) -class Utc(tzinfo): - """UTC - - """ - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO -UTC = Utc() - -class FixedOffset(tzinfo): - """Fixed offset in hours and minutes from UTC - - """ - def __init__(self, offset_hours, offset_minutes, name): - self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) - self.__name = name - - def __getinitargs__(self): - # tzinfo.__reduce__ returns the type as the factory: supply - # defaults here, rather than in __init__. - return 0, 0, 'unknown' - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - def __repr__(self): - return "" % self.__name - -def parse_timezone(tzstring, default_timezone=UTC): - """Parses ISO 8601 time zone specs into tzinfo offsets - - """ - if tzstring == "Z": - return UTC - # This isn't strictly correct, but it's common to encounter dates without - # timezones so I'll assume the default (which defaults to UTC). - # Addresses issue 4. - if tzstring is None: - return default_timezone - m = TIMEZONE_REGEX.match(tzstring) - prefix = m.group('prefix') - hours = int(m.group('hours')) - minutes = m.group('minutes') - if minutes is None: - minutes = 0 - else: - minutes = int(minutes) - if prefix == "-": - hours = -hours - minutes = -minutes - return FixedOffset(hours, minutes, tzstring) - -def parse_date(datestring, default_timezone=UTC): - """Parses ISO 8601 dates into datetime objects - - The timezone is parsed from the date string. However it is quite common to - have dates without a timezone (not strictly correct). In this case the - default timezone specified in default_timezone is used. This is UTC by - default. - """ - if not isinstance(datestring, string_types): - raise ParseError("Expecting a string %r" % datestring) - m = ISO8601_REGEX.match(datestring) - if not m: - raise ParseError("Unable to parse date string %r" % datestring) - groups = m.groupdict() - tz = parse_timezone(groups["timezone"], default_timezone=default_timezone) - if (groups['year'] is None or - groups['month'] is None or - groups['day'] is None): - raise ParseError('Unable to parse date string %r' % datestring) - if groups["hour"] is None: - groups["hour"] = 0 - if groups["minute"] is None: - groups["minute"] = 0 - if groups["second"] is None: - groups["second"] = 0 - if groups["fraction"] is None: - groups["fraction"] = 0 - else: - groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6) - try: - return datetime( - int(groups["year"]), int(groups["month"]), int(groups["day"]), - int(groups["hour"]), int(groups["minute"]), int(groups["second"]), - int(groups["fraction"]), tz) - except ValueError as e: - raise ParseError(*e.args) diff --git a/colander/locale/de/LC_MESSAGES/colander.po b/colander/locale/de/LC_MESSAGES/colander.po index b58eeca..b7663d1 100644 --- a/colander/locale/de/LC_MESSAGES/colander.po +++ b/colander/locale/de/LC_MESSAGES/colander.po @@ -31,11 +31,11 @@ msgstr "Ungültige E-Mail-Adresse" #: colander/__init__.py:315 msgid "${val} is less than minimum value ${min}" -msgstr "${val} ist kleiner als der erlaubter Mindestwert (${min})" +msgstr "${val} ist kleiner als der erlaubte Mindestwert (${min})" #: colander/__init__.py:316 msgid "${val} is greater than maximum value ${max}" -msgstr "${val} ist größer als der erlaubter Höchstwert (${max})" +msgstr "${val} ist größer als der erlaubte Höchstwert (${max})" #: colander/__init__.py:348 msgid "Shorter than minimum length ${min}" @@ -51,11 +51,11 @@ msgstr "\"${val}\" ist nicht in der Auswahl ${choices}" #: colander/__init__.py:377 msgid "One or more of the choices you made was not acceptable" -msgstr "" +msgstr "Mindestens eine Auswahl war nicht akzeptabel" #: colander/__init__.py:423 msgid "Must be a URL" -msgstr "" +msgstr "Muss eine URL sein" #: colander/__init__.py:519 msgid "\"${val}\" is not a mapping type: ${err}" @@ -99,7 +99,7 @@ msgstr "${val} ist keine Zeichenkette" #: colander/__init__.py:1279 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})" -msgstr "" +msgstr "\"${val}\" ist weder in (${false_choices}) noch in (${true_choices}) enthalten" #: colander/__init__.py:1339 colander/__init__.py:1356 #: colander/__init__.py:1366 diff --git a/colander/locale/fr/LC_MESSAGES/colander.mo b/colander/locale/fr/LC_MESSAGES/colander.mo index 164708b..5f0ee9f 100644 Binary files a/colander/locale/fr/LC_MESSAGES/colander.mo and b/colander/locale/fr/LC_MESSAGES/colander.mo differ diff --git a/colander/locale/fr/LC_MESSAGES/colander.po b/colander/locale/fr/LC_MESSAGES/colander.po index e30f1c0..3cba97b 100644 --- a/colander/locale/fr/LC_MESSAGES/colander.po +++ b/colander/locale/fr/LC_MESSAGES/colander.po @@ -8,14 +8,15 @@ msgstr "" "Project-Id-Version: colander 0.8\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2013-05-19 13:15+0200\n" -"PO-Revision-Date: 2011-10-02 21:38+0100\n" -"Last-Translator: Bard Stéphane \n" +"PO-Revision-Date: 2014-06-27 11:46+0100\n" +"Last-Translator: Cédric Messiant \n" "Language-Team: fr \n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"X-Generator: Poedit 1.5.4\n" #: colander/__init__.py:233 msgid "Invalid value" @@ -23,11 +24,11 @@ msgstr "Valeur incorrecte" #: colander/__init__.py:270 msgid "String does not match expected pattern" -msgstr "La chaîne de caractères n'a pas vérifié le modèle attendu" +msgstr "La chaîne de caractères ne correspond pas au modèle attendu" #: colander/__init__.py:287 msgid "Invalid email address" -msgstr "Invalide adresse email" +msgstr "Adresse email invalide" #: colander/__init__.py:315 msgid "${val} is less than minimum value ${min}" @@ -39,7 +40,7 @@ msgstr "${val} est plus grand que la valeur maximum autorisée (${max})" #: colander/__init__.py:348 msgid "Shorter than minimum length ${min}" -msgstr "La longueur est inférieur à la taille minimum autorisée (${min})" +msgstr "La longueur est inférieure à la taille minimum autorisée (${min})" #: colander/__init__.py:354 msgid "Longer than maximum length ${max}" @@ -73,11 +74,10 @@ msgstr "\"${val}\" n'est pas itérable" #: colander/__init__.py:664 msgid "" -"\"${val}\" has an incorrect number of elements (expected ${exp}, was " -"${was})" +"\"${val}\" has an incorrect number of elements (expected ${exp}, was ${was})" msgstr "" -"\"${val}\" possède un nombre incorrecte d'éléments (attendu ${exp}, " -"trouvé ${was})" +"\"${val}\" possède un nombre incorrect d'éléments (attendu ${exp}, trouvé " +"${was})" #: colander/__init__.py:803 msgid "${cstruct} is not iterable" @@ -97,11 +97,12 @@ msgstr "\"${val}\" n'est pas un nombre" #: colander/__init__.py:1268 msgid "${val} is not a string" -msgstr "${val} n'est pas une chaîne de carctères" +msgstr "${val} n'est pas une chaîne de caractères" #: colander/__init__.py:1279 msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})" -msgstr "\"${val}\" ne fait partie ni de (${false_choices}) ni de (${true_choices})" +msgstr "" +"\"${val}\" ne fait partie ni de (${false_choices}) ni de (${true_choices})" #: colander/__init__.py:1339 colander/__init__.py:1356 #: colander/__init__.py:1366 @@ -133,16 +134,14 @@ msgid "\"${val}\" is not a date object" msgstr "\"${val}\" n'est pas un objet date" #: colander/__init__.py:1610 -#, fuzzy #| msgid "Invalid date" msgid "Invalid time" -msgstr "Date invalide" +msgstr "Heure invalide" #: colander/__init__.py:1621 -#, fuzzy #| msgid "\"${val}\" is not a datetime object" msgid "\"${val}\" is not a time object" -msgstr "\"${val}\" n'est pas un objet datetime" +msgstr "\"${val}\" n'est pas un objet time" #: colander/__init__.py:1878 colander/__init__.py:1880 msgid "Required" diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 5ea17d1..36b5a88 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -2739,6 +2739,22 @@ class TestSchemaNodeSubclassing(unittest.TestCase): result = node.deserialize(colander.null) self.assertEqual(result, 10) + def test_subclass_uses_title(self): + import colander + class MyNode(colander.SchemaNode): + schema_type = colander.Int + title = 'some title' + node = MyNode(name='my') + self.assertEqual(node.title, 'some title') + + def test_subclass_title_overwritten_by_constructor(self): + import colander + class MyNode(colander.SchemaNode): + schema_type = colander.Int + title = 'some title' + node = MyNode(name='my', title='other title') + self.assertEqual(node.title, 'other title') + def test_subclass_value_overridden_by_constructor(self): import colander class MyNode(colander.SchemaNode): diff --git a/colander/tests/test_iso8601.py b/colander/tests/test_iso8601.py index c579eee..bd73ae2 100644 --- a/colander/tests/test_iso8601.py +++ b/colander/tests/test_iso8601.py @@ -68,12 +68,17 @@ class Test_FixedOffset(unittest.TestCase): def test___repr__(self): inst = self._makeOne() result = inst.__repr__() - self.assertEqual(result, "") + self.assertEqual(result, "") class Test_parse_timezone(unittest.TestCase): def _callFUT(self, tzstring, **kw): - from ..iso8601 import parse_timezone - return parse_timezone(tzstring, **kw) + # mimic old parse_timezone() by returning a FixedOffset + from datetime import tzinfo + from ..iso8601 import (parse_date, FixedOffset) + if tzstring is None: + tzstring = '' + dt = parse_date("2006-10-11T00:14:33{0}".format(tzstring), **kw) + return dt.tzinfo def test_default_Z(self): from ..iso8601 import UTC diff --git a/docs/basics.rst b/docs/basics.rst index 83c862c..4e8bbcf 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -171,7 +171,7 @@ The imperative style that looks like this still works, of course: .. code-block:: python ranged_int = colander.SchemaNode( - typ=colander.Int(), + schema_type=colander.Int, validator=colander.Range(0, 10), default=10, title='Ranged Int' @@ -182,7 +182,7 @@ But in 1.0a1+, you can alternately now do something like this: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int validator = colander.Range(0, 10) default = 10 title = 'Ranged Int' @@ -195,7 +195,7 @@ the schemanode subclass instead of plain attributes: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -220,7 +220,7 @@ example this will *not* work: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -247,7 +247,7 @@ indeed work): .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -271,7 +271,7 @@ the bind parameters within values that are plain old methods: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' @@ -292,7 +292,7 @@ attributes of the schemanode that rely on binding variables: .. code-block:: python class UserIdSchemaNode(colander.SchemaNode): - typ = colander.String() + schema_type = colander.String title = 'User Id' def after_bind(self, node, kw): @@ -304,7 +304,7 @@ constructor: .. code-block:: python class RangedInt(colander.SchemaNode): - typ = colander.Int() + schema_type = colander.Int default = 10 title = 'Ranged Int' validator = colander.Range(0, 10) @@ -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): diff --git a/setup.py b/setup.py index 9a6c66c..130f79f 100644 --- a/setup.py +++ b/setup.py @@ -24,31 +24,32 @@ 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'] setup(name='colander', - version='1.0b1', + version='1.1dev', 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 :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/tox.ini b/tox.ini index 69295a2..4010935 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py26,py27,py32,py33,pypy,cover,docs + py26,py27,py32,py33,py34,pypy,pypy3,cover,docs [testenv] commands = @@ -11,7 +11,7 @@ commands = basepython = python2.6 commands = - python setup.py nosetests --with-xunit --with-xcoverage + python setup.py nosetests --with-xunit --with-xcoverage --cover-min-percentage=100 deps = nosexcover @@ -20,6 +20,5 @@ basepython = python2.6 commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html -# sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest deps = Sphinx