diff options
-rw-r--r-- | .coveragerc | 12 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 30 | ||||
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | appveyor.yml | 2 | ||||
-rw-r--r-- | docs/api/checker.rst | 269 | ||||
-rw-r--r-- | docs/api/decorator.rst | 6 | ||||
-rw-r--r-- | docs/api/permission.rst | 32 | ||||
-rw-r--r-- | setup.py | 15 | ||||
-rw-r--r-- | src/zope/security/proxy.py | 7 | ||||
-rw-r--r-- | tox.ini | 29 |
12 files changed, 219 insertions, 193 deletions
diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..bb46267 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,12 @@ +[run] +source = zope.security + +[report] +precision = 2 +exclude_lines = + pragma: no cover + pragma NO COVER + if __name__ == '__main__': + raise NotImplementedError + self.fail + raise AssertionError @@ -15,3 +15,4 @@ parts nosetests.xml coverage.xml .eggs +htmlcov/ diff --git a/.travis.yml b/.travis.yml index c5b8cc2..c38e00c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,25 +2,25 @@ language: python sudo: false matrix: include: + - python: 2.7 + - python: 3.4 - python: 3.5 - env: TOXENV=py35 - python: 3.6 - env: TOXENV=py36 -env: - - TOXENV=py27 - - TOXENV=py27-pure - - TOXENV=py33 - - TOXENV=py33-pure - - TOXENV=py34 - - TOXENV=pypy - - TOXENV=coverage - - TOXENV=docs + - python: pypy + - python: pypy3 + - python: 2.7 + env: PURE_PYTHON=1 + - python: 3.4 + env: PURE_PYTHON=1 install: - - pip install -U pip - - pip install -U setuptools - - pip install -U tox + - pip install -U pip setuptools + - pip install -U coveralls coverage + - pip install -U -e .[test,docs] script: - - tox + - coverage run -m zope.testrunner --test-path=src + - coverage run -a -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest +after_success: + - coveralls notifications: email: false cache: pip diff --git a/CHANGES.rst b/CHANGES.rst index 1533b06..c264be8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,11 @@ Changes - Fix ``__length_hint__`` of proxied iterator objects. Previously it was ignored. +- Drop support for Python 3.3. + +- Enable coveralls.io for coverage measurement and run doctests on all + supported Python versions. + 4.1.1 (2017-05-17) ------------------ @@ -12,5 +12,9 @@ :target: http://zopesecurity.readthedocs.org/en/latest/ :alt: Documentation Status +.. image:: https://coveralls.io/repos/github/zopefoundation/zope.security/badge.svg?branch=master + :target: https://coveralls.io/github/zopefoundation/zope.security?branch=master + + The Security framework provides a generic mechanism to implement security policies on Python objects. diff --git a/appveyor.yml b/appveyor.yml index 567e1a5..bfa97eb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,8 +3,6 @@ environment: matrix: - python : 27 - python : 27-x64 - - python : 33 - - python : 33-x64 - python : 34 - python : 34-x64 - python : 35 diff --git a/docs/api/checker.rst b/docs/api/checker.rst index 58d3595..419fe5e 100644 --- a/docs/api/checker.rst +++ b/docs/api/checker.rst @@ -45,7 +45,7 @@ name and permission: >>> cdict = checker.get_permissions >>> pprint(cdict) {'foo': 'zope.security.metaconfigure.test'} - + If we define additional names, they will be added to the dict: >>> protectModule(test_zcml_functest, 'bar', TEST_PERM) @@ -54,7 +54,7 @@ name and permission: {'bar': 'zope.security.metaconfigure.test', 'baz': 'zope.security.metaconfigure.test', 'foo': 'zope.security.metaconfigure.test'} - + The allow directive creates actions for each named defined directly, or via interface: @@ -209,43 +209,43 @@ Rocks are immuatle, non-callable objects without interesting methods. They .. doctest:: - >>> int(type(ProxyFactory( object() )) is object) - 1 - >>> int(type(ProxyFactory( 1 )) is int) - 1 - >>> int(type(ProxyFactory( 1.0 )) is float) - 1 - >>> int(type(ProxyFactory( 1j )) is complex) - 1 - >>> int(type(ProxyFactory( None )) is type(None)) - 1 - >>> int(type(ProxyFactory( 'xxx' )) is str) - 1 - >>> int(type(ProxyFactory( True )) is type(True)) - 1 + >>> type(ProxyFactory( object() )) is object + True + >>> type(ProxyFactory( 1 )) is int + True + >>> type(ProxyFactory( 1.0 )) is float + True + >>> type(ProxyFactory( 1j )) is complex + True + >>> type(ProxyFactory( None )) is type(None) + True + >>> type(ProxyFactory( 'xxx' )) is str + True + >>> type(ProxyFactory( True )) is type(True) + True Datetime-reltatd instances are rocks, too: .. doctest:: >>> from datetime import timedelta, datetime, date, time, tzinfo - >>> int(type(ProxyFactory( timedelta(1) )) is timedelta) - 1 - >>> int(type(ProxyFactory( datetime(2000, 1, 1) )) is datetime) - 1 - >>> int(type(ProxyFactory( date(2000, 1, 1) )) is date) - 1 - >>> int(type(ProxyFactory( time() )) is time) - 1 - >>> int(type(ProxyFactory( tzinfo() )) is tzinfo) - 1 + >>> type(ProxyFactory( timedelta(1) )) is timedelta + True + >>> type(ProxyFactory( datetime(2000, 1, 1) )) is datetime + True + >>> type(ProxyFactory( date(2000, 1, 1) )) is date + True + >>> type(ProxyFactory( time() )) is time + True + >>> type(ProxyFactory( tzinfo() )) is tzinfo + True >>> try: ... from pytz import UTC ... except ImportError: # pytz checker only if pytz is present. - ... 1 + ... True ... else: - ... int(type(ProxyFactory( UTC )) is type(UTC)) - 1 + ... type(ProxyFactory( UTC )) is type(UTC) + True dicts @@ -268,15 +268,15 @@ We can do everything we expect to be able to do with proxied dicts. ['a', 'b'] >>> d.get('a') 1 - >>> int('a' in d) - 1 + >>> 'a' in d + True >>> c = d.copy() >>> check_forbidden_get(c, 'clear') 'ForbiddenAttribute: clear' - >>> int(str(c) in ("{'a': 1, 'b': 2}", "{'b': 2, 'a': 1}")) - 1 - >>> int(repr(c) in ("{'a': 1, 'b': 2}", "{'b': 2, 'a': 1}")) - 1 + >>> str(c) in ("{'a': 1, 'b': 2}", "{'b': 2, 'a': 1}") + True + >>> repr(c) in ("{'a': 1, 'b': 2}", "{'b': 2, 'a': 1}") + True >>> def sorted(x): ... x = list(x) ... x.sort() @@ -293,12 +293,12 @@ not checking that under python > 2): .. doctest:: - >>> int(d != d) - 0 - >>> int(bool(d)) - 1 - >>> int(d.__class__ == dict) - 1 + >>> d != d + False + >>> bool(d) + True + >>> d.__class__ == dict + True lists ##### @@ -322,8 +322,8 @@ We can do everything we expect to be able to do with proxied lists. 2 >>> tuple(l) (1, 2) - >>> int(1 in l) - 1 + >>> 1 in l + True >>> l.index(2) 1 >>> l.count(2) @@ -339,22 +339,22 @@ Always available: .. doctest:: - >>> int(l < l) - 0 - >>> int(l > l) - 0 - >>> int(l <= l) - 1 - >>> int(l >= l) - 1 - >>> int(l == l) - 1 - >>> int(l != l) - 0 - >>> int(bool(l)) - 1 - >>> int(l.__class__ == list) - 1 + >>> l < l + False + >>> l > l + False + >>> l <= l + True + >>> l >= l + True + >>> l == l + True + >>> l != l + False + >>> bool(l) + True + >>> l.__class__ == list + True tuples ###### @@ -373,8 +373,8 @@ We can do everything we expect to be able to do with proxied tuples. 2 >>> list(l) [1, 2] - >>> int(1 in l) - 1 + >>> 1 in l + True >>> str(l) '(1, 2)' >>> repr(l) @@ -386,22 +386,22 @@ Always available: .. doctest:: - >>> int(l < l) - 0 - >>> int(l > l) - 0 - >>> int(l <= l) - 1 - >>> int(l >= l) - 1 - >>> int(l == l) - 1 - >>> int(l != l) - 0 - >>> int(bool(l)) - 1 - >>> int(l.__class__ == tuple) - 1 + >>> l < l + False + >>> l > l + False + >>> l <= l + True + >>> l >= l + True + >>> l == l + True + >>> l != l + False + >>> bool(l) + True + >>> l.__class__ == tuple + True sets #### @@ -543,7 +543,7 @@ we can do everything we expect to be able to do with proxied sets. >>> str(s) == str(us) True - + >>> repr(s) == repr(us) True @@ -565,7 +565,7 @@ we can do everything we expect to be able to do with proxied sets. Note that you can't compare proxied sets with other proxied sets due a limitaion in the set comparison functions which won't work with any kind of proxy. - + .. doctest:: >>> bool(s) @@ -723,7 +723,7 @@ we can do everything we expect to be able to do with proxied frozensets. >>> str(s) == str(us) True - + >>> repr(s) == repr(us) True @@ -745,7 +745,7 @@ we can do everything we expect to be able to do with proxied frozensets. Note that you can't compare proxied sets with other proxied sets due a limitaion in the frozenset comparison functions which won't work with any kind of proxy. - + .. doctest:: >>> bool(s) @@ -838,8 +838,8 @@ New-style classes 'ForbiddenAttribute: __dict__' >>> s = str(C) >>> s = repr(C) - >>> int(C.__module__ == __name__) - 1 + >>> C.__module__ == __name__ + True >>> len(C.__bases__) 1 >>> len(C.__mro__) @@ -849,14 +849,14 @@ Always available: .. doctest:: - >>> int(C == C) - 1 - >>> int(C != C) - 0 - >>> int(bool(C)) - 1 - >>> int(C.__class__ == type) - 1 + >>> C == C + True + >>> C != C + False + >>> bool(C) + True + >>> C.__class__ == type + True New-style Instances ################### @@ -873,21 +873,21 @@ New-style Instances 'ForbiddenAttribute: z' >>> c.x 1 - >>> int(c.__class__ == C) - 1 + >>> c.__class__ == C + True Always available: .. doctest:: - >>> int(c == c) - 1 - >>> int(c != c) - 0 - >>> int(bool(c)) - 1 - >>> int(c.__class__ == C) - 1 + >>> c == c + True + >>> c != c + False + >>> bool(c) + True + >>> c.__class__ == C + True Classic Classes @@ -904,21 +904,25 @@ Classic Classes 'ForbiddenAttribute: __dict__' >>> s = str(C) >>> s = repr(C) - >>> int(C.__module__ == __name__) - 1 - >>> len(C.__bases__) - 1 + >>> C.__module__ == __name__ + True + +Note that these are really only classic on Python 2: + + >>> import sys + >>> len(C.__bases__) == (0 if sys.version_info[0] == 2 else 1) + True Always available: .. doctest:: - >>> int(C == C) - 1 - >>> int(C != C) - 0 - >>> int(bool(C)) - 1 + >>> C == C + True + >>> C != C + False + >>> bool(C) + True Classic Instances ################# @@ -934,21 +938,21 @@ Classic Instances 'ForbiddenAttribute: z' >>> c.x 1 - >>> int(c.__class__ == C) - 1 + >>> c.__class__ == C + True Always available: .. doctest:: - >>> int(c == c) - 1 - >>> int(c != c) - 0 - >>> int(bool(c)) - 1 - >>> int(c.__class__ == C) - 1 + >>> c == c + True + >>> c != c + False + >>> bool(c) + True + >>> c.__class__ == C + True Interfaces and declarations ########################### @@ -992,8 +996,7 @@ We work with the ABCMeta meta class: .. doctest:: >>> import abc - >>> class MyABC: - ... __metaclass__ = abc.ABCMeta + >>> MyABC = abc.ABCMeta('MyABC', (object,), {}) >>> class Foo(MyABC): pass >>> class Bar(Foo): pass >>> PBar = ProxyFactory(Bar) @@ -1005,8 +1008,8 @@ We work with the ABCMeta meta class: 'ForbiddenAttribute: __dict__' >>> s = str(PBar) >>> s = repr(PBar) - >>> int(PBar.__module__ == __name__) - 1 + >>> PBar.__module__ == __name__ + True >>> len(PBar.__bases__) 1 @@ -1014,11 +1017,11 @@ Always available: .. doctest:: - >>> int(PBar == PBar) - 1 - >>> int(PBar != PBar) - 0 - >>> int(bool(PBar)) - 1 - >>> int(PBar.__class__ == type) - 1 + >>> PBar == PBar + True + >>> PBar != PBar + False + >>> bool(PBar) + True + >>> PBar.__class__ == type + False diff --git a/docs/api/decorator.rst b/docs/api/decorator.rst index c05c692..59a02bd 100644 --- a/docs/api/decorator.rst +++ b/docs/api/decorator.rst @@ -57,7 +57,7 @@ Using `selectChecker()`, we can confirm that a `Foo` object uses ... fooChecker.check(foo, 'b') # doctest: +ELLIPSIS ... except ForbiddenAttribute as e: ... e - ForbiddenAttribute('b', <Foo object ...>) + ForbiddenAttribute('b', <...Foo object ...>) and that a `Wrapper` object uses `wrappeChecker`: @@ -71,7 +71,7 @@ and that a `Wrapper` object uses `wrappeChecker`: ... wrapperChecker.check(wrapper, 'a') # doctest: +ELLIPSIS ... except ForbiddenAttribute as e: ... e - ForbiddenAttribute('a', <Foo object ...>) + ForbiddenAttribute('a', <...Foo object ...>) (Note that the object description says `Foo` because the object is a proxy and generally looks and acts like the object it's proxying.) @@ -103,7 +103,7 @@ illustrate, we'll proxify `foo`: ... secure_foo.b # doctest: +ELLIPSIS ... except ForbiddenAttribute as e: ... e - ForbiddenAttribute('b', <Foo object ...>) + ForbiddenAttribute('b', <...Foo object ...>) when we wrap the secured `foo`: diff --git a/docs/api/permission.rst b/docs/api/permission.rst index 819cc90..268f8f6 100644 --- a/docs/api/permission.rst +++ b/docs/api/permission.rst @@ -27,11 +27,11 @@ Traceback (most recent call last): ... ValueError: ('Undefined permission id', 'y') - + The :data:`zope.security.checker.CheckerPublic` permission always exists: .. doctest:: - + >>> from zope.security.checker import CheckerPublic >>> checkPermission(None, CheckerPublic) @@ -45,10 +45,11 @@ The :data:`zope.security.checker.CheckerPublic` permission always exists: >>> y = Permission('y') >>> provideUtility(y, IPermission, 'y') - >>> ids = list(allPermissions(None)) - >>> ids.sort() - >>> ids - ['x', 'y'] + >>> ids = sorted(allPermissions(None)) + >>> for perm in sorted(allPermissions(None)): + ... print(perm) + x + y .. autofunction:: zope.security.permission.PermissionsVocabulary @@ -99,10 +100,10 @@ The non-public permissions 'x' and 'y' are string values: .. doctest:: - >>> vocab.getTermByToken('x').value - 'x' - >>> vocab.getTermByToken('y').value - 'y' + >>> print(vocab.getTermByToken('x').value) + x + >>> print(vocab.getTermByToken('y').value) + y However, the public permission value is CheckerPublic: @@ -115,16 +116,19 @@ and its title is shortened: .. doctest:: - >>> vocab.getTermByToken('zope.Public').title - 'Public' + >>> print(vocab.getTermByToken('zope.Public').title) + Public The terms are sorted by title except for the public permission, which is listed first: .. doctest:: - >>> [term.title for term in vocab] - ['Public', 'x', 'y'] + >>> for term in vocab: + ... print(term.title) + Public + x + y .. testcleanup:: @@ -102,7 +102,7 @@ else: ), Extension("zope.security._zope_security_checker", [os.path.join('src', 'zope', 'security', - "_zope_security_checker.c")] + "_zope_security_checker.c")] ), ] @@ -126,7 +126,6 @@ setup(name='zope.security', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -140,7 +139,7 @@ setup(name='zope.security', url='http://pypi.python.org/pypi/zope.security', license='ZPL 2.1', packages=find_packages('src'), - package_dir = {'': 'src'}, + package_dir={'': 'src'}, namespace_packages=['zope'], setup_requires=setup_requires, ext_modules=ext_modules, @@ -153,9 +152,9 @@ setup(name='zope.security', 'zope.proxy >= 4.1.0', 'zope.schema', ], - test_suite = '__main__.alltests', + test_suite='__main__.alltests', tests_require=TESTS_REQUIRE, - extras_require = dict( + extras_require=dict( pytz=["pytz"], untrustedpython=['zope.untrustedpython'] if not py3 else [], zcml=['zope.configuration'], @@ -163,6 +162,6 @@ setup(name='zope.security', testing=TESTS_REQUIRE + ['nose', 'coverage'], docs=['Sphinx', 'repoze.sphinx.autointerface'], ), - include_package_data = True, - zip_safe = False, - ) + include_package_data=True, + zip_safe=False, +) diff --git a/src/zope/security/proxy.py b/src/zope/security/proxy.py index 77a82d4..302257a 100644 --- a/src/zope/security/proxy.py +++ b/src/zope/security/proxy.py @@ -202,7 +202,12 @@ class ProxyPy(PyProxyBase): def __length_hint__(self): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') - return wrapped.__length_hint__() + try: + hint = wrapped.__length_hint__ + except AttributeError: + return NotImplemented + else: + return hint() def __coerce__(self, other): # For some reason _check_name does not work for coerce() @@ -3,13 +3,14 @@ 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,pypy,jython,py33,coverage,docs - py27,py27-pure,pypy,py33,py33-pure,py34,py35,py36,coverage,docs + py27,py27-pure,pypy,py34-pure,py34,py35,py36,coverage,docs [testenv] -deps = - .[test] commands = - zope-testrunner --test-path=src --auto-progress --auto-color [] + zope-testrunner --test-path=src [] + sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest +deps = + .[test,docs] [testenv:py27-pure] basepython = @@ -18,28 +19,24 @@ setenv = PURE_PYTHON = 1 PIP_CACHE_DIR = {envdir}/.cache -[testenv:py33-pure] +[testenv:py34-pure] basepython = - python3.3 + python3.4 setenv = PURE_PYTHON = 1 PIP_CACHE_DIR = {envdir}/.cache [testenv:coverage] +usedevelop = true basepython = python2.7 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.security - python -c "import shutil; shutil.copyfile('src/zope/__init__.py', '{envdir}/lib/python2.7/site-packages/zope/__init__.py')" - pip install -e . - nosetests --with-xunit --with-xcoverage + coverage run -m zope.testrunner --test-path=src [] + coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest + coverage report --fail-under=92 deps = - .[testing] + {[testenv]deps} coverage - nosexcover [testenv:docs] basepython = @@ -47,5 +44,3 @@ basepython = 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 = - .[docs,test] |