diff --git a/.travis.yml b/.travis.yml index ce5d214..6e19505 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,13 @@ python: - 3.4 - 3.5 - 3.6 - - pypy-5.6.0 + - pypy + - pypy3 +matrix: + include: + - python: "3.7" + dist: xenial + sudo: true script: - coverage run -m zope.testrunner --test-path=src diff --git a/CHANGES.rst b/CHANGES.rst index 6ab7c79..de05167 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,8 +1,11 @@ -Changes -======= +========= + Changes +========= -4.5.1 (unreleased) ------------------- +4.6.0 (unreleased) +================== + +- Add support for Python 3.7. - ``Object`` instances call their schema's ``validateInvariants`` method by default to collect errors from functions decorated with @@ -10,6 +13,14 @@ Changes ``validate_invariants=False`` to the ``Object`` constructor. See `issue 10 `_. +- ``ValidationError`` can be sorted on Python 3. + +- ``DottedName`` and ``Id`` consistently handle non-ASCII unicode + values on Python 2 and 3 by raising ``InvalidDottedName`` and + ``InvalidId`` in ``fromUnicode`` respectively. Previously, a + ``UnicodeEncodeError`` would be raised on Python 2 while Python 3 + would raise the descriptive exception. + - ``Field`` instances are hashable on Python 3, and use a defined hashing algorithm that matches what equality does on all versions of Python. Previously, on Python 2, fields were hashed based on their @@ -19,7 +30,6 @@ Changes compatibility problem. See `issue 36 `_. - - Orderable fields, including ``Int``, ``Float``, ``Decimal``, ``Timedelta``, ``Date`` and ``Time``, can now have a ``missing_value`` without needing to specify concrete ``min`` and @@ -34,7 +44,7 @@ Changes 4.5.0 (2017-07-10) ------------------- +================== - Drop support for Python 2.6, 3.2, and 3.3. @@ -44,19 +54,19 @@ Changes 4.4.2 (2014-09-04) ------------------- +================== - Fix description of min max field: max value is included, not excluded. 4.4.1 (2014-03-19) ------------------- +================== - Add support for Python 3.4. 4.4.0 (2014-01-22) ------------------- +================== - Add an event on field properties to notify that a field has been updated. This event enables definition of subscribers based on an event, a context @@ -65,7 +75,7 @@ Changes 4.3.3 (2014-01-06) ------------------- +================== - PEP 8 cleanup. @@ -82,14 +92,14 @@ Changes 4.3.2 (2013-02-24) ------------------- +================== - Fix Python 2.6 support. (Forgot to run tox with all environments before last release.) 4.3.1 (2013-02-24) ------------------- +================== - Make sure that we do not fail during bytes decoding of term token when generated from a bytes value by ignoring all errors. (Another option would @@ -97,7 +107,7 @@ Changes 4.3.0 (2013-02-24) ------------------- +================== - Fix a bug where bytes values were turned into tokens inproperly in Python 3. @@ -106,18 +116,18 @@ Changes maps schema fields into ``FieldProperty`` instances. 4.2.2 (2012-11-21) ------------------- +================== - Add support for Python 3.3. 4.2.1 (2012-11-09) ------------------- +================== - Fix the default property of fields that have no defaultFactory attribute. 4.2.0 (2012-05-12) ------------------- +================== - Automate build of Sphinx HTML docs and running doctest snippets via tox. @@ -149,13 +159,13 @@ Changes 4.1.1 (2012-03-23) ------------------- +================== - Remove trailing slash in MANIFEST.in, it causes Winbot to crash. 4.1.0 (2012-03-23) ------------------- +================== - Add TreeVocabulary for nested tree-like vocabularies. @@ -167,13 +177,13 @@ Changes IContextSourceBinder is still missing. 4.0.1 (2011-11-14) ------------------- +================== - Fix bug in ``fromUnicode`` method of ``DottedName`` which would fail validation on being given unicode. Introduced in 4.0.0. 4.0.0 (2011-11-09) ------------------- +================== - Fix deprecated unittest methods. @@ -181,14 +191,14 @@ Changes Python 2.5. 3.8.1 (2011-09-23) ------------------- +================== - Fix broken Object field validation. Previous version was using a volatile property on object field values which ends in a ForbiddenAttribute error on security proxied objects. 3.8.0 (2011-03-18) ------------------- +================== - Implement a ``defaultFactory`` attribute for all fields. It is a callable that can be used to compute default values. The simplest case is:: @@ -205,7 +215,7 @@ Changes Date(defaultFactory=today) 3.7.1 (2010-12-25) ------------------- +================== - Rename the validation token, used in the validation of schema with Object Field to avoid infinite recursion: @@ -217,31 +227,31 @@ Changes https://bugs.launchpad.net/zope.schema/+bug/191236 3.7.0 (2010-09-12) ------------------- +================== - Improve error messages when term tokens or values are duplicates. - Fix the buildout so the tests run. 3.6.4 (2010-06-08) ------------------- +================== - fix validation of schema with Object Field that specify Interface schema. 3.6.3 (2010-04-30) ------------------- +================== - Prefer the standard libraries doctest module to the one from zope.testing. 3.6.2 (2010-04-30) ------------------- +================== - Avoid maximum recursion when validating Object field that points to cycles - Make the dependency on ``zope.i18nmessageid`` optional. 3.6.1 (2010-01-05) ------------------- +================== - Allow "setup.py test" to run at least a subset of the tests runnable via ``bin/test`` (227 for ``setup.py test`` vs. 258. for @@ -253,7 +263,7 @@ Changes - Make "setup.py test" tests pass on Jython. 3.6.0 (2009-12-22) ------------------- +================== - Prefer zope.testing.doctest over doctestunit. @@ -263,7 +273,7 @@ Changes instead of storing directly on the instance __dict__. 3.5.4 (2009-03-25) ------------------- +================== - Don't fail trying to validate default value for Choice fields with IContextSourceBinder object given as a source. See @@ -281,7 +291,7 @@ Changes 3.5.3 (2009-03-10) ------------------- +================== - Make Choice and Bool fields implement IFromUnicode interface, because they do provide the ``fromUnicode`` method. @@ -296,7 +306,7 @@ Changes - Remove zpkg-related file. 3.5.2 (2009-02-04) ------------------- +================== - Made validation tests compatible with Python 2.5 again (hopefully not breaking Python 2.4) @@ -304,7 +314,7 @@ Changes - Add an __all__ package attribute to expose documentation. 3.5.1 (2009-01-31) ------------------- +================== - Stop using the old old set type. @@ -318,7 +328,7 @@ Changes much more sense for debugging for developers. 3.5.0a2 (2008-12-11) --------------------- +==================== - Move zope.testing to "test" extras_require, as it is not needed for zope.schema itself. @@ -329,7 +339,7 @@ Changes example is z3c.form's converter. 3.5.0a1 (2008-10-10) --------------------- +==================== - Add the doctests to the long description. @@ -345,19 +355,19 @@ Changes - zope.schema now works on Python2.5 3.4.0 (2007-09-28) ------------------- +================== Add BeforeObjectAssignedEvent that is triggered before the object field sets a value. 3.3.0 (2007-03-15) ------------------- +================== Corresponds to the version of the zope.schema package shipped as part of the Zope 3.3.0 release. 3.2.1 (2006-03-26) ------------------- +================== Corresponds to the version of the zope.schema package shipped as part of the Zope 3.2.1 release. @@ -366,7 +376,7 @@ Fix missing import of 'VocabularyRegistryError'. See http://www.zope.org/Collectors/Zope3-dev/544 . 3.2.0 (2006-01-05) ------------------- +================== Corresponds to the version of the zope.schema package shipped as part of the Zope 3.2.0 release. @@ -375,7 +385,7 @@ Add "iterable" sources to replace vocabularies, which are now deprecated and scheduled for removal in Zope 3.3. 3.1.0 (2005-10-03) ------------------- +================== Corresponds to the version of the zope.schema package shipped as part of the Zope 3.1.0 release. @@ -386,7 +396,7 @@ argument (sources are a simpler implementation). Add 'TimeDelta' and 'ASCIILine' field types. 3.0.0 (2004-11-07) ------------------- +================== Corresponds to the version of the zope.schema package shipped as part of the Zope X3.0.0 release. diff --git a/README.rst b/README.rst index 06fe54c..5fe5acd 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,26 @@ -``zope.schema`` -=============== +============= + zope.schema +============= .. image:: https://img.shields.io/pypi/v/zope.schema.svg - :target: https://pypi.python.org/pypi/zope.schema/ - :alt: Latest Version + :target: https://pypi.org/project/zope.schema/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/zope.schema.svg + :target: https://pypi.org/project/zope.schema/ + :alt: Supported Python versions .. image:: https://travis-ci.org/zopefoundation/zope.schema.svg?branch=master - :target: https://travis-ci.org/zopefoundation/zope.schema + :target: https://travis-ci.org/zopefoundation/zope.schema + :alt: Build Status .. image:: https://readthedocs.org/projects/zopeschema/badge/?version=latest - :target: http://zopeschema.readthedocs.org/en/latest/ - :alt: Documentation Status + :target: https://zopeschema.readthedocs.org/en/latest/ + :alt: Documentation Status + +.. image:: https://coveralls.io/repos/github/zopefoundation/zope.schema/badge.svg + :target: https://coveralls.io/github/zopefoundation/zope.schema + :alt: Code Coverage Schemas extend the notion of interfaces to detailed descriptions of Attributes (but not methods). Every schema is an interface and diff --git a/setup.py b/setup.py index 60ab19a..e14fb6e 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ def read(*rnames): author_email='zope-dev@zope.org', long_description=(read('README.rst') + '\n\n' + read('CHANGES.rst')), packages=find_packages('src'), - package_dir = {'': 'src'}, + package_dir={'': 'src'}, namespace_packages=['zope', ], #extras_require={'docs': ['z3c.recipe.sphinxdoc']}, install_requires=REQUIRES, @@ -66,13 +66,14 @@ def read(*rnames): "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Framework :: Zope3", "Topic :: Software Development :: Libraries :: Python Modules", ], - include_package_data = True, - zip_safe = False, + include_package_data=True, + zip_safe=False, tests_require=TESTS_REQUIRE, extras_require={ 'docs': [ diff --git a/src/zope/schema/_bootstrapinterfaces.py b/src/zope/schema/_bootstrapinterfaces.py index acb9a23..60f265f 100644 --- a/src/zope/schema/_bootstrapinterfaces.py +++ b/src/zope/schema/_bootstrapinterfaces.py @@ -13,6 +13,8 @@ ############################################################################## """Bootstrap schema interfaces and exceptions """ +from functools import total_ordering + import zope.interface from zope.schema._messageid import _ @@ -26,27 +28,33 @@ class StopValidation(Exception): """ +@total_ordering class ValidationError(zope.interface.Invalid): """Raised if the Validation process fails.""" def doc(self): return self.__class__.__doc__ - def __cmp__(self, other): + def __lt__(self, other): + # There's no particular reason we choose to sort this way, + # it's just the way we used to do it with __cmp__. if not hasattr(other, 'args'): - return -1 - return cmp(self.args, other.args) + return True + return self.args < other.args def __eq__(self, other): if not hasattr(other, 'args'): return False return self.args == other.args + # XXX : This is probably inconsistent with __eq__, which is + # a violation of the language spec. __hash__ = zope.interface.Invalid.__hash__ # python3 def __repr__(self): # pragma: no cover - return '%s(%s)' % (self.__class__.__name__, - ', '.join(repr(arg) for arg in self.args)) + return '%s(%s)' % ( + self.__class__.__name__, + ', '.join(repr(arg) for arg in self.args)) class RequiredMissing(ValidationError): diff --git a/src/zope/schema/_field.py b/src/zope/schema/_field.py index 0d02e40..67d4210 100644 --- a/src/zope/schema/_field.py +++ b/src/zope/schema/_field.py @@ -371,13 +371,35 @@ def fromUnicode(self, value): r"$").match +class _StrippedNativeStringLine(NativeStringLine): + + _invalid_exc_type = None + + def fromUnicode(self, value): + v = value.strip() + # On Python 2, self._type is bytes, so we need to encode + # unicode down to ASCII bytes. On Python 3, self._type is + # unicode, but we don't want to allow non-ASCII values, to match + # Python 2 (our regexs would reject that anyway.) + try: + v = v.encode('ascii') # bytes + except UnicodeEncodeError: + raise self._invalid_exc_type(value) + if not isinstance(v, self._type): + v = v.decode('ascii') + self.validate(v) + return v + + @implementer(IDottedName) -class DottedName(NativeStringLine): +class DottedName(_StrippedNativeStringLine): """Dotted name field. Values of DottedName fields must be Python-style dotted names. """ + _invalid_exc_type = InvalidDottedName + def __init__(self, *args, **kw): self.min_dots = int(kw.pop("min_dots", 0)) if self.min_dots < 0: @@ -405,21 +427,17 @@ def _validate(self, value): raise InvalidDottedName("too many dots; no more than %d allowed" % self.max_dots, value) - def fromUnicode(self, value): - v = value.strip() - if not isinstance(v, self._type): - v = v.encode('ascii') - self.validate(v) - return v @implementer(IId, IFromUnicode) -class Id(NativeStringLine): +class Id(_StrippedNativeStringLine): """Id field Values of id fields must be either uris or dotted names. """ + _invalid_exc_type = InvalidId + def _validate(self, value): super(Id, self)._validate(value) if _isuri(value): @@ -429,15 +447,6 @@ def _validate(self, value): raise InvalidId(value) - def fromUnicode(self, value): - """ See IFromUnicode. - """ - v = value.strip() - if not isinstance(v, self._type): - v = v.encode('ascii') - self.validate(v) - return v - @implementer(IInterfaceField) class InterfaceField(Field): diff --git a/src/zope/schema/tests/__init__.py b/src/zope/schema/tests/__init__.py index 58ecb2c..b7d1a56 100644 --- a/src/zope/schema/tests/__init__.py +++ b/src/zope/schema/tests/__init__.py @@ -42,7 +42,7 @@ (re.compile(r"zope.schema._bootstrapinterfaces.WrongType:"), r"WrongType:"), ]) -else: +else: # pragma: no cover py3_checker = renormalizing.RENormalizing([ (re.compile(r"([^'])b'([^']*)'"), r"\1'\2'"), diff --git a/src/zope/schema/tests/test__bootstrapinterfaces.py b/src/zope/schema/tests/test__bootstrapinterfaces.py index 6d11334..73e4dd1 100644 --- a/src/zope/schema/tests/test__bootstrapinterfaces.py +++ b/src/zope/schema/tests/test__bootstrapinterfaces.py @@ -13,10 +13,11 @@ ############################################################################## import unittest - -def _skip_under_py3(testcase): - from zope.schema._compat import PY3 - return unittest.skipIf(PY3, "Not under Python 3")(testcase) +try: + compare = cmp +except NameError: + def compare(a, b): + return -1 if a < b else (0 if a == b else 1) class ValidationErrorTests(unittest.TestCase): @@ -33,20 +34,17 @@ class Derived(self._getTargetClass()): inst = Derived() self.assertEqual(inst.doc(), 'DERIVED') - @_skip_under_py3 def test___cmp___no_args(self): - # Py3k?? ve = self._makeOne() - self.assertEqual(cmp(ve, object()), -1) + self.assertEqual(compare(ve, object()), -1) + self.assertEqual(compare(object(), ve), 1) - @_skip_under_py3 def test___cmp___hit(self): - # Py3k?? left = self._makeOne('abc') right = self._makeOne('def') - self.assertEqual(cmp(left, right), -1) - self.assertEqual(cmp(left, left), 0) - self.assertEqual(cmp(right, left), 1) + self.assertEqual(compare(left, right), -1) + self.assertEqual(compare(left, left), 0) + self.assertEqual(compare(right, left), 1) def test___eq___no_args(self): ve = self._makeOne() diff --git a/src/zope/schema/tests/test__field.py b/src/zope/schema/tests/test__field.py index 7ce5ab4..09456fc 100644 --- a/src/zope/schema/tests/test__field.py +++ b/src/zope/schema/tests/test__field.py @@ -1152,7 +1152,6 @@ def test_validate_not_a_dotted_name(self): field.validate, 'http://example.com/\nDAV:') def test_fromUnicode_dotted_name_ok(self): - field = self._makeOne() self.assertEqual(field.fromUnicode(u'dotted.name'), 'dotted.name') @@ -1162,6 +1161,7 @@ def test_fromUnicode_invalid(self): field = self._makeOne() self.assertRaises(InvalidDottedName, field.fromUnicode, u'') + self.assertRaises(InvalidDottedName, field.fromUnicode, u'\u2603') self.assertRaises(ConstraintNotSatisfied, field.fromUnicode, u'http://example.com/\nDAV:') @@ -1240,6 +1240,7 @@ def test_fromUnicode_invalid(self): field = self._makeOne() self.assertRaises(InvalidId, field.fromUnicode, u'') self.assertRaises(InvalidId, field.fromUnicode, u'abc') + self.assertRaises(InvalidId, field.fromUnicode, u'\u2603') self.assertRaises(ConstraintNotSatisfied, field.fromUnicode, u'http://example.com/\nDAV:') diff --git a/tox.ini b/tox.ini index 043e2c0..8012429 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = # Jython support pending 2.7 support, due 2012-07-15 or so. See: # http://fwierzbicki.blogspot.com/2012/03/adconion-to-fund-jython-27.html # py27,jython,pypy,coverage - py27,py34,py35,py36,pypy,pypy3,coverage,docs + py27,py34,py35,py36,py37,pypy,pypy3,coverage,docs [testenv] deps = @@ -12,15 +12,12 @@ commands = zope-testrunner --test-path=src [testenv:coverage] +usedevelop = true basepython = - python2.7 + python3.6 commands = -# The installed version messes up nose's test discovery / coverage reporting -# So, we uninstall that from the environment, and then install the editable -# version, before running nosetests. - pip uninstall -y zope.schema - pip install -e . coverage run -m zope.testrunner --test-path=src + coverage report --fail-under=100 deps = .[test] coverage diff --git a/version.txt b/version.txt index ed70cbb..4643863 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.5.1.dev0 +4.6.0.dev0