././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2328384 venusian-3.0.0/0000755000076600000240000000000000000000000014311 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0 venusian-3.0.0/.coveragerc0000644000076600000240000000025300000000000016432 0ustar00xistencestaff00000000000000[run] parallel = true source = venusian [paths] source = src/venusian */src/venusian */site-packages/venusian [report] show_missing = true precision = 2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0 venusian-3.0.0/.travis.yml0000644000076600000240000000133600000000000016425 0ustar00xistencestaff00000000000000# Wire up travis language: python sudo: false matrix: include: - python: 3.5 env: TOXENV=docs - python: 3.8 env: TOXENV=lint dist: xenial sudo: true - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 dist: xenial sudo: true - python: 3.7 env: TOXENV=py37 dist: xenial sudo: true - python: 3.8 env: TOXENV=py38 dist: xenial sudo: true install: - travis_retry pip install tox script: - travis_retry tox notifications: email: - pyramid-checkins@lists.repoze.org irc: channels: - "chat.freenode.net#pyramid" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983311.0 venusian-3.0.0/CHANGES.rst0000644000076600000240000001722200000000000016117 0ustar00xistencestaff000000000000003.0.0 (2019-10-04) ------------------ - This release matches 2.0.0 other than in the version number. This fixes an issue with Requires-Python metadata not being uploaded correctly to PyPi. This version is only compatible with Python 3.5+ 2.0.0 (2019-10-04) ------------------ - Drop support for Python 2.7, 3.3, and 3.4 - Removed the usage of the ``imp`` module to squelch the warnings regarding a deprecated modules. See https://github.com/Pylons/venusian/pull/63 and https://github.com/Pylons/venusian/issues/57 1.2.0 (2019-01-08) ------------------ - Add support for Python 3.7. - Drop support for Python 3.3. 1.1.0 (2017-04-24) ------------------ - Updated to using py.test instead of nosetest, and added support for Python 3.4 -> 3.6 - Make scanning more resilient of metaclasses that return proxies for any attribute access. - Fix bug where using the same venusian decorator on both a class and its methods would cause the method decorations to be ignored. See https://github.com/Pylons/venusian/issues/40 - Drop support for Python 2.6. - Drop support for Python 3.2: it is no longer supported by current packaging / CI tools. - Support loaders that require the module name as argument to their ``get_filename()`` method. This fixes problems with zipped packages on Python 3. - Micro-optimization when ignores are used (see https://github.com/Pylons/venusian/pull/20). - A tox run now combines coverage between Py2 and Py3. 1.0 (2014-06-30) ---------------- - Fix an issue under PyPy > 2.0 where attached decorators may not be found. - Drop support of Python 2.4 / 2.5 / Jython. - Add ``lift`` and ``onlyliftedfrom`` class decorators to allow for inheritance of venusian decorators attached to superclass methods. See the API documentation for more information. - Fix bug where otherwise undecorated subclass of a superclass that had venusian decorators on it would inherit its superclass' decorations. Venusian decorators should have never been inherited implicitly. See https://github.com/Pylons/venusian/issues/11#issuecomment-4977352 1.0a8 (2013-04-15) ------------------ - Pass ``ignore`` argument along recursively to ``walk_packages`` so custom ignore functions will ignore things recursively. See https://github.com/Pylons/venusian/pull/16 - Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4). 1.0a7 (2012-08-25) ------------------ - Venusian now works on Python 3.3b2+ (importlib-based). - Use nose-exclude instead of relying on fragile module-scope code to ensure we don't get errors resulting from import of fixture code during "nosetests". - Bug fix: no longer suppress ``ImportError`` while scanning by default. If you want to suppress ``ImportError`` while scanning, you'll now need use an ``onerror`` callback as described in the documentation. 1.0a6 (2012-04-23) ------------------ - Don't ignore decorated objects within their original locations if they happen to be imported into another module (remove ``seen`` set from invoke in venusian scanning). See https://github.com/Pylons/venusian/pull/13 . 1.0a5 (2012-04-21) ------------------ - Slightly less sucky way to ignore objects during scanning that are only imported into a module but not actually defined there. See 1.0a4 change notes for rationale. Now instead of checking whether the module of the *scanned object* matches the module being scanned, we check whether the module of the *Venusian attachment* matches the module being scanned. This allows some genuine uses of imported objects as Venusian scan targets while preventing inappropriate double-scanning of objects that have a venusian attachment which just happen to be imported into other scanned modules. - Add ``dev`` and ``docs`` setup.py commands (ala Pyramid). 1.0a4 (2012-04-16) ------------------ - Attempt to ignore objects during scanning that are only imported into a module but not actually defined there. This is a semantics change, but it's the right thing to do, because I found myself facing a situation like this:: # in a module named "one" from two import anotheradecoratedthing @adecorator def adecoratedthing(): pass # and scanning both modules scan('one') scan('two') In this case you'd wind up with two repeated registrations for "anotherdecoratedthing", which isn't what anyone expects. 1.0a3 (2012-02-08) ------------------ - Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``. This argument allows a user to ignore packages, modules, and global objects by name during a ``scan``. See the "ignore Scan Argument" in the narrative documentation for more details. 1.0a2 (2011-09-02) ------------------ - Close ImpLoader file handle to avoid resource warnings on Python 3. 1.0a1 (2011-08-27) ------------------ - Python 3 compatibility. - Allow an ``onerror`` callback to be passed to ``Scanner.scan()``. 0.9 (2011-06-18) ---------------- - Prevent corner case scan-time exception when trying to introspect insane module-scope objects. See https://github.com/Pylons/venusian/issues/5 . 0.8 (2011-04-30) ---------------- - Normal "setup.py test" can't support running the venusian tests under py 2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs. To get around this, we used to depend on ``nose`` in ``setup_requires`` and tell "setup.py test" to use nose by setting test_suite to "nose.collector" but we can't anymore because folks use Venusian in systems which install from pip bundles; pip bundles do not support setup_requires. So, sorry, we're painted into a corner; at this point you just have to know to install nose and run "setup.py nosetests" rather than "setup.py test". Or just run "tox" which tests it under all Pythons. 0.7 (2011-03-16) ---------------- - Use Pylons theme in documentation. - Fix orphaned pyc test on pypy. - Fix GitHub Issue #1: subclasses of decorated classes that do not have any decorations should not inherit the decorations of their parent classes. - Fix GitHub Issue #2: scans should only "find" each object once per scan, regardless of how many modules that object is imported into. 0.6 (2011-01-09) ---------------- - Some metaclasses (Elixir's) don't raise an AttributeError when asked for a nonexistent attribute during a scan. We now catch all exceptions when interrogating an object for ``__venusian_callbacks__`` rather than just AttributeError. 0.5 (2010-12-19) ---------------- - Make ``codeinfo`` attribute available as an attribute of the AttachInfo object. It will be a tuple in the form ``(filename, lineno, function, sourceline)`` representing the context of the venusian decorator. Eg. ``('/home/chrism/projects/venusian/tests/test_advice.py', 81, 'testCallInfo', 'add_handler(foo, bar)')`` 0.4 (2010-09-03) ---------------- - Bug fix: when a venusian decorator used as a class decorator was used against both a class *and* a subclass of that class, the superclass and subclass would effectively share the same set of callbacks. This was not the intent: each class declaration should have its own local set of callbacks; callbacks added via decorations should not be inherited, and a superclass should not receive its subclass' decorations. - Arrange test fixtures into a single directory. 0.3 (2010-06-24) ---------------- - Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a corresponding ``.py`` file) during a scan. 0.2 (2010-04-18) ---------------- - Add the concept of scan categories (see the "Scan Categories" section of the documentation) to allow an application to make use of more than one Venusian-using framework simultaneously. 0.1 (2010-02-15) ---------------- - Initial release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/CONTRIBUTORS.txt0000644000076600000240000001131600000000000017011 0ustar00xistencestaff00000000000000Pylons Project Contributor Agreement ==================================== The submitter agrees by adding his or her name within the section below named "Contributors" and submitting the resulting modified document to the canonical shared repository location for this software project (whether directly, as a user with "direct commit access", or via a "pull request"), he or she is signing a contract electronically. The submitter becomes a Contributor after a) he or she signs this document by adding their name beneath the "Contributors" section below, and b) the resulting document is accepted into the canonical version control repository. Treatment of Account --------------------- Contributor will not allow anyone other than the Contributor to use his or her username or source repository login to submit code to a Pylons Project source repository. Should Contributor become aware of any such use, Contributor will immediately by notifying Agendaless Consulting. Notification must be performed by sending an email to webmaster@agendaless.com. Until such notice is received, Contributor will be presumed to have taken all actions made through Contributor's account. If the Contributor has direct commit access, Agendaless Consulting will have complete control and discretion over capabilities assigned to Contributor's account, and may disable Contributor's account for any reason at any time. Legal Effect of Contribution ---------------------------- Upon submitting a change or new work to a Pylons Project source Repository (a "Contribution"), you agree to assign, and hereby do assign, a one-half interest of all right, title and interest in and to copyright and other intellectual property rights with respect to your new and original portions of the Contribution to Agendaless Consulting. You and Agendaless Consulting each agree that the other shall be free to exercise any and all exclusive rights in and to the Contribution, without accounting to one another, including without limitation, the right to license the Contribution to others under the Repoze Public License. This agreement shall run with title to the Contribution. Agendaless Consulting does not convey to you any right, title or interest in or to the Program or such portions of the Contribution that were taken from the Program. Your transmission of a submission to the Pylons Project source Repository and marks of identification concerning the Contribution itself constitute your intent to contribute and your assignment of the work in accordance with the provisions of this Agreement. License Terms ------------- Code committed to the Pylons Project source repository (Committed Code) must be governed by the Repoze Public License (http://repoze.org/LICENSE.txt, aka "the RPL") or another license acceptable to Agendaless Consulting. Until Agendaless Consulting declares in writing an acceptable license other than the RPL, only the RPL shall be used. A list of exceptions is detailed within the "Licensing Exceptions" section of this document, if one exists. Representations, Warranty, and Indemnification ---------------------------------------------- Contributor represents and warrants that the Committed Code does not violate the rights of any person or entity, and that the Contributor has legal authority to enter into this Agreement and legal authority over Contributed Code. Further, Contributor indemnifies Agendaless Consulting against violations. Cryptography ------------ Contributor understands that cryptographic code may be subject to government regulations with which Agendaless Consulting and/or entities using Committed Code must comply. Any code which contains any of the items listed below must not be checked-in until Agendaless Consulting staff has been notified and has approved such contribution in writing. - Cryptographic capabilities or features - Calls to cryptographic features - User interface elements which provide context relating to cryptography - Code which may, under casual inspection, appear to be cryptographic. Notices ------- Contributor confirms that any notices required will be included in any Committed Code. Licensing Exceptions ==================== None. List of Contributors ==================== The below-signed are contributors to a code repository that is part of the project named "Venusian". Each below-signed contributor has read, understand and agrees to the terms above in the section within this document entitled "Pylons Project Contributor Agreement" as of the date beside his or her name. Contributors ------------ - Chris McDonough, 2011/02/16 - Chris Withers, 2011/03/14 - Joel Bohman, 2011/07/28 - Olaf Conradi, 2013/09/16 - Wichert Akkerman, 2015/02/23 - Marc Abramowitz, 2015/02/24 - Bert JW Regeer, 2017-04-24 - Steve Piercy, 2017-08-31 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/COPYRIGHT.txt0000644000076600000240000000015500000000000016423 0ustar00xistencestaff00000000000000Copyright (c) 2011 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/LICENSE.txt0000644000076600000240000000337700000000000016146 0ustar00xistencestaff00000000000000License A copyright notice accompanies this license document that identifies the copyright holders. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0 venusian-3.0.0/MANIFEST.in0000644000076600000240000000053000000000000016045 0ustar00xistencestaff00000000000000graft src/venusian graft tests graft docs prune docs/_build prune docs/_themes include README.rst include CHANGES.rst include LICENSE.txt include CONTRIBUTING.rst include CONTRIBUTORS.txt include COPYRIGHT.txt include pyproject.toml setup.cfg include .coveragerc include tox.ini .travis.yml rtd.txt recursive-exclude * __pycache__ *.py[cod] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2335327 venusian-3.0.0/PKG-INFO0000644000076600000240000002631500000000000015415 0ustar00xistencestaff00000000000000Metadata-Version: 2.1 Name: venusian Version: 3.0.0 Summary: A library for deferring decorator actions Home-page: https://pylonsproject.org/ Author: Chris McDonough, Agendaless Consulting Author-email: pylons-devel@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: venusian ======== .. image:: https://travis-ci.org/Pylons/venusian.png?branch=master :target: https://travis-ci.org/Pylons/venusian .. image:: https://readthedocs.org/projects/venusian/badge/?version=latest :target: https://docs.pylonsproject.org/projects/venusian/en/latest/ :alt: Documentation Status Venusian is a library which allows framework authors to defer decorator actions. Instead of taking actions when a function (or class) decorator is executed at import time, you can defer the action usually taken by the decorator until a separate "scan" phase. See the "docs" directory of the package or the online documentation at https://docs.pylonsproject.org/projects/venusian/en/latest/ 3.0.0 (2019-10-04) ------------------ - This release matches 2.0.0 other than in the version number. This fixes an issue with Requires-Python metadata not being uploaded correctly to PyPi. This version is only compatible with Python 3.5+ 2.0.0 (2019-10-04) ------------------ - Drop support for Python 2.7, 3.3, and 3.4 - Removed the usage of the ``imp`` module to squelch the warnings regarding a deprecated modules. See https://github.com/Pylons/venusian/pull/63 and https://github.com/Pylons/venusian/issues/57 1.2.0 (2019-01-08) ------------------ - Add support for Python 3.7. - Drop support for Python 3.3. 1.1.0 (2017-04-24) ------------------ - Updated to using py.test instead of nosetest, and added support for Python 3.4 -> 3.6 - Make scanning more resilient of metaclasses that return proxies for any attribute access. - Fix bug where using the same venusian decorator on both a class and its methods would cause the method decorations to be ignored. See https://github.com/Pylons/venusian/issues/40 - Drop support for Python 2.6. - Drop support for Python 3.2: it is no longer supported by current packaging / CI tools. - Support loaders that require the module name as argument to their ``get_filename()`` method. This fixes problems with zipped packages on Python 3. - Micro-optimization when ignores are used (see https://github.com/Pylons/venusian/pull/20). - A tox run now combines coverage between Py2 and Py3. 1.0 (2014-06-30) ---------------- - Fix an issue under PyPy > 2.0 where attached decorators may not be found. - Drop support of Python 2.4 / 2.5 / Jython. - Add ``lift`` and ``onlyliftedfrom`` class decorators to allow for inheritance of venusian decorators attached to superclass methods. See the API documentation for more information. - Fix bug where otherwise undecorated subclass of a superclass that had venusian decorators on it would inherit its superclass' decorations. Venusian decorators should have never been inherited implicitly. See https://github.com/Pylons/venusian/issues/11#issuecomment-4977352 1.0a8 (2013-04-15) ------------------ - Pass ``ignore`` argument along recursively to ``walk_packages`` so custom ignore functions will ignore things recursively. See https://github.com/Pylons/venusian/pull/16 - Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4). 1.0a7 (2012-08-25) ------------------ - Venusian now works on Python 3.3b2+ (importlib-based). - Use nose-exclude instead of relying on fragile module-scope code to ensure we don't get errors resulting from import of fixture code during "nosetests". - Bug fix: no longer suppress ``ImportError`` while scanning by default. If you want to suppress ``ImportError`` while scanning, you'll now need use an ``onerror`` callback as described in the documentation. 1.0a6 (2012-04-23) ------------------ - Don't ignore decorated objects within their original locations if they happen to be imported into another module (remove ``seen`` set from invoke in venusian scanning). See https://github.com/Pylons/venusian/pull/13 . 1.0a5 (2012-04-21) ------------------ - Slightly less sucky way to ignore objects during scanning that are only imported into a module but not actually defined there. See 1.0a4 change notes for rationale. Now instead of checking whether the module of the *scanned object* matches the module being scanned, we check whether the module of the *Venusian attachment* matches the module being scanned. This allows some genuine uses of imported objects as Venusian scan targets while preventing inappropriate double-scanning of objects that have a venusian attachment which just happen to be imported into other scanned modules. - Add ``dev`` and ``docs`` setup.py commands (ala Pyramid). 1.0a4 (2012-04-16) ------------------ - Attempt to ignore objects during scanning that are only imported into a module but not actually defined there. This is a semantics change, but it's the right thing to do, because I found myself facing a situation like this:: # in a module named "one" from two import anotheradecoratedthing @adecorator def adecoratedthing(): pass # and scanning both modules scan('one') scan('two') In this case you'd wind up with two repeated registrations for "anotherdecoratedthing", which isn't what anyone expects. 1.0a3 (2012-02-08) ------------------ - Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``. This argument allows a user to ignore packages, modules, and global objects by name during a ``scan``. See the "ignore Scan Argument" in the narrative documentation for more details. 1.0a2 (2011-09-02) ------------------ - Close ImpLoader file handle to avoid resource warnings on Python 3. 1.0a1 (2011-08-27) ------------------ - Python 3 compatibility. - Allow an ``onerror`` callback to be passed to ``Scanner.scan()``. 0.9 (2011-06-18) ---------------- - Prevent corner case scan-time exception when trying to introspect insane module-scope objects. See https://github.com/Pylons/venusian/issues/5 . 0.8 (2011-04-30) ---------------- - Normal "setup.py test" can't support running the venusian tests under py 2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs. To get around this, we used to depend on ``nose`` in ``setup_requires`` and tell "setup.py test" to use nose by setting test_suite to "nose.collector" but we can't anymore because folks use Venusian in systems which install from pip bundles; pip bundles do not support setup_requires. So, sorry, we're painted into a corner; at this point you just have to know to install nose and run "setup.py nosetests" rather than "setup.py test". Or just run "tox" which tests it under all Pythons. 0.7 (2011-03-16) ---------------- - Use Pylons theme in documentation. - Fix orphaned pyc test on pypy. - Fix GitHub Issue #1: subclasses of decorated classes that do not have any decorations should not inherit the decorations of their parent classes. - Fix GitHub Issue #2: scans should only "find" each object once per scan, regardless of how many modules that object is imported into. 0.6 (2011-01-09) ---------------- - Some metaclasses (Elixir's) don't raise an AttributeError when asked for a nonexistent attribute during a scan. We now catch all exceptions when interrogating an object for ``__venusian_callbacks__`` rather than just AttributeError. 0.5 (2010-12-19) ---------------- - Make ``codeinfo`` attribute available as an attribute of the AttachInfo object. It will be a tuple in the form ``(filename, lineno, function, sourceline)`` representing the context of the venusian decorator. Eg. ``('/home/chrism/projects/venusian/tests/test_advice.py', 81, 'testCallInfo', 'add_handler(foo, bar)')`` 0.4 (2010-09-03) ---------------- - Bug fix: when a venusian decorator used as a class decorator was used against both a class *and* a subclass of that class, the superclass and subclass would effectively share the same set of callbacks. This was not the intent: each class declaration should have its own local set of callbacks; callbacks added via decorations should not be inherited, and a superclass should not receive its subclass' decorations. - Arrange test fixtures into a single directory. 0.3 (2010-06-24) ---------------- - Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a corresponding ``.py`` file) during a scan. 0.2 (2010-04-18) ---------------- - Add the concept of scan categories (see the "Scan Categories" section of the documentation) to allow an application to make use of more than one Venusian-using framework simultaneously. 0.1 (2010-02-15) ---------------- - Initial release. Keywords: web wsgi zope Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.5 Description-Content-Type: text/x-rst Provides-Extra: testing Provides-Extra: docs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/README.rst0000644000076600000240000000132200000000000015776 0ustar00xistencestaff00000000000000venusian ======== .. image:: https://travis-ci.org/Pylons/venusian.png?branch=master :target: https://travis-ci.org/Pylons/venusian .. image:: https://readthedocs.org/projects/venusian/badge/?version=latest :target: https://docs.pylonsproject.org/projects/venusian/en/latest/ :alt: Documentation Status Venusian is a library which allows framework authors to defer decorator actions. Instead of taking actions when a function (or class) decorator is executed at import time, you can defer the action usually taken by the decorator until a separate "scan" phase. See the "docs" directory of the package or the online documentation at https://docs.pylonsproject.org/projects/venusian/en/latest/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.1449008 venusian-3.0.0/docs/0000755000076600000240000000000000000000000015241 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.1458972 venusian-3.0.0/docs/.static/0000755000076600000240000000000000000000000016606 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/docs/.static/logo_hi.gif0000644000076600000240000000752400000000000020725 0ustar00xistencestaff00000000000000GIF89a3BBBًzι<<>>ԾԾտѻԿLKIԶľBBA@@?|||??>AAAȼEEEHGEڷcccյųMMM˽͸̾vur@??YYYxCCCþihc:::ӽ!,3 H*\ȰÇ#JHŋ3jȱdžIrCȲ˅.ȜI^F͟?ѬIhE*XMJu! j%D. MjJ?_Ŗ@D|"݅t['Y EHG8._7CW"7)Tv'r֦+RcN+'n c ZMd#g׎9f _HrA\FSAUR`rMq`E"Wҥ&7ĄB5BU t fM?~O?5WGՂ|,ȢC4D54Xc8+pA , 9$ C- Cy  & E(q0 t$ ܴf(D.@>yXM4+ 7H‚JhTBE BZ0'@YN16tQbP X*:ab lTåuQf jJ dBDNC tP<P _PPRlpʴ ;D2$$aQCk72/Dx0(g& J#̐A:r AD!8(a 8žT4΃»@ 3(h.@B氽T@L;|B١sC@khQ:QęCt 0`=GJX-W|A !,@3zrasA rnqRh2[K\ Djئ<9E*Z8p@!p.n B x| ՐtS%=a,$`h %@A'6xB †) 6x;50bLPxF+90bA00)4l#QC-D&lp}PGvG<ڠ'cC!u!,._h ZN_a $ @Ҏ+P2R(ՇMosP/`qg.b Ed_ D 2,%vvm!^+P/yz"<S?yH€WPR C4;@`b9-@_0jj`!`\8lyxw WH0 `cPaQ 0$ W}GJ l   aCЍpqQ@PA dvyGP pYP E" (P{ 0@@ 1 F@d s @ 900) ٟ ;././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0 venusian-3.0.0/docs/.static/repoze.css0000644000076600000240000000077500000000000020635 0ustar00xistencestaff00000000000000@import url('default.css'); body { background-color: #006339; } div.document { background-color: #dad3bd; } div.sphinxsidebar h3, h4, h5, a { color: #127c56 !important; } div.related { color: #dad3bd !important; background-color: #00744a; } div.related a { color: #dad3bd !important; } /* override the justify text align of the default */ div.body p { text-align: left !important; } /* fix google chrome
 tag renderings */

pre {
   line-height: normal !important;
}
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0
venusian-3.0.0/docs/Makefile0000644000076600000240000000521500000000000016704 0ustar00xistencestaff00000000000000# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    = -W
SPHINXBUILD   = sphinx-build
PAPER         =

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html web pickle htmlhelp latex changes linkcheck

help:
	@echo "Please use \`make ' where  is one of"
	@echo "  html      to make standalone HTML files"
	@echo "  pickle    to make pickle files (usable by e.g. sphinx-web)"
	@echo "  htmlhelp  to make HTML files and a HTML help project"
	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  changes   to make an overview over all changed/added/deprecated items"
	@echo "  linkcheck to check all external links for integrity"

clean:
	-rm -rf _build/*

html:
	mkdir -p _build/html _build/doctrees
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
	@echo
	@echo "Build finished. The HTML pages are in _build/html."

text:
	mkdir -p _build/text _build/doctrees
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text
	@echo
	@echo "Build finished. The HTML pages are in _build/text."

pickle:
	mkdir -p _build/pickle _build/doctrees
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
	@echo
	@echo "Build finished; now you can process the pickle files or run"
	@echo "  sphinx-web _build/pickle"
	@echo "to start the sphinx-web server."

web: pickle

htmlhelp:
	mkdir -p _build/htmlhelp _build/doctrees
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in _build/htmlhelp."

latex:
	mkdir -p _build/latex _build/doctrees
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
	cp _static/*.png _build/latex
	./convert_images.sh
	cp _static/latex-warning.png _build/latex
	cp _static/latex-note.png _build/latex
	@echo
	@echo "Build finished; the LaTeX files are in _build/latex."
	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
	      "run these through (pdf)latex."

changes:
	mkdir -p _build/changes _build/doctrees
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
	@echo
	@echo "The overview file is in _build/changes."

linkcheck:
	mkdir -p _build/linkcheck _build/doctrees
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in _build/linkcheck/output.txt."

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub
	@echo
	@echo "Build finished. The epub file is in _build/epub."

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0
venusian-3.0.0/docs/api.rst0000644000076600000240000000045300000000000016546 0ustar00xistencestaff00000000000000API Documentation for Venusian
==============================

.. automodule:: venusian

  .. autoclass:: Scanner

     .. automethod:: scan

  .. autoclass:: AttachInfo

  .. autofunction:: attach(wrapped, callback, category=None, name=None)

  .. autoclass:: lift

  .. autoclass:: onlyliftedfrom
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/docs/conf.py0000644000076600000240000001556700000000000016556 0ustar00xistencestaff00000000000000# -*- coding: utf-8 -*-
#
# venusian documentation build configuration file
#
# This file is execfile()d with the current directory set to its containing
# dir.
#
# The contents of this file are pickled, so don't put values in the
# namespace that aren't pickleable (module imports are okay, they're
# removed automatically).
#
# All configuration values have a default value; values that are commented
# out serve to show the default value.

import sys
import os
import datetime
import pkg_resources

# 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)
    sys.path.append(parent)

# Options for HTML output
# -----------------------

sys.path.append(os.path.abspath("_themes"))
html_theme_path = ["_themes"]
html_theme = "pylons"
html_theme_options = dict(github_url="https://github.com/Pylons/venusian")

# If your extensions are in another directory, add it here. If the
# directory is relative to the documentation root, use os.path.abspath to
# make it absolute, like shown here.
# sys.path.append(os.path.abspath('some/directory'))

# General configuration
# ---------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
    "sphinx.ext.autodoc",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = [".templates"]

# The suffix of source filenames.
source_suffix = ".rst"

# The master toctree document.
master_doc = "index"

# General substitutions.
project = "venusian"
year = datetime.datetime.now().year
copyright = "2012-%s Pylons Project " % year

# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
version = pkg_resources.get_distribution("venusian").version
# The full version, including alpha/beta/rc tags.
release = version

# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
today_fmt = "%B %d, %Y"

# List of documents that shouldn't be included in the build.
# unused_docs = []

# List of directories, relative to source directories, that shouldn't be
# searched for source files.
# exclude_dirs = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

exclude_patterns = [
    "_themes/README.rst",
]

# Options for HTML output
# -----------------------

html_theme_options = {"github_url": "https://github.com/Pylons/venusian"}

# The style sheet to use for HTML and HTML Help pages. A file of that name
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
# html_style = 'pyramid.css'

# The name for this set of Sphinx documents.  If None, it defaults to
# " v documentation".
# html_title = None

# A shorter title for the navigation bar.  Default is the same as
# html_title.
# html_short_title = None

# The name of an image file (within the static path) to place at the top of
# the sidebar.
# html_logo = '.static/logo_hi.gif'

# The name of an image file (within the static path) to use as favicon of
# the docs.  This file should be a Windows icon file (.ico) being 16x16 or
# 32x32 pixels large.
# html_favicon = None

# Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin
# "default.css".
# html_static_path = ['.static']

# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
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

# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}

# If false, no module index is generated.
# html_use_modindex = True

# If false, no index is generated.
# html_use_index = True

# If true, the index is split into individual pages for each letter.
# html_split_index = False

# If true, the reST sources are included in the HTML build as
# _sources/.
# html_copy_source = True

# If true, an OpenSearch description file will be output, and all pages
# will contain a  tag referring to it.  The value of this option must
# be the base URL from which the finished HTML is served.
# html_use_opensearch = ''

# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = ''

# Output file base name for HTML help builder.
htmlhelp_basename = "atemplatedoc"


# Options for LaTeX output
# ------------------------

# The paper size ('letter' or 'a4').
# latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
# latex_font_size = '10pt'

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, document class [howto/manual]).
latex_documents = [
    ("index", "atemplate.tex", "venusian Documentation", "Pylons Project", "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"

# For "manual" documents, if this is true, then toplevel headings are
# parts, not chapters.
# latex_use_parts = False

# Additional stuff for the LaTeX preamble.
# latex_preamble = ''

# Documents to append as an appendix to all manuals.
# latex_appendices = []

# If false, no module index is generated.
# latex_use_modindex = True
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0
venusian-3.0.0/docs/glossary.rst0000644000076600000240000000052700000000000017642 0ustar00xistencestaff00000000000000.. _glossary:

Glossary
========

.. glossary::
   :sorted:

   scan
      Walk a module or package executing callbacks defined by
      venusian-aware decorators along the way.

   Martian
      The package venusian was inspired by, part of the :term:`Grok`
      project.

   Grok
      A Zope-based `web framework `.

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/docs/index.rst0000644000076600000240000004736400000000000017120 0ustar00xistencestaff00000000000000.. _venusian:

Venusian
========

Venusian is a library which allows you to defer the action of
decorators.  Instead of taking actions when a function, method, or
class decorator is executed at import time, you can defer the action
until a separate "scan" phase.

This library is most useful for framework authors.  It is compatible with
CPython versions 3.5+. It is also known to work on PyPy 6.0.0 (Python 3.5+).

.. note::

   The name "Venusian" is a riff on a library named :term:`Martian`
   (which had its genesis in the :term:`Grok` web framework), from
   which the idea for Venusian was stolen.  Venusian is similar to
   Martian, but it offers less functionality, making it slightly
   simpler to use.

Overview
--------

Offering a decorator that wraps a function, method, or class can be a
convenience to your framework's users.  But the very purpose of a
decorator makes it likely to impede testability of the function or
class it decorates: use of a decorator often prevents the function it
decorates from being called with the originally passed arguments, or a
decorator may modify the return value of the decorated function.  Such
modifications to behavior are "hidden" in the decorator code itself.

For example, let's suppose your framework defines a decorator function
named ``jsonify`` which can wrap a function that returns an arbitrary
Python data structure and renders it to a JSON serialization:

.. code-block:: python
   :linenos:

    import json

    def jsonify(wrapped):
        def json_wrapper(request):
            result = wrapped(request)
            dumped = json.dumps(result)
            return dumped
        return json_wrapper

Let's also suppose a user has written an application using your
framework, and he has imported your jsonify decorator function, and
uses it to decorate an application function:

.. code-block:: python
   :linenos:

    from theframework import jsonify

    @jsonify
    def logged_in(request):
       return {'result':'Logged in'}

As a result of an import of the module containing the ``logged_in``
function, a few things happen:

- The user's ``logged_in`` function is replaced by the
  ``json_wrapper`` function.

- The only reference left to the original ``logged_in`` function is
  inside the frame stack of the call to the ``jsonify`` decorator.

This means, from the perspective of the application developer that the
original ``logged_in`` function has effectively "disappeared" when it
is decorated with your ``jsonify`` decorator.  Without bothersome
hackery, it can no longer be imported or retrieved by its original
author.

More importantly, it also means that if the developer wants to unit
test the ``logged_in`` function, he'll need to do so only indirectly:
he'll need to call the ``json_wrapper`` wrapper decorator function and
test that the json returned by the function contains the expected
values.  This will often imply using the ``json.loads`` function to
turn the result of the function *back* into a Python dictionary from
the JSON representation serialized by the decorator.

If the developer is a stickler for unit testing, however, he'll want
to test *only* the function he has actually defined, not the wrapper
code implied by the decorator your framework has provided.  This is
the very definition of unit testing (testing a "unit" without any
other integration with other code).  In this case, it is also more
convenient for him to be able to test the function without the
decorator: he won't need to use the ``json.loads`` function to turn
the result back into a dictionary to make test assertions against.
It's likely such a developer will try to find ways to get at the
original function for testing purposes.

To do so, he might refactor his code to look like this:

.. code-block:: python
   :linenos:

    from theframework import jsonify

    @jsonify
    def logged_in(request):
       return _logged_in(request)

    def _logged_in(request):
       return {'result':'Logged in'}

Then in test code he might import only the ``_logged_in`` function
instead of the decorated ``logged_in`` function for purposes of unit
testing.  In such a scenario, the concentious unit testing app
developer has to define two functions for each decorated function.  If
you're thinking "that looks pretty tedious", you're right.

To give the intrepid tester an "out", you might be tempted as a
framework author to leave a reference to the original function around
somewhere that the unit tester can import and use only for testing
purposes.  You might modify the ``jsonify`` decorator like so in order
to do that:

.. code-block:: python
   :linenos:

    import json
    def jsonify(wrapped):
        def json_wrapper(request):
            result = wrapped(request)
            dumped = json.dumps(result)
            return dumped
        json_wrapper.original_function = wrapped
        return json_wrapper

The line ``json_wrapper.original_function = wrapped`` is the
interesting one above.  It means that the application developer has a
chance to grab a reference to his original function:

.. code-block:: python
   :linenos:

    from myapp import logged_in
    result = logged_in.original_func(None)
    self.assertEqual(result['result'], 'Logged in')

That works.  But it's just a little weird.  Since the ``jsonify``
decorator function has been imported by the developer from a module in
your framework, the developer probably shouldn't really need to know
how it works.  If he needs to read its code, or understand
documentation about how the decorator functions for testing purposes,
your framework *might* be less valuable to him on some level.  This is
arguable, really.  If you use some consistent pattern like this for
all your decorators, it might be a perfectly reasonable solution.

However, what if the decorators offered by your framework were passive
until activated explicitly?  This is the promise of using Venusian
within your decorator implementations.  You may use Venusian within
your decorators to associate a wrapped function, class, or method with
a callback.  Then you can return the originally wrapped function.
Instead of your decorators being "active", the callback associated
with the decorator is passive until a "scan" is initiated.

Using Venusian
--------------

The most basic use of Venusian within a decorator implementation is
demonstrated below.

.. code-block:: python
   :linenos:

   import venusian

   def jsonify(wrapped):
       def callback(scanner, name, ob):
           print 'jsonified'
       venusian.attach(wrapped, callback)
       return wrapped

As you can see, this decorator actually calls into venusian, but then
simply returns the wrapped object.  Effectively this means that this
decorator is "passive" when the module is imported.

Usage of the decorator:

.. code-block:: python
   :linenos:

   from theframework import jsonify

   @jsonify
   def logged_in(request):
       return {'result':'Logged in'}

Note that when we import and use the function, the fact that it is
decorated with the ``jsonify`` decorator is immaterial.  Our decorator
doesn't actually change its behavior.

.. code-block:: python
   :linenos:

   >>> from theapp import logged_in
   >>> logged_in()
   {'result':'Logged in'}
   >>>

This is the intended result.  During unit testing, the original
function can be imported and tested despite the fact that it has been
wrapped with a decorator.

However, we can cause something to happen when we invoke a :term:`scan`.

.. code-block:: python
   :linenos:

   import venusian
   import theapp

   scanner = venusian.Scanner()
   scanner.scan(theapp)

Above we've imported a module named ``theapp``. The ``logged_in``
function which we decorated with our ``jsonify`` decorator lives in
this module.  We've also imported the :mod:`venusian` module, and
we've created an instance of the :class:`venusian.Scanner` class.
Once we've created the instance of :class:`venusian.Scanner`, we
invoke its :meth:`venusian.Scanner.scan` method, passing the
``theapp`` module as an argument to the method.

Here's what happens as a result of invoking the
:meth:`venusian.Scanner.scan` method:

#. Every object defined at module scope within the ``theapp`` Python
   module will be inspected to see if it has had a Venusian callback
   attached to it.

#. For every object that *does* have a Venusian callback attached to
   it, the callback is called.

We could have also passed the ``scan`` method a Python *package*
instead of a module.  This would recursively import each module in the
package (as well as any modules in subpackages), looking for
callbacks.

.. note:: During scan, the only Python files that are processed are
   Python *source* (``.py``) files.  Compiled Python files (``.pyc``,
   ``.pyo`` files) without a corresponding source file are ignored.

In our case, because the callback we defined within the ``jsonify``
decorator function prints ``jsonified`` when it is invoked, which
means that the word ``jsonified`` will be printed to the console when
we cause :meth:`venusian.Scanner.scan` to be invoked.  How is this
useful?  It's not!  At least not yet.  Let's create a more realistic
example.

Let's change our ``jsonify`` decorator to perform a more useful action
when a scan is invoked by changing the body of its callback.

.. code-block:: python
   :linenos:

   import venusian

   def jsonify(wrapped):
       def callback(scanner, name, ob):
           def jsonified(request):
               result = wrapped(request)
               return json.dumps(result)
           scanner.registry.add(name, jsonified)
       venusian.attach(wrapped, callback)
       return wrapped

Now if we invoke a scan, we'll get an error:

.. code-block:: python
   :linenos:

   import venusian
   import theapp

   scanner = venusian.Scanner()
   scanner.scan(theapp)

   AttributeError: Scanner has no attribute 'registry'.

The :class:`venusian.Scanner` class constructor accepts any key-value
pairs; for each key/value pair passed to the scanner's constructor, an
attribute named after the key which points at the value is added to
the scanner instance.  So when you do:

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner(a=1)

Thereafter, ``scanner.a`` will equal the integer 1.

Any number of key-value pairs can be passed to a scanner.  The purpose
of being able to pass arbitrary key/value pairs to a scanner is to
allow cooperating decorator callbacks to access these values: each
callback is passed the ``scanner`` constructed when a scan is invoked.

Let's fix our example by creating an object named ``registry`` that
we'll pass to our scanner's constructor:

.. code-block:: python
   :linenos:

   import venusian
   import theapp

   class Registry(object):
       def __init__(self):
          self.registered = []

       def add(self, name, ob):
          self.registered.append((name, ob))

   registry = Registry()
   scanner = venusian.Scanner(registry=registry)
   scanner.scan(theapp)

At this point, we have a system which, during a scan, for each object
that is wrapped with a Venusian-aware decorator, a tuple will be
appended to the ``registered`` attribute of a ``Registry`` object.
The first element of the tuple will be the decorated object's name,
the second element of the tuple will be a "truly" decorated object.
In our case, this will be a jsonify-decorated callable.

Our framework can then use the information in the registry to decide
which view function to call when a request comes in.

Venusian callbacks must accept three arguments:

``scanner``

  This will be the instance of the scanner that has had its ``scan``
  method invoked.

``name``

  This is the module-level name of the object being decorated.

``ob``

  This is the object being decorated if it's a function or an
  instance; if the object being decorated is a *method*, however, this
  value will be the *class*.

If you consider that the decorator and the scanner can cooperate, and
can perform arbitrary actions together, you can probably imagine a
system where a registry will be populated that informs some
higher-level system (such as a web framework) about the available
decorated functions.

Scan Categories
---------------

Because an application may use two separate Venusian-using frameworks,
Venusian allows for the concept of "scan categories".

The :func:`venusian.attach` function accepts an additional argument
named ``category``.

For example:

.. code-block:: python
   :linenos:

   import venusian

   def jsonify(wrapped):
       def callback(scanner, name, ob):
           def jsonified(request):
               result = wrapped(request)
               return json.dumps(result)
           scanner.registry.add(name, jsonified)
       venusian.attach(wrapped, callback, category='myframework')
       return wrapped

Note the ``category='myframework'`` argument in the call to
:func:`venusian.attach`.  This tells Venusian to attach the callback
to the wrapped object under the specific scan category
``myframework``.  The default scan category is ``None``.

Later, during :meth:`venusian.Scanner.scan`, a user can choose to
activate all the decorators associated only with a particular set of
scan categories by passing a ``categories`` argument.  For example:

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner(a=1)
   scanner.scan(theapp, categories=('myframework',))

The default ``categories`` argument is ``None``, which means activate
all Venusian callbacks during a scan regardless of their category.

``onerror`` Scan Callback
-------------------------

.. versionadded:: 1.0

By default, when Venusian scans a package, it will propagate all exceptions
raised while attempting to import code.  You can use an ``onerror`` callback
argument to :meth:`venusian.Scanner.scan` to change this behavior.

The ``onerror`` argument should either be ``None`` or a callback function
which behaves the same way as the ``onerror`` callback function described in
http://docs.python.org/library/pkgutil.html#pkgutil.walk_packages .

Here's an example ``onerror`` callback that ignores all :exc:`ImportError`
exceptions:

.. code-block:: python
   :linenos:

     import sys
     def onerror(name):
         if not issubclass(sys.exc_info()[0], ImportError):
             raise # reraise the last exception

Here's how we'd use this callback:

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner()
   scanner.scan(theapp, onerror=onerror)

The ``onerror`` callback should execute ``raise`` at some point if any
exception is to be propagated, otherwise it can simply return.  The ``name``
passed to ``onerror`` is the module or package dotted name that could not be
imported due to an exception.


``ignore`` Scan Argument
------------------------

.. versionadded:: 1.0a3

The ``ignore`` to ``scan`` allows you to ignore certain modules, packages, or
global objects during a scan.  It should be a sequence containing strings
and/or callables that will be used to match against the full dotted name of
each object encountered during the scanning process.  If the ignore value you
provide matches a package name, global objects contained by that package as
well any submodules and subpackages of the package (and any global objects
contained by them) will be ignored.  If the ignore value you provide matches
a module name, any global objects in that module will be ignored.  If the
ignore value you provide matches a global object that lives in a package or
module, only that particular global object will be ignored.

The sequence can contain any of these three types of objects:

- A string representing a full dotted name.  To name an object by dotted
  name, use a string representing the full dotted name.  For example, if you
  want to ignore the ``my.package`` package and any of its subobjects during
  the scan, pass ``ignore=['my.package']``.  If the string matches a global
  object (e.g. ``ignore=['my.package.MyClass']``), only that object will be
  ignored and the rest of the objects in the module or package that contains
  the object will be processed.

- A string representing a relative dotted name.  To name an object relative
  to the ``package`` passed to this method, use a string beginning with a
  dot.  For example, if the ``package`` you've passed is imported as
  ``my.package``, and you pass ``ignore=['.mymodule']``, the
  ``my.package.mymodule`` module and any of its subobjects will be omitted
  during scan processing.  If the string matches a global object
  (e.g. ``ignore=['my.package.MyClass']``), only that object will be ignored
  and the rest of the objects in the module or package that contains the
  object will be processed.

- A callable that accepts a full dotted name string of an object as its
  single positional argument and returns ``True`` or ``False``.  If the
  callable returns ``True`` or anything else truthy, the module, package, or
  global object is ignored, if it returns ``False`` or anything else falsy,
  it is not ignored.  If the callable matches a package name, the package as
  well as any of that package's submodules and subpackages (recursively) will
  be ignored.  If the callable matches a module name, that module and any of
  its contained global objects will be ignored.  If the callable matches a
  global object name, only that object name will be ignored.  For example, if
  you want to skip all packages, modules, and global objects that have a full
  dotted name that ends with the word "tests", you can use
  ``ignore=[re.compile('tests$').search]``.

Here's an example of how we might use the ``ignore`` argument to ``scan`` to
ignore an entire package (and any of its submodules and subpackages) by
absolute dotted name:

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner()
   scanner.scan(theapp, ignore=['theapp.package'])

Here's an example of how we might use the ``ignore`` argument to ``scan`` to
ignore an entire package (and any of its submodules and subpackages) by
relative dotted name (``theapp.package``):

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner()
   scanner.scan(theapp, ignore=['.package'])

Here's an example of how we might use the ``ignore`` argument to ``scan`` to
ignore a particular class object:

.. code-block:: python
   :linenos:

   import venusian
   scanner = venusian.Scanner()
   scanner.scan(theapp, ignore=['theapp.package.MyClass'])

Here's an example of how we might use the ``ignore`` argument to ``scan`` to
ignore any module, package, or global object that has a name which ends
with the string ``tests``:

.. code-block:: python
   :linenos:

   import re
   import venusian
   scanner = venusian.Scanner()
   scanner.scan(theapp, ignore=[re.compile('tests$').search])

You can mix and match the three types in the list.  For example,
``scanner.scan(my, ignore=['my.package', '.someothermodule',
re.compile('tests$').search])`` would cause ``my.package`` (and all its
submodules and subobjects) to be ignored, ``my.someothermodule`` to be
ignored, and any modules, packages, or global objects found during the scan
that have a full dotted path that ends with the word ``tests`` to be ignored
beneath the ``my`` package.

Packages and modules matched by any ignore in the list will not be imported,
and their top-level code will not be run as a result.


Limitations and Audience
------------------------

Venusian is not really a tool that is maximally useful to an
application developer.  It would be a little silly to use it every
time you needed a decorator.  Instead, it's most useful for framework
authors, in order to be able to say to their users "the frobozz
decorator doesn't change the output of your function at all" in
documentation.  This is a lot easier than telling them how to test
methods/functions/classes decorated by each individual decorator
offered by your frameworks.

API Documentation / Glossary
----------------------------

.. toctree::
   :maxdepth: 2

   api.rst
   glossary.rst

Indices and tables
------------------

* :ref:`glossary`
* :ref:`modindex`
* :ref:`search`
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/pyproject.toml0000644000076600000240000000023700000000000017227 0ustar00xistencestaff00000000000000[build-system]
requires = ["setuptools >= 40.9.0"]
build-backend = "setuptools.build_meta"

[tool.black]
py36 = false
exclude = '''
/(
  \.git
  | .tox
)/
'''
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1505508334.0
venusian-3.0.0/rtd.txt0000644000076600000240000000001300000000000015635 0ustar00xistencestaff00000000000000-e .[docs]
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2353764
venusian-3.0.0/setup.cfg0000644000076600000240000000230500000000000016132 0ustar00xistencestaff00000000000000[metadata]
name = venusian
version = 3.0.0
description = A library for deferring decorator actions
long_description = file: README.rst, CHANGES.rst
long_description_content_type = text/x-rst
keywords = web wsgi zope
license = BSD-derived (http://www.repoze.org/LICENSE.txt)
classifiers = 
	Development Status :: 6 - Mature
	Intended Audience :: Developers
	Programming Language :: Python :: 3
	Programming Language :: Python :: 3.5
	Programming Language :: Python :: 3.6
	Programming Language :: Python :: 3.7
	Programming Language :: Python :: 3.8
	Programming Language :: Python :: Implementation :: CPython
	Programming Language :: Python :: Implementation :: PyPy
url = https://pylonsproject.org/
author = Chris McDonough, Agendaless Consulting
author_email = pylons-devel@googlegroups.com

[options]
package_dir = 
	=src
packages = find:
python_requires = >=3.5

[options.packages.find]
where = src

[options.extras_require]
testing = 
	pytest
	pytest-cov
	coverage
docs = 
	Sphinx
	repoze.sphinx.autointerface

[bdist_wheel]
universal = 0

[tool:pytest]
ignore = tests/fixtures/
python_files = test_*.py
testpaths = 
	tests
addopts = -W always --cov --cov-report=term-missing

[egg_info]
tag_build = 
tag_date = 0

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/setup.py0000644000076600000240000000014700000000000016025 0ustar00xistencestaff00000000000000"""
Necessary for pip install -e, and python setup.py check
"""

from setuptools import setup

setup()
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.0574155
venusian-3.0.0/src/0000755000076600000240000000000000000000000015100 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.1469114
venusian-3.0.0/src/venusian/0000755000076600000240000000000000000000000016730 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/src/venusian/__init__.py0000644000076600000240000005347600000000000021060 0ustar00xistencestaff00000000000000from inspect import getmembers, getmro, isclass
from pkgutil import iter_modules
import sys

from venusian.advice import getFrameInfo

ATTACH_ATTR = "__venusian_callbacks__"
LIFTONLY_ATTR = "__venusian_liftonly_callbacks__"


class Scanner(object):
    def __init__(self, **kw):
        self.__dict__.update(kw)

    def scan(self, package, categories=None, onerror=None, ignore=None):
        """ Scan a Python package and any of its subpackages.  All
        top-level objects will be considered; those marked with
        venusian callback attributes related to ``category`` will be
        processed.

        The ``package`` argument should be a reference to a Python
        package or module object.

        The ``categories`` argument should be sequence of Venusian
        callback categories (each category usually a string) or the
        special value ``None`` which means all Venusian callback
        categories.  The default is ``None``.

        The ``onerror`` argument should either be ``None`` or a callback
        function which behaves the same way as the ``onerror`` callback
        function described in
        http://docs.python.org/library/pkgutil.html#pkgutil.walk_packages .
        By default, during a scan, Venusian will propagate all errors that
        happen during its code importing process, including
        :exc:`ImportError`.  If you use a custom ``onerror`` callback, you
        can change this behavior.
        
        Here's an example ``onerror`` callback that ignores
        :exc:`ImportError`::

            import sys
            def onerror(name):
                if not issubclass(sys.exc_info()[0], ImportError):
                    raise # reraise the last exception

        The ``name`` passed to ``onerror`` is the module or package dotted
        name that could not be imported due to an exception.

        .. versionadded:: 1.0
           the ``onerror`` callback

        The ``ignore`` argument allows you to ignore certain modules,
        packages, or global objects during a scan.  It should be a sequence
        containing strings and/or callables that will be used to match
        against the full dotted name of each object encountered during a
        scan.  The sequence can contain any of these three types of objects:

        - A string representing a full dotted name.  To name an object by
          dotted name, use a string representing the full dotted name.  For
          example, if you want to ignore the ``my.package`` package *and any
          of its subobjects or subpackages* during the scan, pass
          ``ignore=['my.package']``.

        - A string representing a relative dotted name.  To name an object
          relative to the ``package`` passed to this method, use a string
          beginning with a dot.  For example, if the ``package`` you've
          passed is imported as ``my.package``, and you pass
          ``ignore=['.mymodule']``, the ``my.package.mymodule`` mymodule *and
          any of its subobjects or subpackages* will be omitted during scan
          processing.

        - A callable that accepts a full dotted name string of an object as
          its single positional argument and returns ``True`` or ``False``.
          For example, if you want to skip all packages, modules, and global
          objects with a full dotted path that ends with the word "tests", you
          can use ``ignore=[re.compile('tests$').search]``.  If the callable
          returns ``True`` (or anything else truthy), the object is ignored,
          if it returns ``False`` (or anything else falsy) the object is not
          ignored.  *Note that unlike string matches, ignores that use a
          callable don't cause submodules and subobjects of a module or
          package represented by a dotted name to also be ignored, they match
          individual objects found during a scan, including packages,
          modules, and global objects*.

        You can mix and match the three types of strings in the list.  For
        example, if the package being scanned is ``my``,
        ``ignore=['my.package', '.someothermodule',
        re.compile('tests$').search]`` would cause ``my.package`` (and all
        its submodules and subobjects) to be ignored, ``my.someothermodule``
        to be ignored, and any modules, packages, or global objects found
        during the scan that have a full dotted name that ends with the word
        ``tests`` to be ignored.

        Note that packages and modules matched by any ignore in the list will
        not be imported, and their top-level code will not be run as a result.

        A string or callable alone can also be passed as ``ignore`` without a
        surrounding list.
        
        .. versionadded:: 1.0a3
           the ``ignore`` argument
        """

        pkg_name = package.__name__

        if ignore is not None and (
            isinstance(ignore, str) or not hasattr(ignore, "__iter__")
        ):
            ignore = [ignore]
        elif ignore is None:
            ignore = []

        # non-leading-dotted name absolute object name
        str_ignores = [ign for ign in ignore if isinstance(ign, str)]
        # leading dotted name relative to scanned package
        rel_ignores = [ign for ign in str_ignores if ign.startswith(".")]
        # non-leading dotted names
        abs_ignores = [ign for ign in str_ignores if not ign.startswith(".")]
        # functions, e.g. re.compile('pattern').search
        callable_ignores = [ign for ign in ignore if callable(ign)]

        def _ignore(fullname):
            for ign in rel_ignores:
                if fullname.startswith(pkg_name + ign):
                    return True
            for ign in abs_ignores:
                # non-leading-dotted name absolute object name
                if fullname.startswith(ign):
                    return True
            for ign in callable_ignores:
                if ign(fullname):
                    return True
            return False

        def invoke(mod_name, name, ob):

            fullname = mod_name + "." + name

            if _ignore(fullname):
                return

            category_keys = categories
            try:
                # Some metaclasses do insane things when asked for an
                # ``ATTACH_ATTR``, like not raising an AttributeError but
                # some other arbitary exception.  Some even shittier
                # introspected code lets us access ``ATTACH_ATTR`` far but
                # barfs on a second attribute access for ``attached_to``
                # (still not raising an AttributeError, but some other
                # arbitrary exception).  Finally, the shittiest code of all
                # allows the attribute access of the ``ATTACH_ATTR`` *and*
                # ``attached_to``, (say, both ``ob.__getattr__`` and
                # ``attached_categories.__getattr__`` returning a proxy for
                # any attribute access), which either a) isn't callable or b)
                # is callable, but, when called, shits its pants in an
                # potentially arbitrary way (although for b, only TypeError
                # has been seen in the wild, from PyMongo).  Thus the
                # catchall except: return here, which in any other case would
                # be high treason.
                attached_categories = getattr(ob, ATTACH_ATTR)
                if not attached_categories.attached_to(mod_name, name, ob):
                    return
            except:
                return
            if category_keys is None:
                category_keys = list(attached_categories.keys())
                try:
                    # When metaclasses return proxies for any attribute access
                    # the list may contain keys of different types which might
                    # not be sortable.  In that case we can just return,
                    # because we're not dealing with a proper venusian
                    # callback.
                    category_keys.sort()
                except TypeError:  # pragma: no cover
                    return
            for category in category_keys:
                callbacks = attached_categories.get(category, [])
                try:
                    # Metaclasses might trick us by reaching this far and then
                    # fail with too little values to unpack.
                    for callback, cb_mod_name, liftid, scope in callbacks:
                        if cb_mod_name != mod_name:
                            # avoid processing objects that were imported into
                            # this module but were not actually defined there
                            continue
                        callback(self, name, ob)
                except ValueError:  # pragma: nocover
                    continue

        for name, ob in getmembers(package):
            # whether it's a module or a package, we need to scan its
            # members; walk_packages only iterates over submodules and
            # subpackages
            invoke(pkg_name, name, ob)

        if hasattr(package, "__path__"):  # package, not module
            results = walk_packages(
                package.__path__,
                package.__name__ + ".",
                onerror=onerror,
                ignore=_ignore,
            )

            for importer, modname, ispkg in results:
                loader = importer.find_module(modname)
                if loader is not None:  # happens on pypy with orphaned pyc
                    try:
                        get_filename = getattr(loader, "get_filename", None)
                        if get_filename is None:  # pragma: nocover
                            get_filename = loader._get_filename
                        try:
                            fn = get_filename(modname)
                        except TypeError:  # pragma: nocover
                            fn = get_filename()

                        # NB: use __import__(modname) rather than
                        # loader.load_module(modname) to prevent
                        # inappropriate double-execution of module code
                        try:
                            __import__(modname)
                        except Exception:
                            if onerror is not None:
                                onerror(modname)
                            else:
                                raise
                        module = sys.modules.get(modname)
                        if module is not None:
                            for name, ob in getmembers(module, None):
                                invoke(modname, name, ob)
                    finally:
                        if hasattr(loader, "file") and hasattr(
                            loader.file, "close"
                        ):  # pragma: nocover
                            loader.file.close()


class AttachInfo(object):
    """
    An instance of this class is returned by the
    :func:`venusian.attach` function.  It has the following
    attributes:

    ``scope``

      One of ``exec``, ``module``, ``class``, ``function call`` or
      ``unknown`` (each a string).  This is the scope detected while
      executing the decorator which runs the attach function.

    ``module``

      The module in which the decorated function was defined.

    ``locals``

      A dictionary containing decorator frame's f_locals.

    ``globals``

      A dictionary containing decorator frame's f_globals.

    ``category``

      The ``category`` argument passed to ``attach`` (or ``None``, the
      default).

    ``codeinfo``

      A tuple in the form ``(filename, lineno, function, sourceline)``
      representing the context of the venusian decorator used.  Eg.
      ``('/home/chrism/projects/venusian/tests/test_advice.py', 81,
      'testCallInfo', 'add_handler(foo, bar)')``
      
    """

    def __init__(self, **kw):
        self.__dict__.update(kw)


class Categories(dict):
    def __init__(self, attached_to):
        super(dict, self).__init__()
        if isinstance(attached_to, tuple):
            self.attached_id = attached_to
        else:
            self.attached_id = id(attached_to)
        self.lifted = False

    def attached_to(self, mod_name, name, obj):
        if isinstance(self.attached_id, int):
            return self.attached_id == id(obj)
        return self.attached_id == (mod_name, name)


def attach(wrapped, callback, category=None, depth=1, name=None):
    """ Attach a callback to the wrapped object.  It will be found
    later during a scan.  This function returns an instance of the
    :class:`venusian.AttachInfo` class.

    ``category`` should be ``None`` or a string representing a decorator
    category name.

    ``name`` should be ``None`` or a string representing a subcategory within
    the category.  This will be used by the ``lift`` class decorator to
    determine if decorations of a method should be inherited or overridden.
    """

    frame = sys._getframe(depth + 1)
    scope, module, f_locals, f_globals, codeinfo = getFrameInfo(frame)
    module_name = getattr(module, "__name__", None)
    wrapped_name = getattr(wrapped, "__name__", None)
    class_name = codeinfo[2]

    liftid = "%s %s" % (wrapped_name, name)

    if scope == "class":
        # we're in the midst of a class statement
        categories = f_locals.get(ATTACH_ATTR, None)
        if categories is None or not categories.attached_to(
            module_name, class_name, None
        ):
            categories = Categories((module_name, class_name))
            f_locals[ATTACH_ATTR] = categories
        callbacks = categories.setdefault(category, [])
    else:
        categories = getattr(wrapped, ATTACH_ATTR, None)
        if categories is None or not categories.attached_to(
            module_name, wrapped_name, wrapped
        ):
            # if there aren't any attached categories, or we've retrieved
            # some by inheritance, we need to create new ones
            categories = Categories(wrapped)
            setattr(wrapped, ATTACH_ATTR, categories)
        callbacks = categories.setdefault(category, [])

    callbacks.append((callback, module_name, liftid, scope))

    return AttachInfo(
        scope=scope,
        module=module,
        locals=f_locals,
        globals=f_globals,
        category=category,
        codeinfo=codeinfo,
    )


def walk_packages(path=None, prefix="", onerror=None, ignore=None):
    """Yields (module_loader, name, ispkg) for all modules recursively
    on path, or, if path is None, all accessible modules.

    'path' should be either None or a list of paths to look for
    modules in.

    'prefix' is a string to output on the front of every module name
    on output.

    Note that this function must import all *packages* (NOT all
    modules!) on the given path, in order to access the __path__
    attribute to find submodules.

    'onerror' is a function which gets called with one argument (the name of
    the package which was being imported) if any exception occurs while
    trying to import a package.  If no onerror function is supplied, any
    exception is exceptions propagated, terminating the search.

    'ignore' is a function fed a fullly dotted name; if it returns True, the
    object is skipped and not returned in results (and if it's a package it's
    not imported).

    Examples:

    # list all modules python can access
    walk_packages()

    # list all submodules of ctypes
    walk_packages(ctypes.__path__, ctypes.__name__+'.')

    # NB: we can't just use pkgutils.walk_packages because we need to ignore
    # things
    """

    def seen(p, m={}):
        if p in m:  # pragma: no cover
            return True
        m[p] = True

    # iter_modules is nonrecursive
    for importer, name, ispkg in iter_modules(path, prefix):

        if ignore is not None and ignore(name):
            # if name is a package, ignoring here will cause
            # all subpackages and submodules to be ignored too
            continue

        # do any onerror handling before yielding

        if ispkg:
            try:
                __import__(name)
            except Exception:
                if onerror is not None:
                    onerror(name)
                else:
                    raise
            else:
                yield importer, name, ispkg
                path = getattr(sys.modules[name], "__path__", None) or []

                # don't traverse path items we've seen before
                path = [p for p in path if not seen(p)]

                for item in walk_packages(path, name + ".", onerror, ignore):
                    yield item
        else:
            yield importer, name, ispkg


class lift(object):
    """
    A class decorator which 'lifts' superclass venusian configuration
    decorations into subclasses.  For example::

      from venusian import lift
      from somepackage import venusian_decorator

      class Super(object):
          @venusian_decorator()
          def boo(self): pass

          @venusian_decorator()
          def hiss(self): pass

          @venusian_decorator()
          def jump(self): pass
              
      @lift()
      class Sub(Super):
          def boo(self): pass

          def hiss(self): pass
          
          @venusian_decorator()
          def smack(self): pass

    The above configuration will cause the callbacks of seven venusian
    decorators.  The ones attached to Super.boo, Super.hiss, and Super.jump
    *plus* ones attached to Sub.boo, Sub.hiss, Sub.hump and Sub.smack.

    If a subclass overrides a decorator on a method, its superclass decorators
    will be ignored for the subclass.  That means that in this configuration::

      from venusian import lift
      from somepackage import venusian_decorator

      class Super(object):
          @venusian_decorator()
          def boo(self): pass

          @venusian_decorator()
          def hiss(self): pass

      @lift()
      class Sub(Super):

          def boo(self): pass
          
          @venusian_decorator()
          def hiss(self): pass

    Only four, not five decorator callbacks will be run: the ones attached to
    Super.boo and Super.hiss, the inherited one of Sub.boo and the
    non-inherited one of Sub.hiss.  The inherited decorator on Super.hiss will
    be ignored for the subclass.

    The ``lift`` decorator takes a single argument named 'categories'.  If
    supplied, it should be a tuple of category names.  Only decorators
    in this category will be lifted if it is suppled.
          
    """

    def __init__(self, categories=None):
        self.categories = categories

    def __call__(self, wrapped):
        if not isclass(wrapped):
            raise RuntimeError(
                '"lift" only works as a class decorator; you tried to use '
                "it against %r" % wrapped
            )
        frame = sys._getframe(1)
        scope, module, f_locals, f_globals, codeinfo = getFrameInfo(frame)
        module_name = getattr(module, "__name__", None)
        newcategories = Categories(wrapped)
        newcategories.lifted = True
        for cls in getmro(wrapped):
            attached_categories = cls.__dict__.get(ATTACH_ATTR, None)
            if attached_categories is None:
                attached_categories = cls.__dict__.get(LIFTONLY_ATTR, None)
            if attached_categories is not None:
                for cname, category in attached_categories.items():
                    if cls is not wrapped:
                        if self.categories and not cname in self.categories:
                            continue
                    callbacks = newcategories.get(cname, [])
                    newcallbacks = []
                    for cb, _, liftid, cscope in category:
                        append = True
                        toappend = (cb, module_name, liftid, cscope)
                        if cscope == "class":
                            for ncb, _, nliftid, nscope in callbacks:
                                if nscope == "class" and liftid == nliftid:
                                    append = False
                        if append:
                            newcallbacks.append(toappend)
                    newcategory = list(callbacks) + newcallbacks
                    newcategories[cname] = newcategory
                if attached_categories.lifted:
                    break
        if newcategories:  # if it has any keys
            setattr(wrapped, ATTACH_ATTR, newcategories)
        return wrapped


class onlyliftedfrom(object):
    """
    A class decorator which marks a class as 'only lifted from'.  Decorations
    made on methods of the class won't have their callbacks called directly,
    but classes which inherit from only-lifted-from classes which also use the
    ``lift`` class decorator will use the superclass decoration callbacks.

    For example::
    
      from venusian import lift, onlyliftedfrom
      from somepackage import venusian_decorator

      @onlyliftedfrom()
      class Super(object):
          @venusian_decorator()
          def boo(self): pass

          @venusian_decorator()
          def hiss(self): pass

      @lift()
      class Sub(Super):

          def boo(self): pass
          
          def hiss(self): pass

    Only two decorator callbacks will be run: the ones attached to Sub.boo and
    Sub.hiss.  The inherited decorators on Super.boo and Super.hiss will be
    not be registered.
    """

    def __call__(self, wrapped):
        if not isclass(wrapped):
            raise RuntimeError(
                '"onlyliftedfrom" only works as a class decorator; you tried '
                "to use it against %r" % wrapped
            )
        cats = getattr(wrapped, ATTACH_ATTR, None)
        class_name = wrapped.__name__
        module_name = wrapped.__module__
        key = (module_name, class_name, wrapped)
        if cats is None or not cats.attached_to(*key):
            # we either have no categories or our categories are defined
            # in a superclass
            return
        delattr(wrapped, ATTACH_ATTR)
        setattr(wrapped, LIFTONLY_ATTR, cats)
        return wrapped
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/src/venusian/advice.py0000644000076600000240000000545000000000000020541 0ustar00xistencestaff00000000000000##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Class advice.

This module was adapted from 'protocols.advice', part of the Python
Enterprise Application Kit (PEAK).  Please notify the PEAK authors
(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
Zope-specific changes are required, so that the PEAK version of this module
can be kept in sync.

PEAK is a Python application framework that interoperates with (but does
not require) Zope 3 and Twisted.  It provides tools for manipulating UML
models, object-relational persistence, aspect-oriented programming, and more.
Visit the PEAK home page at http://peak.telecommunity.com for more information.

$Id: advice.py 25177 2004-06-02 13:17:31Z jim $
"""

import inspect
import sys


def getFrameInfo(frame):
    """Return (kind,module,locals,globals) for a frame

    'kind' is one of "exec", "module", "class", "function call", or "unknown".
    """

    f_locals = frame.f_locals
    f_globals = frame.f_globals

    sameNamespace = f_locals is f_globals
    hasModule = "__module__" in f_locals
    hasName = "__name__" in f_globals

    sameName = hasModule and hasName
    sameName = sameName and f_globals["__name__"] == f_locals["__module__"]

    module = hasName and sys.modules.get(f_globals["__name__"]) or None

    namespaceIsModule = module and module.__dict__ is f_globals

    frameinfo = inspect.getframeinfo(frame)
    try:
        sourceline = frameinfo[3][0].strip()
    except:  # pragma NO COVER
        # dont understand circumstance here, 3rdparty code without comment
        sourceline = frameinfo[3]

    codeinfo = frameinfo[0], frameinfo[1], frameinfo[2], sourceline

    if not namespaceIsModule:  # pragma no COVER
        # some kind of funky exec
        kind = "exec"  # don't know how to repeat this scenario
    elif sameNamespace and not hasModule:
        kind = "module"
    elif sameName and not sameNamespace:
        kind = "class"
    elif not sameNamespace:
        kind = "function call"
    else:  # pragma NO COVER
        # How can you have f_locals is f_globals, and have '__module__' set?
        # This is probably module-level code, but with a '__module__' variable.
        kind = "unknown"
    return kind, module, f_locals, f_globals, codeinfo
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2069058
venusian-3.0.0/src/venusian.egg-info/0000755000076600000240000000000000000000000020422 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983401.0
venusian-3.0.0/src/venusian.egg-info/PKG-INFO0000644000076600000240000002631500000000000021526 0ustar00xistencestaff00000000000000Metadata-Version: 2.1
Name: venusian
Version: 3.0.0
Summary: A library for deferring decorator actions
Home-page: https://pylonsproject.org/
Author: Chris McDonough, Agendaless Consulting
Author-email: pylons-devel@googlegroups.com
License: BSD-derived (http://www.repoze.org/LICENSE.txt)
Description: venusian
        ========
        
        .. image:: https://travis-ci.org/Pylons/venusian.png?branch=master
                :target: https://travis-ci.org/Pylons/venusian
        
        .. image:: https://readthedocs.org/projects/venusian/badge/?version=latest
                :target: https://docs.pylonsproject.org/projects/venusian/en/latest/
                :alt: Documentation Status
        
        Venusian is a library which allows framework authors to defer
        decorator actions.  Instead of taking actions when a function (or
        class) decorator is executed at import time, you can defer the action
        usually taken by the decorator until a separate "scan" phase.
        
        See the "docs" directory of the package or the online documentation at
        https://docs.pylonsproject.org/projects/venusian/en/latest/
        
        3.0.0 (2019-10-04)
        ------------------
        
        - This release matches 2.0.0 other than in the version number. This fixes an
          issue with Requires-Python metadata not being uploaded correctly to PyPi.
        
          This version is only compatible with Python 3.5+
        
        2.0.0 (2019-10-04)
        ------------------
        
        - Drop support for Python 2.7, 3.3, and 3.4
        
        - Removed the usage of the ``imp`` module to squelch the warnings regarding a
          deprecated modules. See https://github.com/Pylons/venusian/pull/63 and
          https://github.com/Pylons/venusian/issues/57
        
        1.2.0 (2019-01-08)
        ------------------
        
        - Add support for Python 3.7.
        
        - Drop support for Python 3.3.
        
        1.1.0 (2017-04-24)
        ------------------
        
        - Updated to using py.test instead of nosetest, and added support for Python
          3.4 -> 3.6
        
        - Make scanning more resilient of metaclasses that return proxies for any
          attribute access.
        
        - Fix bug where using the same venusian decorator on both a class and its
          methods would cause the method decorations to be ignored. See
          https://github.com/Pylons/venusian/issues/40
        
        - Drop support for Python 2.6.
        
        - Drop support for Python 3.2:  it is no longer supported by current
          packaging / CI tools.
        
        - Support loaders that require the module name as argument to their
          ``get_filename()`` method. This fixes problems with zipped packages
          on Python 3.
        
        - Micro-optimization when ignores are used (see
          https://github.com/Pylons/venusian/pull/20).
        
        - A tox run now combines coverage between Py2 and Py3.
        
        1.0 (2014-06-30)
        ----------------
        
        - Fix an issue under PyPy > 2.0 where attached decorators may not be found.
        
        - Drop support of Python 2.4 / 2.5 / Jython.
        
        - Add ``lift`` and ``onlyliftedfrom`` class decorators to allow for inheritance
          of venusian decorators attached to superclass methods.  See the API
          documentation for more information.
        
        - Fix bug where otherwise undecorated subclass of a superclass that had
          venusian decorators on it would inherit its superclass' decorations.
          Venusian decorators should have never been inherited implicitly.  See
          https://github.com/Pylons/venusian/issues/11#issuecomment-4977352
        
        1.0a8 (2013-04-15)
        ------------------
        
        - Pass ``ignore`` argument along recursively to ``walk_packages`` so custom
          ignore functions will ignore things recursively.  See
          https://github.com/Pylons/venusian/pull/16
        
        - Don't run tox tests under Python 2.4 anymore (tox no longer supports 2.4).
        
        1.0a7 (2012-08-25)
        ------------------
        
        - Venusian now works on Python 3.3b2+ (importlib-based).
        
        - Use nose-exclude instead of relying on fragile module-scope code to ensure
          we don't get errors resulting from import of fixture code during
          "nosetests".
        
        - Bug fix: no longer suppress ``ImportError`` while scanning by default.  If
          you want to suppress ``ImportError`` while scanning, you'll now need use an
          ``onerror`` callback as described in the documentation.
        
        1.0a6 (2012-04-23)
        ------------------
        
        - Don't ignore decorated objects within their original locations if they
          happen to be imported into another module (remove ``seen`` set from invoke
          in venusian scanning).  See https://github.com/Pylons/venusian/pull/13 .
        
        1.0a5 (2012-04-21)
        ------------------
        
        - Slightly less sucky way to ignore objects during scanning that are only
          imported into a module but not actually defined there.  See 1.0a4 change
          notes for rationale.  Now instead of checking whether the module of the
          *scanned object* matches the module being scanned, we check whether the
          module of the *Venusian attachment* matches the module being scanned.  This
          allows some genuine uses of imported objects as Venusian scan targets while
          preventing inappropriate double-scanning of objects that have a venusian
          attachment which just happen to be imported into other scanned modules.
        
        - Add ``dev`` and ``docs`` setup.py commands (ala Pyramid).
        
        1.0a4 (2012-04-16)
        ------------------
        
        - Attempt to ignore objects during scanning that are only imported into a
          module but not actually defined there.  This is a semantics change, but
          it's the right thing to do, because I found myself facing a situation like
          this::
        
            # in a module named "one"
        
            from two import anotheradecoratedthing
            @adecorator
            def adecoratedthing(): pass
        
            # and scanning both modules
            scan('one')
            scan('two')
        
          In this case you'd wind up with two repeated registrations for
          "anotherdecoratedthing", which isn't what anyone expects.
        
        1.0a3 (2012-02-08)
        ------------------
        
        - Add an ``ignore`` argument to the ``scan`` method of a ``Scanner``.  This
          argument allows a user to ignore packages, modules, and global objects by
          name during a ``scan``.  See the "ignore Scan Argument" in the narrative
          documentation for more details.
        
        1.0a2 (2011-09-02)
        ------------------
        
        - Close ImpLoader file handle to avoid resource warnings on Python 3.
        
        1.0a1 (2011-08-27)
        ------------------
        
        - Python 3 compatibility.
        
        - Allow an ``onerror`` callback to be passed to ``Scanner.scan()``.
        
        0.9 (2011-06-18)
        ----------------
        
        - Prevent corner case scan-time exception when trying to introspect insane
          module-scope objects.  See https://github.com/Pylons/venusian/issues/5 .
        
        0.8 (2011-04-30)
        ----------------
        
        - Normal "setup.py test" can't support running the venusian tests under py
          2.4 or 2.5; when it scans the 'classdecorators' fixture, it barfs.  To get
          around this, we used to depend on ``nose`` in ``setup_requires`` and tell
          "setup.py test" to use nose by setting test_suite to "nose.collector" but
          we can't anymore because folks use Venusian in systems which install from
          pip bundles; pip bundles do not support setup_requires.  So, sorry, we're
          painted into a corner; at this point you just have to know to install nose
          and run "setup.py nosetests" rather than "setup.py test".  Or just run
          "tox" which tests it under all Pythons.
        
        0.7 (2011-03-16)
        ----------------
        
        - Use Pylons theme in documentation.
        
        - Fix orphaned pyc test on pypy.
        
        - Fix GitHub Issue #1: subclasses of decorated classes that do not
          have any decorations should not inherit the decorations of their
          parent classes. 
        
        - Fix GitHub Issue #2: scans should only "find" each object once per
          scan, regardless of how many modules that object is imported into.
        
        0.6 (2011-01-09)
        ----------------
        
        - Some metaclasses (Elixir's) don't raise an AttributeError when asked for a
          nonexistent attribute during a scan.  We now catch all exceptions when
          interrogating an object for ``__venusian_callbacks__`` rather than just
          AttributeError.
        
        0.5 (2010-12-19)
        ----------------
        
        - Make ``codeinfo`` attribute available as an attribute of the AttachInfo
          object. It will be a tuple in the form ``(filename, lineno, function,
          sourceline)`` representing the context of the venusian decorator.  Eg.
          ``('/home/chrism/projects/venusian/tests/test_advice.py', 81,
          'testCallInfo', 'add_handler(foo, bar)')``
        
        0.4 (2010-09-03)
        ----------------
        
        - Bug fix: when a venusian decorator used as a class decorator was
          used against both a class *and* a subclass of that class, the
          superclass and subclass would effectively share the same set of
          callbacks.  This was not the intent: each class declaration should
          have its own local set of callbacks; callbacks added via decorations
          should not be inherited, and a superclass should not receive its
          subclass' decorations.
        
        - Arrange test fixtures into a single directory.
        
        0.3 (2010-06-24)
        ----------------
        
        - Ignore orphaned modules (``.pyc`` or ``.pyo`` files without a
          corresponding ``.py`` file) during a scan.
        
        0.2 (2010-04-18)
        ----------------
        
        - Add the concept of scan categories (see the "Scan Categories"
          section of the documentation) to allow an application to make use of
          more than one Venusian-using framework simultaneously.
        
        0.1 (2010-02-15)
        ----------------
        
        - Initial release.
        
Keywords: web wsgi zope
Platform: UNKNOWN
Classifier: Development Status :: 6 - Mature
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.5
Description-Content-Type: text/x-rst
Provides-Extra: testing
Provides-Extra: docs
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983401.0
venusian-3.0.0/src/venusian.egg-info/SOURCES.txt0000644000076600000240000000432100000000000022306 0ustar00xistencestaff00000000000000.coveragerc
.travis.yml
CHANGES.rst
CONTRIBUTORS.txt
COPYRIGHT.txt
LICENSE.txt
MANIFEST.in
README.rst
pyproject.toml
rtd.txt
setup.cfg
setup.py
tox.ini
docs/Makefile
docs/api.rst
docs/conf.py
docs/glossary.rst
docs/index.rst
docs/.static/logo_hi.gif
docs/.static/repoze.css
src/venusian/__init__.py
src/venusian/advice.py
src/venusian.egg-info/PKG-INFO
src/venusian.egg-info/SOURCES.txt
src/venusian.egg-info/dependency_links.txt
src/venusian.egg-info/requires.txt
src/venusian.egg-info/top_level.txt
tests/__init__.py
tests/test_advice.py
tests/test_venusian.py
tests/fixtures/__init__.py
tests/fixtures/category.py
tests/fixtures/class_and_method.py
tests/fixtures/classdecorator.py
tests/fixtures/inheritance.py
tests/fixtures/lifting1.py
tests/fixtures/lifting2.py
tests/fixtures/lifting3.py
tests/fixtures/lifting4.py
tests/fixtures/lifting5.py
tests/fixtures/subclassing.py
tests/fixtures/zipped.zip
tests/fixtures/attrerror/__init__.py
tests/fixtures/attrerror/will_cause_import_error.py
tests/fixtures/attrerror_package/__init__.py
tests/fixtures/attrerror_package/will_cause_import_error/__init__.py
tests/fixtures/import_and_scan/__init__.py
tests/fixtures/import_and_scan/mock.py
tests/fixtures/import_and_scan/one.py
tests/fixtures/import_and_scan/two.py
tests/fixtures/importerror/__init__.py
tests/fixtures/importerror/will_cause_import_error.py
tests/fixtures/importerror_package/__init__.py
tests/fixtures/importerror_package/will_cause_import_error/__init__.py
tests/fixtures/importonly/__init__.py
tests/fixtures/importonly/one.py
tests/fixtures/importonly/two.py
tests/fixtures/nested/__init__.py
tests/fixtures/nested/sub1/__init__.py
tests/fixtures/nested/sub1/subsub1/__init__.py
tests/fixtures/nested/sub2/__init__.py
tests/fixtures/nested/sub2/subsub2/__init__.py
tests/fixtures/one/__init__.py
tests/fixtures/one/module.py
tests/fixtures/one/module2.py
tests/fixtures/pyc/__init__.py
tests/fixtures/pyc/module.py
tests/fixtures/pyc/subpackage/__init__.py
tests/fixtures/subpackages/__init__.py
tests/fixtures/subpackages/mod2.py
tests/fixtures/subpackages/childpackage/__init__.py
tests/fixtures/subpackages/childpackage/will_cause_import_error.py
tests/fixtures/two/__init__.py
tests/fixtures/two/mod1.py
tests/fixtures/two/mod2.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983401.0
venusian-3.0.0/src/venusian.egg-info/dependency_links.txt0000644000076600000240000000000100000000000024470 0ustar00xistencestaff00000000000000
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983401.0
venusian-3.0.0/src/venusian.egg-info/requires.txt0000644000076600000240000000012100000000000023014 0ustar00xistencestaff00000000000000
[docs]
Sphinx
repoze.sphinx.autointerface

[testing]
pytest
pytest-cov
coverage
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983401.0
venusian-3.0.0/src/venusian.egg-info/top_level.txt0000644000076600000240000000001100000000000023144 0ustar00xistencestaff00000000000000venusian
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2088792
venusian-3.0.0/tests/0000755000076600000240000000000000000000000015453 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/__init__.py0000644000076600000240000000001200000000000017555 0ustar00xistencestaff00000000000000# package
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2149224
venusian-3.0.0/tests/fixtures/0000755000076600000240000000000000000000000017324 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/__init__.py0000644000076600000240000000150200000000000021433 0ustar00xistencestaff00000000000000import venusian


class decorator(object):
    category = None
    call_count = 0

    def __init__(self, **kw):
        self.__dict__.update(kw)

    def __call__(self, wrapped):
        view_config = self.__dict__.copy()

        def callback(context, name, ob):
            if hasattr(context, "test"):
                context.test(ob=ob, name=name, **view_config)
            self.__class__.call_count += 1

        info = venusian.attach(wrapped, callback, category=self.category)
        if info.scope == "class":
            # we're in the midst of a class statement
            if view_config.get("attr") is None:
                view_config["attr"] = wrapped.__name__
        return wrapped


class categorydecorator(decorator):
    category = "mycategory"


class categorydecorator2(decorator):
    category = "mycategory2"
././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1572983402.215735
venusian-3.0.0/tests/fixtures/attrerror/0000755000076600000240000000000000000000000021350 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/attrerror/__init__.py0000644000076600000240000000017700000000000023466 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/attrerror/will_cause_import_error.py0000644000076600000240000000002500000000000026651 0ustar00xistencestaff00000000000000raise AttributeError
././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1572983402.216153
venusian-3.0.0/tests/fixtures/attrerror_package/0000755000076600000240000000000000000000000023023 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/attrerror_package/__init__.py0000644000076600000240000000017700000000000025141 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2165785
venusian-3.0.0/tests/fixtures/attrerror_package/will_cause_import_error/0000755000076600000240000000000000000000000027755 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/attrerror_package/will_cause_import_error/__init__.py0000644000076600000240000000002500000000000032063 0ustar00xistencestaff00000000000000raise AttributeError
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/category.py0000644000076600000240000000044100000000000021512 0ustar00xistencestaff00000000000000from tests.fixtures import categorydecorator
from tests.fixtures import categorydecorator2


@categorydecorator(function=True)
def function(request):  # pragma: no cover
    return request


@categorydecorator2(function=True)
def function2(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/class_and_method.py0000644000076600000240000000027200000000000023166 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(class_=True)
class ClassWithMethod(object):
    @decorator(method=True)
    def method_on_class(self):  # pragma: no cover
        pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/classdecorator.py0000644000076600000240000000024700000000000022711 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(superclass=True)
class SuperClass(object):
    pass


@decorator(subclass=True)
class SubClass(SuperClass):
    pass
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2183208
venusian-3.0.0/tests/fixtures/import_and_scan/0000755000076600000240000000000000000000000022464 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/import_and_scan/__init__.py0000644000076600000240000000001200000000000024566 0ustar00xistencestaff00000000000000# package
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/import_and_scan/mock.py0000644000076600000240000000137000000000000023770 0ustar00xistencestaff00000000000000class _Call(tuple):  # pragma: no cover
    def __new__(cls, value=(), name=None):
        name = ""
        args = ()
        kwargs = {}
        _len = len(value)
        if _len == 3:
            name, args, kwargs = value
        return tuple.__new__(cls, (name, args, kwargs))

    def __init__(self, value=(), name=None):
        self.name = name

    def __call__(self, *args, **kwargs):
        if self.name is None:
            return _Call(("", args, kwargs), name="()")
        else:
            return _Call((self.name, args, kwargs), name=self.name + "()")

    def __getattr__(self, attr):
        if self.name is None:
            return _Call(name=attr)
        else:
            return _Call(name="%s.%s" % (self.name, attr))


call = _Call()
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/import_and_scan/one.py0000644000076600000240000000035700000000000023624 0ustar00xistencestaff00000000000000from tests.fixtures import decorator
from tests.fixtures.import_and_scan.two import twofunction  # should not be scanned


@decorator(function=True)
def onefunction(request):  # pragma: no cover
    twofunction(request)
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/import_and_scan/two.py0000644000076600000240000000020200000000000023641 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def twofunction(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2191625
venusian-3.0.0/tests/fixtures/importerror/0000755000076600000240000000000000000000000021710 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importerror/__init__.py0000644000076600000240000000017700000000000024026 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importerror/will_cause_import_error.py0000644000076600000240000000002400000000000027210 0ustar00xistencestaff00000000000000import doesnt.exist
././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1572983402.219554
venusian-3.0.0/tests/fixtures/importerror_package/0000755000076600000240000000000000000000000023363 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importerror_package/__init__.py0000644000076600000240000000017700000000000025501 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1572983402.219945
venusian-3.0.0/tests/fixtures/importerror_package/will_cause_import_error/0000755000076600000240000000000000000000000030315 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importerror_package/will_cause_import_error/__init__.py0000644000076600000240000000002400000000000032422 0ustar00xistencestaff00000000000000import doesnt.exist
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2211397
venusian-3.0.0/tests/fixtures/importonly/0000755000076600000240000000000000000000000021540 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importonly/__init__.py0000644000076600000240000000001200000000000023642 0ustar00xistencestaff00000000000000# package
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importonly/one.py0000644000076600000240000000032200000000000022670 0ustar00xistencestaff00000000000000from tests.fixtures import decorator

from tests.fixtures.importonly.two import twofunction  # should not be scanned


@decorator(function=True)
def onefunction(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/importonly/two.py0000644000076600000240000000020200000000000022715 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def twofunction(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/inheritance.py0000644000076600000240000000016300000000000022167 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator()
class Parent(object):
    pass


class Child(Parent):
    pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/lifting1.py0000644000076600000240000000121100000000000021406 0ustar00xistencestaff00000000000000from venusian import lift
from tests.fixtures import decorator


class Super1(object):  # pragma: no cover
    @decorator()
    def classname(self):
        pass

    @decorator()
    def boo(self):
        pass

    @decorator()
    def ram(self):
        pass

    def jump(self):
        pass


class Super2(object):  # pragma: no cover
    def boo(self):
        pass

    @decorator()
    def hiss(self):
        pass

    @decorator()
    def jump(self):
        pass


@lift()
class Sub(Super1, Super2):  # pragma: no cover
    def boo(self):
        pass

    def hiss(self):
        pass

    @decorator()
    def smack(self):
        pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/lifting2.py0000644000076600000240000000127500000000000021421 0ustar00xistencestaff00000000000000from venusian import lift, onlyliftedfrom
from tests.fixtures import decorator


@onlyliftedfrom()
class Super1(object):  # pragma: no cover
    @decorator()
    def classname(self):
        pass

    @decorator()
    def boo(self):
        pass

    @decorator()
    def ram(self):
        pass

    def jump(self):
        pass


@onlyliftedfrom()
class Super2(object):  # pragma: no cover
    def boo(self):
        pass

    @decorator()
    def hiss(self):
        pass

    @decorator()
    def jump(self):
        pass


@lift()
class Sub(Super1, Super2):  # pragma: no cover
    def boo(self):
        pass

    def hiss(self):
        pass

    @decorator()
    def smack(self):
        pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/lifting3.py0000644000076600000240000000134500000000000021420 0ustar00xistencestaff00000000000000from venusian import lift, onlyliftedfrom
from tests.fixtures import decorator


@onlyliftedfrom()
class NoDefinitions(object):
    pass


@onlyliftedfrom()
class Super1(object):  # pragma: no cover
    @decorator()
    def classname(self):
        pass

    @decorator()
    def boo(self):
        pass

    @decorator()
    def ram(self):
        pass

    def jump(self):
        pass


class Super2(object):  # pragma: no cover
    def boo(self):
        pass

    @decorator()
    def hiss(self):
        pass

    @decorator()
    def jump(self):
        pass


@lift()
class Sub(Super1, Super2):  # pragma: no cover
    def boo(self):
        pass

    def hiss(self):
        pass

    @decorator()
    def smack(self):
        pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/lifting4.py0000644000076600000240000000075200000000000021422 0ustar00xistencestaff00000000000000from venusian import lift, onlyliftedfrom
from tests.fixtures import (
    categorydecorator,
    categorydecorator2,
)


@onlyliftedfrom()
class Super(object):  # pragma: no cover
    @categorydecorator()
    def hiss(self):
        pass

    @categorydecorator2()
    def jump(self):
        pass


@lift(("mycategory",))
class Sub(Super):  # pragma: no cover
    def hiss(self):
        pass

    def jump(self):
        pass

    @categorydecorator2()
    def smack(self):
        pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/lifting5.py0000644000076600000240000000131500000000000021417 0ustar00xistencestaff00000000000000from venusian import lift
from tests.fixtures import decorator


class Super1(object):  # pragma: no cover
    @decorator()
    def classname(self):
        pass

    @decorator()
    def boo(self):
        pass

    @decorator()
    def ram(self):
        pass

    @decorator()
    def jump(self):
        pass


@lift()
class Super2(Super1):  # pragma: no cover
    @decorator()
    def boo(self):
        pass

    @decorator()
    def hiss(self):
        pass

    @decorator()
    def jump(self):
        pass


@lift()
class Sub(Super2):  # pragma: no cover
    @decorator()
    def boo(self):
        pass

    @decorator()
    def hiss(self):
        pass

    @decorator()
    def smack(self):
        pass
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2215571
venusian-3.0.0/tests/fixtures/nested/0000755000076600000240000000000000000000000020606 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/nested/__init__.py0000644000076600000240000000017700000000000022724 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2219896
venusian-3.0.0/tests/fixtures/nested/sub1/0000755000076600000240000000000000000000000021460 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/nested/sub1/__init__.py0000644000076600000240000000017700000000000023576 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2225685
venusian-3.0.0/tests/fixtures/nested/sub1/subsub1/0000755000076600000240000000000000000000000023044 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/nested/sub1/subsub1/__init__.py0000644000076600000240000000017700000000000025162 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2231627
venusian-3.0.0/tests/fixtures/nested/sub2/0000755000076600000240000000000000000000000021461 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/nested/sub2/__init__.py0000644000076600000240000000017700000000000023577 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1572983402.223799
venusian-3.0.0/tests/fixtures/nested/sub2/subsub2/0000755000076600000240000000000000000000000023046 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/nested/sub2/subsub2/__init__.py0000644000076600000240000000017700000000000025164 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2255776
venusian-3.0.0/tests/fixtures/one/0000755000076600000240000000000000000000000020105 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/one/__init__.py0000644000076600000240000000001200000000000022207 0ustar00xistencestaff00000000000000# package
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/one/module.py0000644000076600000240000000063400000000000021747 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request


class Class(object):
    @decorator(method=True)
    def method(self, request):  # pragma: no cover
        return request


class Instance(object):
    def __call__(self, request):  # pragma: no cover
        return request


inst = Instance()
inst = decorator(instance=True)(inst)
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/one/module2.py0000644000076600000240000000063400000000000022031 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request


class Class(object):
    @decorator(method=True)
    def method(self, request):  # pragma: no cover
        return request


class Instance(object):
    def __call__(self, request):  # pragma: no cover
        return request


inst = Instance()
inst = decorator(instance=True)(inst)
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2266068
venusian-3.0.0/tests/fixtures/pyc/0000755000076600000240000000000000000000000020117 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/pyc/__init__.py0000644000076600000240000000000600000000000022224 0ustar00xistencestaff00000000000000# pkg
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/pyc/module.py0000644000076600000240000000063400000000000021761 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request


class Class(object):
    @decorator(method=True)
    def method(self, request):  # pragma: no cover
        return request


class Instance(object):
    def __call__(self, request):  # pragma: no cover
        return request


inst = Instance()
inst = decorator(instance=True)(inst)
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2273479
venusian-3.0.0/tests/fixtures/pyc/subpackage/0000755000076600000240000000000000000000000022224 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/pyc/subpackage/__init__.py0000644000076600000240000000020200000000000024327 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def pkgfunction(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/subclassing.py0000644000076600000240000000047400000000000022220 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


class Super(object):  # pragma: no cover
    @decorator()
    def classname(self):
        pass

    @decorator()
    def boo(self):
        pass


# the Sub class must not inherit the decorations of its superclass when scanned


class Sub(Super):  # pragma: no cover
    pass
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2288442
venusian-3.0.0/tests/fixtures/subpackages/0000755000076600000240000000000000000000000021614 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/subpackages/__init__.py0000644000076600000240000000017700000000000023732 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator(function=True)
def function(request):  # pragma: no cover
    return request
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2300255
venusian-3.0.0/tests/fixtures/subpackages/childpackage/0000755000076600000240000000000000000000000024213 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/subpackages/childpackage/__init__.py0000644000076600000240000000000000000000000026312 0ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/subpackages/childpackage/will_cause_import_error.py0000644000076600000240000000002400000000000031513 0ustar00xistencestaff00000000000000import doesnt.exist
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/subpackages/mod2.py0000644000076600000240000000002500000000000023024 0ustar00xistencestaff00000000000000raise AttributeError
././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1572983402.2321649
venusian-3.0.0/tests/fixtures/two/0000755000076600000240000000000000000000000020135 5ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/two/__init__.py0000644000076600000240000000000000000000000022234 0ustar00xistencestaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/two/mod1.py0000644000076600000240000000012200000000000021342 0ustar00xistencestaff00000000000000from tests.fixtures import decorator


@decorator()
class Class(object):
    pass
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/two/mod2.py0000644000076600000240000000003000000000000021341 0ustar00xistencestaff00000000000000from .mod1 import Class
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/fixtures/zipped.zip0000644000076600000240000000157400000000000021352 0ustar00xistencestaff00000000000000PK2cOtcmoduleinzip.pyUT	 ȿ]!ȿ]ux
0y
O޽ڥZM;m)!$|#c G<%͢fp:XvA*%T݆pWQ^U5I	E;=/x!g7`OmƥZBNb3kNRi QFk?8`vH9+ˇ6צǴxPK
2cO
packageinzip/UT	 ȿ]$ȿ]uxPK
2cOpackageinzip/__init__.pyUT	 ȿ] ȿ]uxPK
2cO$packageinzip/moduleinpackageinzip.pyUT	 ȿ] ȿ]uxPK2cOtcmoduleinzip.pyUT ȿ]uxPK
2cO
Apackageinzip/UT ȿ]uxPK
2cOGpackageinzip/__init__.pyUT ȿ]uxPK
2cO$packageinzip/moduleinpackageinzip.pyUT ȿ]uxPKo././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/test_advice.py0000644000076600000240000000640200000000000020321 0ustar00xistencestaff00000000000000##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for advice

This module was adapted from 'protocols.tests.advice', part of the Python
Enterprise Application Kit (PEAK).  Please notify the PEAK authors
(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
Zope-specific changes are required, so that the PEAK version of this module
can be kept in sync.

PEAK is a Python application framework that interoperates with (but does
not require) Zope 3 and Twisted.  It provides tools for manipulating UML
models, object-relational persistence, aspect-oriented programming, and more.
Visit the PEAK home page at http://peak.telecommunity.com for more information.

$Id: test_advice.py 40836 2005-12-16 22:40:51Z benji_york $
"""

import unittest
import sys
from venusian import advice

PY3 = sys.version_info[0] >= 3

if not PY3:

    class ClassicClass:
        classLevelFrameInfo = advice.getFrameInfo(sys._getframe())


class NewStyleClass(object):
    classLevelFrameInfo = advice.getFrameInfo(sys._getframe())


moduleLevelFrameInfo = advice.getFrameInfo(sys._getframe())


class FrameInfoTest(unittest.TestCase):

    classLevelFrameInfo = advice.getFrameInfo(sys._getframe())

    def testModuleInfo(self):
        kind, module, f_locals, f_globals, codeinfo = moduleLevelFrameInfo
        self.assertEqual(kind, "module")
        for d in module.__dict__, f_locals, f_globals:
            self.assertTrue(d is globals())
        self.assertEqual(len(codeinfo), 4)

    if not PY3:

        def testClassicClassInfo(self):
            (
                kind,
                module,
                f_locals,
                f_globals,
                codeinfo,
            ) = ClassicClass.classLevelFrameInfo
            self.assertEqual(kind, "class")

            self.assertTrue(f_locals is ClassicClass.__dict__)  # ???
            for d in module.__dict__, f_globals:
                self.assertTrue(d is globals())
            self.assertEqual(len(codeinfo), 4)

    def testNewStyleClassInfo(self):
        (
            kind,
            module,
            f_locals,
            f_globals,
            codeinfo,
        ) = NewStyleClass.classLevelFrameInfo
        self.assertEqual(kind, "class")

        for d in module.__dict__, f_globals:
            self.assertTrue(d is globals())
        self.assertEqual(len(codeinfo), 4)

    def testCallInfo(self):
        (kind, module, f_locals, f_globals, codeinfo) = advice.getFrameInfo(
            sys._getframe()
        )
        self.assertEqual(kind, "function call")
        self.assertTrue(f_locals is locals())  # ???
        for d in module.__dict__, f_globals:
            self.assertTrue(d is globals())
        self.assertEqual(len(codeinfo), 4)
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tests/test_venusian.py0000644000076600000240000011610200000000000020715 0ustar00xistencestaff00000000000000import unittest
import sys
import re
import os
import contextlib


@contextlib.contextmanager
def with_entry_in_sys_path(entry):
    """Context manager that temporarily puts an entry at head of sys.path"""
    sys.path.insert(0, entry)
    yield
    sys.path.remove(entry)


def zip_file_in_sys_path():
    """Context manager that puts zipped.zip at head of sys.path"""
    zip_pkg_path = os.path.join(os.path.dirname(__file__), "fixtures", "zipped.zip")
    return with_entry_in_sys_path(zip_pkg_path)


class _Test(object):
    def __init__(self):
        self.registrations = []

    def __call__(self, **kw):
        self.registrations.append(kw)


class TestScanner(unittest.TestCase):
    def _makeOne(self, **kw):
        from venusian import Scanner

        return Scanner(**kw)

    def test_package(self):
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(one)
        self.assertEqual(len(test.registrations), 6)
        test.registrations.sort(key=lambda x: (x["name"], x["ob"].__module__))
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module2 import function as func2
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module2 import inst as inst2
        from tests.fixtures.one.module import Class as Class1
        from tests.fixtures.one.module2 import Class as Class2

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "Class")
        self.assertEqual(test.registrations[1]["ob"], Class2)
        self.assertEqual(test.registrations[1]["method"], True)

        self.assertEqual(test.registrations[2]["name"], "function")
        self.assertEqual(test.registrations[2]["ob"], func1)
        self.assertEqual(test.registrations[2]["function"], True)

        self.assertEqual(test.registrations[3]["name"], "function")
        self.assertEqual(test.registrations[3]["ob"], func2)
        self.assertEqual(test.registrations[3]["function"], True)

        self.assertEqual(test.registrations[4]["name"], "inst")
        self.assertEqual(test.registrations[4]["ob"], inst1)
        self.assertEqual(test.registrations[4]["instance"], True)

        self.assertEqual(test.registrations[5]["name"], "inst")
        self.assertEqual(test.registrations[5]["ob"], inst2)
        self.assertEqual(test.registrations[5]["instance"], True)

    def test_module_in_zip(self):
        with zip_file_in_sys_path():
            import moduleinzip
        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(moduleinzip)
        self.assertEqual(len(test.registrations), 3)
        test.registrations.sort(key=lambda x: (x["name"], x["ob"].__module__))
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module2 import function as func2
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module2 import inst as inst2
        from tests.fixtures.one.module import Class as Class1
        from tests.fixtures.one.module2 import Class as Class2

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], moduleinzip.Class)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], moduleinzip.function)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], moduleinzip.inst)
        self.assertEqual(test.registrations[2]["instance"], True)

    def test_package_in_zip(self):
        with zip_file_in_sys_path():
            import packageinzip
        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(packageinzip)

    def test_package_with_orphaned_pyc_file(self):
        # There is a module2.pyc file in the "pycfixtures" package; it
        # has no corresponding .py source file.  Such orphaned .pyc
        # files should be ignored during scanning.
        from tests.fixtures import pyc

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(pyc)
        self.assertEqual(len(test.registrations), 4)
        test.registrations.sort(key=lambda x: (x["name"], x["ob"].__module__))
        from tests.fixtures.pyc.module import function as func1
        from tests.fixtures.pyc.module import inst as inst1
        from tests.fixtures.pyc.module import Class as Class1
        from tests.fixtures.pyc import subpackage

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], inst1)
        self.assertEqual(test.registrations[2]["instance"], True)

        self.assertEqual(test.registrations[3]["name"], "pkgfunction")
        self.assertEqual(test.registrations[3]["ob"], subpackage.pkgfunction)
        self.assertEqual(test.registrations[3]["function"], True)

    def test_module(self):
        from tests.fixtures.one import module

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(module)
        self.assertEqual(len(test.registrations), 3)
        test.registrations.sort(key=lambda x: (x["name"], x["ob"].__module__))
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module import Class as Class1

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], inst1)
        self.assertEqual(test.registrations[2]["instance"], True)

    def test_ignore_imported(self):
        # even though "twofunction" is imported into "one", it should not
        # be registered, because it's only imported in one and not defined
        # there
        from tests.fixtures.importonly import one
        from tests.fixtures.importonly import two

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(one)
        self.assertEqual(len(test.registrations), 1)
        scanner.scan(two)
        self.assertEqual(len(test.registrations), 2)

    def test_dont_ignore_legit_decorators(self):
        # make sure venusian picks up other decorated things from
        # imported modules when the whole package is scanned
        from tests.fixtures import import_and_scan

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(import_and_scan)
        self.assertEqual(len(test.registrations), 2)

    def test_one_category(self):
        from tests.fixtures import category

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(category, categories=("mycategory",))
        self.assertEqual(len(test.registrations), 1)
        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], category.function)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_all_categories_implicit(self):
        from tests.fixtures import category

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(category)
        self.assertEqual(len(test.registrations), 2)
        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], category.function)
        self.assertEqual(test.registrations[0]["function"], True)
        self.assertEqual(test.registrations[1]["name"], "function2")
        self.assertEqual(test.registrations[1]["ob"], category.function2)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_all_categories_explicit(self):
        from tests.fixtures import category

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(category, categories=("mycategory", "mycategory2"))
        self.assertEqual(len(test.registrations), 2)
        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], category.function)
        self.assertEqual(test.registrations[0]["function"], True)
        self.assertEqual(test.registrations[1]["name"], "function2")
        self.assertEqual(test.registrations[1]["ob"], category.function2)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_decorations_arent_inherited(self):
        from tests.fixtures import inheritance

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(inheritance)
        self.assertEqual(
            test.registrations, [dict(name="Parent", ob=inheritance.Parent),]
        )

    def test_classdecorator(self):
        from tests.fixtures import classdecorator

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(classdecorator)
        test.registrations.sort(key=lambda x: (x["name"], x["ob"].__module__))
        self.assertEqual(len(test.registrations), 2)
        self.assertEqual(test.registrations[0]["name"], "SubClass")
        self.assertEqual(test.registrations[0]["ob"], classdecorator.SubClass)
        self.assertEqual(test.registrations[0]["subclass"], True)
        self.assertEqual(test.registrations[1]["name"], "SuperClass")
        self.assertEqual(test.registrations[1]["ob"], classdecorator.SuperClass)
        self.assertEqual(test.registrations[1]["superclass"], True)

    def test_class_and_method_decorator(self):
        from tests.fixtures import class_and_method

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(class_and_method)
        self.assertEqual(len(test.registrations), 2)
        self.assertEqual(test.registrations[0]["name"], "ClassWithMethod")
        self.assertEqual(test.registrations[0]["ob"], class_and_method.ClassWithMethod)
        self.assertEqual(test.registrations[0]["method"], True)
        self.assertEqual(test.registrations[1]["name"], "ClassWithMethod")
        self.assertEqual(test.registrations[1]["ob"], class_and_method.ClassWithMethod)
        self.assertEqual(test.registrations[1]["class_"], True)

    def test_scan_only_finds_classdecoration_once(self):
        from tests.fixtures import two
        from tests.fixtures.two.mod1 import Class

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(two)
        self.assertEqual(test.registrations, [dict(name="Class", ob=Class),])

    def test_importerror_during_scan_default_onerror(self):
        from tests.fixtures import importerror

        test = _Test()
        scanner = self._makeOne(test=test)
        # without a custom onerror, scan will propagate the importerror from
        # will_cause_import_error
        self.assertRaises(ImportError, scanner.scan, importerror)

    def test_importerror_during_scan_default_onerror_with_ignore(self):
        from tests.fixtures import importerror

        test = _Test()
        scanner = self._makeOne(test=test)
        # scan will ignore the errors from will_cause_import_error due
        # to us choosing to ignore that package
        scanner.scan(
            importerror, ignore="tests.fixtures.importerror.will_cause_import_error"
        )

    def test_importerror_during_scan_custom_onerror(self):
        from tests.fixtures import importerror

        test = _Test()
        scanner = self._makeOne(test=test)
        # with this custom onerror, scan will not propagate the importerror
        # from will_raise_importerror
        def onerror(name):
            if not issubclass(sys.exc_info()[0], ImportError):
                raise

        scanner.scan(importerror, onerror=onerror)
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.importerror import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_importerror_in_package_during_scan_custom_onerror(self):
        from tests.fixtures import importerror_package

        md("tests.fixtures.importerror_package.will_cause_import_error")
        test = _Test()
        scanner = self._makeOne(test=test)
        # with this custom onerror, scan will not propagate the importerror
        # from will_raise_importerror
        def onerror(name):
            raise ValueError

        self.assertRaises(
            ValueError, scanner.scan, importerror_package, onerror=onerror
        )
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.importerror_package import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_attrerror_during_scan_custom_onerror(self):
        from tests.fixtures import attrerror

        test = _Test()
        scanner = self._makeOne(test=test)
        # with this custom onerror, scan will not propagate the importerror
        # from will_raise_importerror
        def onerror(name):
            if not issubclass(sys.exc_info()[0], ImportError):
                raise

        self.assertRaises(AttributeError, scanner.scan, attrerror, onerror=onerror)
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.attrerror import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_attrerror_in_package_during_scan_custom_onerror(self):
        from tests.fixtures import attrerror_package

        md("tests.fixtures.attrerror_package.will_cause_import_error")
        test = _Test()
        scanner = self._makeOne(test=test)
        # with this custom onerror, scan will not propagate the importerror
        # from will_raise_importerror
        def onerror(name):
            if not issubclass(sys.exc_info()[0], ImportError):
                raise

        self.assertRaises(
            AttributeError, scanner.scan, attrerror_package, onerror=onerror
        )
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.attrerror_package import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_attrerror_in_package_during_scan_no_custom_onerror(self):
        from tests.fixtures import attrerror_package

        md("tests.fixtures.attrerror_package.will_cause_import_error")
        test = _Test()
        scanner = self._makeOne(test=test)
        self.assertRaises(AttributeError, scanner.scan, attrerror_package)
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.attrerror_package import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_onerror_used_to_swallow_all_exceptions(self):
        from tests.fixtures import subpackages

        test = _Test()
        scanner = self._makeOne(test=test)
        # onerror can also be used to skip errors while scanning submodules
        # e.g.: test modules under a given library
        swallowed = []

        def ignore_child(name):
            swallowed.append(name)

        scanner.scan(subpackages, onerror=ignore_child)
        self.assertEqual(
            swallowed,
            [
                "tests.fixtures.subpackages.childpackage.will_cause_import_error",
                "tests.fixtures.subpackages.mod2",
            ],
        )
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.subpackages import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_ignore_by_full_dotted_name(self):
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(one, ignore=["tests.fixtures.one.module2"])
        self.assertEqual(len(test.registrations), 3)
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module import Class as Class1

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], inst1)
        self.assertEqual(test.registrations[2]["instance"], True)

    def test_ignore_by_full_dotted_name2(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(nested, ignore=["tests.fixtures.nested.sub1"])
        self.assertEqual(len(test.registrations), 3)
        from tests.fixtures.nested import function as func1
        from tests.fixtures.nested.sub2 import function as func2
        from tests.fixtures.nested.sub2.subsub2 import function as func3

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func2)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "function")
        self.assertEqual(test.registrations[2]["ob"], func3)
        self.assertEqual(test.registrations[2]["function"], True)

    def test_ignore_by_full_dotted_name3(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            nested, ignore=["tests.fixtures.nested.sub1", "tests.fixtures.nested.sub2"]
        )
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.nested import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_ignore_by_full_dotted_name4(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            nested,
            ignore=["tests.fixtures.nested.sub1", "tests.fixtures.nested.function"],
        )
        self.assertEqual(len(test.registrations), 2)
        from tests.fixtures.nested.sub2 import function as func2
        from tests.fixtures.nested.sub2.subsub2 import function as func3

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func2)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func3)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_ignore_by_relative_dotted_name(self):
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(one, ignore=[".module2"])
        self.assertEqual(len(test.registrations), 3)
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module import Class as Class1

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], inst1)
        self.assertEqual(test.registrations[2]["instance"], True)

    def test_ignore_by_relative_dotted_name2(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(nested, ignore=[".sub1"])
        self.assertEqual(len(test.registrations), 3)
        from tests.fixtures.nested import function as func1
        from tests.fixtures.nested.sub2 import function as func2
        from tests.fixtures.nested.sub2.subsub2 import function as func3

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func2)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "function")
        self.assertEqual(test.registrations[2]["ob"], func3)
        self.assertEqual(test.registrations[2]["function"], True)

    def test_ignore_by_relative_dotted_name3(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(nested, ignore=[".sub1", ".sub2"])
        self.assertEqual(len(test.registrations), 1)
        from tests.fixtures.nested import function as func1

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

    def test_ignore_by_relative_dotted_name4(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(nested, ignore=[".sub1", ".function"])
        self.assertEqual(len(test.registrations), 2)
        from tests.fixtures.nested.sub2 import function as func2
        from tests.fixtures.nested.sub2.subsub2 import function as func3

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func2)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func3)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_ignore_by_function(self):
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            one, ignore=[re.compile("Class").search, re.compile("inst").search]
        )
        self.assertEqual(len(test.registrations), 2)
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module2 import function as func2

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func1)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func2)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_ignore_by_function_nested(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(nested, ignore=[re.compile(".function$").search])
        self.assertEqual(len(test.registrations), 0)

    def test_ignore_by_function_nested2(self):
        from tests.fixtures import nested

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            nested,
            ignore=[re.compile("sub2$").search, re.compile("nested.function$").search],
        )
        self.assertEqual(len(test.registrations), 2)

        from tests.fixtures.nested.sub1 import function as func2
        from tests.fixtures.nested.sub1.subsub1 import function as func3

        self.assertEqual(test.registrations[0]["name"], "function")
        self.assertEqual(test.registrations[0]["ob"], func2)
        self.assertEqual(test.registrations[0]["function"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func3)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_ignore_as_string(self):
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(one, ignore="tests.fixtures.one.module2")
        self.assertEqual(len(test.registrations), 3)
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module import inst as inst1
        from tests.fixtures.one.module import Class as Class1

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

        self.assertEqual(test.registrations[2]["name"], "inst")
        self.assertEqual(test.registrations[2]["ob"], inst1)
        self.assertEqual(test.registrations[2]["instance"], True)

    def test_ignore_mixed_string_and_func(self):
        import re
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            one, ignore=["tests.fixtures.one.module2", re.compile("inst").search]
        )
        self.assertEqual(len(test.registrations), 2)
        from tests.fixtures.one.module import function as func1
        from tests.fixtures.one.module import Class as Class1

        self.assertEqual(test.registrations[0]["name"], "Class")
        self.assertEqual(test.registrations[0]["ob"], Class1)
        self.assertEqual(test.registrations[0]["method"], True)

        self.assertEqual(test.registrations[1]["name"], "function")
        self.assertEqual(test.registrations[1]["ob"], func1)
        self.assertEqual(test.registrations[1]["function"], True)

    def test_ignore_mixed_string_abs_rel_and_func(self):
        import re
        from tests.fixtures import one

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(
            one,
            ignore=["tests.fixtures.one.module2", ".module", re.compile("inst").search],
        )
        self.assertEqual(len(test.registrations), 0)

    def test_lifting1(self):
        from tests.fixtures import lifting1

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(lifting1)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 11)

        self.assertEqual(test.registrations[0]["attr"], "boo")
        self.assertEqual(test.registrations[0]["name"], "Sub")
        self.assertEqual(test.registrations[0]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[1]["attr"], "classname")
        self.assertEqual(test.registrations[1]["name"], "Sub")
        self.assertEqual(test.registrations[1]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[2]["attr"], "hiss")
        self.assertEqual(test.registrations[2]["name"], "Sub")
        self.assertEqual(test.registrations[2]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[3]["attr"], "jump")
        self.assertEqual(test.registrations[3]["name"], "Sub")
        self.assertEqual(test.registrations[3]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[4]["attr"], "ram")
        self.assertEqual(test.registrations[4]["name"], "Sub")
        self.assertEqual(test.registrations[4]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[5]["attr"], "smack")
        self.assertEqual(test.registrations[5]["name"], "Sub")
        self.assertEqual(test.registrations[5]["ob"], lifting1.Sub)

        self.assertEqual(test.registrations[6]["attr"], "boo")
        self.assertEqual(test.registrations[6]["name"], "Super1")
        self.assertEqual(test.registrations[6]["ob"], lifting1.Super1)

        self.assertEqual(test.registrations[7]["attr"], "classname")
        self.assertEqual(test.registrations[7]["name"], "Super1")
        self.assertEqual(test.registrations[7]["ob"], lifting1.Super1)

        self.assertEqual(test.registrations[8]["attr"], "ram")
        self.assertEqual(test.registrations[8]["name"], "Super1")
        self.assertEqual(test.registrations[8]["ob"], lifting1.Super1)

        self.assertEqual(test.registrations[9]["attr"], "hiss")
        self.assertEqual(test.registrations[9]["name"], "Super2")
        self.assertEqual(test.registrations[9]["ob"], lifting1.Super2)

        self.assertEqual(test.registrations[10]["attr"], "jump")
        self.assertEqual(test.registrations[10]["name"], "Super2")
        self.assertEqual(test.registrations[10]["ob"], lifting1.Super2)

    def test_lifting2(self):
        from tests.fixtures import lifting2

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(lifting2)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 6)

        self.assertEqual(test.registrations[0]["attr"], "boo")
        self.assertEqual(test.registrations[0]["name"], "Sub")
        self.assertEqual(test.registrations[0]["ob"], lifting2.Sub)

        self.assertEqual(test.registrations[1]["attr"], "classname")
        self.assertEqual(test.registrations[1]["name"], "Sub")
        self.assertEqual(test.registrations[1]["ob"], lifting2.Sub)

        self.assertEqual(test.registrations[2]["attr"], "hiss")
        self.assertEqual(test.registrations[2]["name"], "Sub")
        self.assertEqual(test.registrations[2]["ob"], lifting2.Sub)

        self.assertEqual(test.registrations[3]["attr"], "jump")
        self.assertEqual(test.registrations[3]["name"], "Sub")
        self.assertEqual(test.registrations[3]["ob"], lifting2.Sub)

        self.assertEqual(test.registrations[4]["attr"], "ram")
        self.assertEqual(test.registrations[4]["name"], "Sub")
        self.assertEqual(test.registrations[4]["ob"], lifting2.Sub)

        self.assertEqual(test.registrations[5]["attr"], "smack")
        self.assertEqual(test.registrations[5]["name"], "Sub")
        self.assertEqual(test.registrations[5]["ob"], lifting2.Sub)

    def test_lifting3(self):
        from tests.fixtures import lifting3

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(lifting3)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 8)

        self.assertEqual(test.registrations[0]["attr"], "boo")
        self.assertEqual(test.registrations[0]["name"], "Sub")
        self.assertEqual(test.registrations[0]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[1]["attr"], "classname")
        self.assertEqual(test.registrations[1]["name"], "Sub")
        self.assertEqual(test.registrations[1]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[2]["attr"], "hiss")
        self.assertEqual(test.registrations[2]["name"], "Sub")
        self.assertEqual(test.registrations[2]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[3]["attr"], "jump")
        self.assertEqual(test.registrations[3]["name"], "Sub")
        self.assertEqual(test.registrations[3]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[4]["attr"], "ram")
        self.assertEqual(test.registrations[4]["name"], "Sub")
        self.assertEqual(test.registrations[4]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[5]["attr"], "smack")
        self.assertEqual(test.registrations[5]["name"], "Sub")
        self.assertEqual(test.registrations[5]["ob"], lifting3.Sub)

        self.assertEqual(test.registrations[6]["attr"], "hiss")
        self.assertEqual(test.registrations[6]["name"], "Super2")
        self.assertEqual(test.registrations[6]["ob"], lifting3.Super2)

        self.assertEqual(test.registrations[7]["attr"], "jump")
        self.assertEqual(test.registrations[7]["name"], "Super2")
        self.assertEqual(test.registrations[7]["ob"], lifting3.Super2)

    def test_lifting4(self):
        from tests.fixtures import lifting4

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(lifting4)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 2)

        self.assertEqual(test.registrations[0]["attr"], "hiss")
        self.assertEqual(test.registrations[0]["name"], "Sub")
        self.assertEqual(test.registrations[0]["ob"], lifting4.Sub)

        self.assertEqual(test.registrations[1]["attr"], "smack")
        self.assertEqual(test.registrations[1]["name"], "Sub")
        self.assertEqual(test.registrations[1]["ob"], lifting4.Sub)

    def test_lifting5(self):
        from tests.fixtures import lifting5

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(lifting5)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 15)

        self.assertEqual(test.registrations[0]["attr"], "boo")
        self.assertEqual(test.registrations[0]["name"], "Sub")
        self.assertEqual(test.registrations[0]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[1]["attr"], "classname")
        self.assertEqual(test.registrations[1]["name"], "Sub")
        self.assertEqual(test.registrations[1]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[2]["attr"], "hiss")
        self.assertEqual(test.registrations[2]["name"], "Sub")
        self.assertEqual(test.registrations[2]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[3]["attr"], "jump")
        self.assertEqual(test.registrations[3]["name"], "Sub")
        self.assertEqual(test.registrations[3]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[4]["attr"], "ram")
        self.assertEqual(test.registrations[4]["name"], "Sub")
        self.assertEqual(test.registrations[4]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[5]["attr"], "smack")
        self.assertEqual(test.registrations[5]["name"], "Sub")
        self.assertEqual(test.registrations[5]["ob"], lifting5.Sub)

        self.assertEqual(test.registrations[6]["attr"], "boo")
        self.assertEqual(test.registrations[6]["name"], "Super1")
        self.assertEqual(test.registrations[6]["ob"], lifting5.Super1)

        self.assertEqual(test.registrations[7]["attr"], "classname")
        self.assertEqual(test.registrations[7]["name"], "Super1")
        self.assertEqual(test.registrations[7]["ob"], lifting5.Super1)

        self.assertEqual(test.registrations[8]["attr"], "jump")
        self.assertEqual(test.registrations[8]["name"], "Super1")
        self.assertEqual(test.registrations[8]["ob"], lifting5.Super1)

        self.assertEqual(test.registrations[9]["attr"], "ram")
        self.assertEqual(test.registrations[9]["name"], "Super1")
        self.assertEqual(test.registrations[9]["ob"], lifting5.Super1)

        self.assertEqual(test.registrations[10]["attr"], "boo")
        self.assertEqual(test.registrations[10]["name"], "Super2")
        self.assertEqual(test.registrations[10]["ob"], lifting5.Super2)

        self.assertEqual(test.registrations[11]["attr"], "classname")
        self.assertEqual(test.registrations[11]["name"], "Super2")
        self.assertEqual(test.registrations[11]["ob"], lifting5.Super2)

        self.assertEqual(test.registrations[12]["attr"], "hiss")
        self.assertEqual(test.registrations[12]["name"], "Super2")
        self.assertEqual(test.registrations[12]["ob"], lifting5.Super2)

        self.assertEqual(test.registrations[13]["attr"], "jump")
        self.assertEqual(test.registrations[13]["name"], "Super2")
        self.assertEqual(test.registrations[13]["ob"], lifting5.Super2)

        self.assertEqual(test.registrations[14]["attr"], "ram")
        self.assertEqual(test.registrations[14]["name"], "Super2")
        self.assertEqual(test.registrations[14]["ob"], lifting5.Super2)

    def test_subclassing(self):
        from tests.fixtures import subclassing

        test = _Test()
        scanner = self._makeOne(test=test)
        scanner.scan(subclassing)
        test.registrations.sort(
            key=lambda x: (x["name"], x["attr"], x["ob"].__module__)
        )
        self.assertEqual(len(test.registrations), 2)

        self.assertEqual(test.registrations[0]["attr"], "boo")
        self.assertEqual(test.registrations[0]["name"], "Super")
        self.assertEqual(test.registrations[0]["ob"], subclassing.Super)

        self.assertEqual(test.registrations[1]["attr"], "classname")
        self.assertEqual(test.registrations[1]["name"], "Super")
        self.assertEqual(test.registrations[1]["ob"], subclassing.Super)


class Test_lift(unittest.TestCase):
    def _makeOne(self, categories=None):
        from venusian import lift

        return lift(categories)

    def test_not_class(self):
        inst = self._makeOne()
        self.assertRaises(RuntimeError, inst, None)


class Test_onlyliftedfrom(unittest.TestCase):
    def _makeOne(self):
        from venusian import onlyliftedfrom

        return onlyliftedfrom()

    def test_not_class(self):
        inst = self._makeOne()
        self.assertRaises(RuntimeError, inst, None)


def md(name):  # pragma: no cover
    if name in sys.modules:
        del sys.modules[name]
././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1572983294.0
venusian-3.0.0/tox.ini0000644000076600000240000000270200000000000015625 0ustar00xistencestaff00000000000000[tox]
envlist =
    lint,
    py35,py36,py37,py38,pypy3,
    docs,
    coverage
isolated_build = True

[testenv]
commands =
    pytest {posargs:}
extras =
    testing
setenv =
    COVERAGE_FILE=.coverage.{envname}

[testenv:coverage]
basepython = python3.8
commands =
    coverage combine
    coverage xml
    coverage report --show-missing
deps =
    coverage
setenv =
    COVERAGE_FILE=.coverage

[testenv:lint]
skip_install = True
basepython = python3.8
commands =
    black --check --diff .
    check-manifest
    # build sdist/wheel
    python -m pep517.build .
    twine check dist/*
deps =
    black
    readme_renderer
    check-manifest
    pep517
    twine

[testenv:docs]
whitelist_externals =
    make
commands =
    make -C docs html BUILDDIR={envdir} SPHINXOPTS="-W -E"
extras =
    docs

[testenv:run-flake8]
skip_install = True
basepython = python3.8
commands =
    flake8 src/requests_safe/ tests
deps =
    flake8
    flake8-bugbear

[testenv:run-black]
skip_install = True
basepython = python3.8
commands =
    black .
deps =
    black

[testenv:build]
skip_install = true
basepython = python3.8
commands =
    # clean up build/ and dist/ folders
    python -c 'import shutil; shutil.rmtree("build", ignore_errors=True)'
    # Make sure we aren't forgetting anything
    check-manifest
    # build sdist/wheel
    python -m pep517.build .
    # Verify all is well
    twine check dist/*

deps =
    readme_renderer
    check-manifest
    pep517
    twine