././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8670142 venusian-3.1.1/0000755000175000001440000000000014723213705012614 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/.coveragerc0000644000175000001440000000025314723200770014733 0ustar00chrismusers[run] parallel = true source = venusian [paths] source = src/venusian */src/venusian */site-packages/venusian [report] show_missing = true precision = 2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8520138 venusian-3.1.1/.github/0000755000175000001440000000000014723213705014154 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/.github/dependabot.yml0000644000175000001440000000033214723200770017000 0ustar00chrismusers# Set update schedule for GitHub Actions version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: # Check for updates to GitHub Actions every weekday interval: "daily" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8520138 venusian-3.1.1/.github/workflows/0000755000175000001440000000000014723213705016211 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733103549.0 venusian-3.1.1/.github/workflows/ci-tests.yml0000644000175000001440000000515314723207675020504 0ustar00chrismusersname: Build and test on: # Only on pushes to master or one of the release branches we build on push push: branches: - main - "[0-9].[0-9]+-branch" tags: - '**' # Build pull requests pull_request: jobs: test: strategy: matrix: py: - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "pypy-3.8" os: - "ubuntu-latest" - "windows-latest" - "macos-12" # (later seem to fail building python) architecture: - x64 - x86 exclude: # Linux and macOS don't have x86 python - os: "ubuntu-latest" architecture: x86 - os: "macos-12" architecture: x86 name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Setup python uses: actions/setup-python@v4 with: python-version: ${{ matrix.py }} architecture: ${{ matrix.architecture }} - run: pip install tox - name: Running tox run: tox -e py coverage: runs-on: ubuntu-latest name: Validate coverage steps: - uses: actions/checkout@v4 - name: Setup python 3.9 uses: actions/setup-python@v4 with: python-version: 3.9 architecture: x64 - run: pip install tox - run: tox -e py39,coverage docs: runs-on: ubuntu-latest name: Build the documentation steps: - uses: actions/checkout@v4 - name: Setup python uses: actions/setup-python@v4 with: python-version: 3.9 architecture: x64 - run: pip install tox - run: tox -e docs lint: runs-on: ubuntu-latest name: Lint the package steps: - uses: actions/checkout@v4 - name: Setup python uses: actions/setup-python@v4 with: python-version: 3.9 architecture: x64 - run: pip install tox - run: tox -e lint ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/.readthedocs.yaml0000644000175000001440000000123314723200770016040 0ustar00chrismusers# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.10" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF formats: - pdf # Optionally declare the Python requirements required to build your docs python: install: - method: pip path: . extra_requirements: - docs - method: setuptools path: . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733104934.0 venusian-3.1.1/CHANGES.rst0000644000175000001440000001767514723212446014437 0ustar00chrismusers3.1.1 (2024-12-01) ------------------ - Add support for Python 3.13 (thanks to musicinmybrain). - Fix GitHub test actions. 3.1.0 (2023-11-06) ------------------ - Remove support for Python 3.5 and 3.6 - Add support for Python 3.9, 3.10, 3.11 and 3.12. - Use GitHub Actions instead of Travis. 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/CONTRIBUTING.rst0000644000175000001440000000575214723200770015264 0ustar00chrismusers.. highlight:: shell ============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/Pylons/venusian/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ venusian could always use more documentation, whether as part of the official venusian docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://github.com/Pylons/venusian/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `venusian` for local development. 1. Fork the `venusian` repo on GitHub. 2. Clone your fork locally:: $ git clone git@github.com:your_name_here/venusian.git 3. Install your local copy into a virtualenv:: $ python3 -m venv env $ env/bin/pip install -e .[docs,testing] $ env/bin/pip install tox 4. Create a branch for local development:: $ git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ env/bin/tox 6. Commit your changes and push your branch to GitHub:: $ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature 7. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for all supported versions of Python (see the `classifiers` section in https://github.com/Pylons/venusian/blob/main/setup.cfg). Verify that the "All checks have passed" flag is green on the "Conversation" page of the PR before merging. Tips ---- To run a subset of tests:: $ env/bin/py.test tests.test_venusian ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/CONTRIBUTORS.txt0000644000175000001440000001140714723200770015313 0ustar00chrismusersPylons 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 - Gouji Ochiai, 2022-12-23 - Florian Schulze, 2023-10-31 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/COPYRIGHT.txt0000644000175000001440000000015514723200770014724 0ustar00chrismusersCopyright (c) 2011 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/LICENSE.txt0000644000175000001440000000337714723200770014447 0ustar00chrismusersLicense 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/MANIFEST.in0000644000175000001440000000053514723200770014353 0ustar00chrismusersgraft src/venusian graft docs prune docs/_build graft tests include README.rst include CHANGES.rst include CONTRIBUTING.rst include CONTRIBUTORS.txt include LICENSE.txt include COPYRIGHT.txt include .coveragerc pyproject.toml setup.cfg include tox.ini .readthedocs.yaml graft .github global-exclude __pycache__ *.py[cod] global-exclude .DS_Store ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8670142 venusian-3.1.1/PKG-INFO0000644000175000001440000002414514723213705013717 0ustar00chrismusersMetadata-Version: 2.1 Name: venusian Version: 3.1.1 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 (Repoze) Keywords: web wsgi zope Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: License :: Repoze Public License Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE.txt Provides-Extra: testing Requires-Dist: pytest; extra == "testing" Requires-Dist: pytest-cov; extra == "testing" Requires-Dist: coverage; extra == "testing" Provides-Extra: docs Requires-Dist: Sphinx>=4.3.2; extra == "docs" Requires-Dist: repoze.sphinx.autointerface; extra == "docs" Requires-Dist: pylons-sphinx-themes; extra == "docs" Requires-Dist: sphinx-copybutton; extra == "docs" venusian ======== .. image:: https://github.com/Pylons/venusian/workflows/Build%20and%20test/badge.svg :target: https://github.com/Pylons/venusian/actions?query=workflow%3A%22Build+and+test%22 .. 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.1.1 (2024-12-01) ------------------ - Add support for Python 3.13 (thanks to musicinmybrain). - Fix GitHub test actions. 3.1.0 (2023-11-06) ------------------ - Remove support for Python 3.5 and 3.6 - Add support for Python 3.9, 3.10, 3.11 and 3.12. - Use GitHub Actions instead of Travis. 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/README.rst0000644000175000001440000000141314723200770014300 0ustar00chrismusersvenusian ======== .. image:: https://github.com/Pylons/venusian/workflows/Build%20and%20test/badge.svg :target: https://github.com/Pylons/venusian/actions?query=workflow%3A%22Build+and+test%22 .. 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/ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.853014 venusian-3.1.1/docs/0000755000175000001440000000000014723213705013544 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/docs/Makefile0000644000175000001440000000561614723200770015212 0ustar00chrismusers# Makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -W SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/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 $(BUILDDIR)/* html: mkdir -p $(BUILDDIR)/html $(BUILDDIR)/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." text: mkdir -p $(BUILDDIR)/text $(BUILDDIR)/doctrees $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/text." pickle: mkdir -p $(BUILDDIR)/pickle $(BUILDDIR)/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files or run" @echo " sphinx-web $(BUILDDIR)/pickle" @echo "to start the sphinx-web server." web: pickle htmlhelp: mkdir -p $(BUILDDIR)/htmlhelp $(BUILDDIR)/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." latex: mkdir -p $(BUILDDIR)/latex $(BUILDDIR)/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex cp _static/*.png $(BUILDDIR)/latex ./convert_images.sh cp _static/latex-warning.png $(BUILDDIR)/latex cp _static/latex-note.png $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p $(BUILDDIR)/changes $(BUILDDIR)/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: mkdir -p $(BUILDDIR)/linkcheck $(BUILDDIR)/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/docs/api.rst0000644000175000001440000000045314723200770015047 0ustar00chrismusersAPI Documentation for Venusian ============================== .. automodule:: venusian .. autoclass:: Scanner .. automethod:: scan .. autoclass:: AttachInfo .. autofunction:: attach(wrapped, callback, category=None, name=None) .. autoclass:: lift .. autoclass:: onlyliftedfrom ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/docs/conf.py0000644000175000001440000000573514723200770015053 0ustar00chrismusers# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath(".")) import datetime import pkg_resources import pylons_sphinx_themes # -- 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", "sphinx_copybutton", ] # General substitutions. author = "Pylons Project" 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 # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "pylons" html_theme_path = pylons_sphinx_themes.get_html_themes_path() html_theme_options = dict( github_url="https://github.com/Pylons/venusian", canonical_url="https://docs.pylonsproject.org/projects/venusian/en/latest/", ) # 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'] # Control display of sidebars html_sidebars = { "**": [ "localtoc.html", "ethicalads.html", "relations.html", "sourcelink.html", "searchbox.html", ] } # 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" # Do not use smart quotes. smartquotes = False # Output file base name for HTML help builder. htmlhelp_basename = "atemplatedoc" # -- Options for LaTeX output ------------------------------------------------- # 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"), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/docs/glossary.rst0000644000175000001440000000052714723200770016143 0ustar00chrismusers.. _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 `. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/docs/index.rst0000644000175000001440000004740414723200770015414 0ustar00chrismusers.. _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.7+. It is also known to work on PyPy (Compatible with Python 3.7+). .. 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 conscientious 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(None) {'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` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/pyproject.toml0000644000175000001440000000067414723200770015535 0ustar00chrismusers[build-system] requires = ["setuptools >= 40.9.0"] build-backend = "setuptools.build_meta" [tool.black] target-version = ['py37', 'py38', 'py39', 'py310'] exclude = ''' /( \.git | .tox | build )/ ''' # This next section only exists for people that have their editors # automatically call isort, black already sorts entries on its own when run. [tool.isort] profile = "black" src_paths = ["src", "tests"] known_first_party = "venusian" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.868014 venusian-3.1.1/setup.cfg0000644000175000001440000000262114723213705014436 0ustar00chrismusers[metadata] name = venusian version = 3.1.1 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_files = LICENSE.txt license = BSD-derived (Repoze) classifiers = Development Status :: 6 - Mature Intended Audience :: Developers Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy License :: Repoze Public License url = https://pylonsproject.org/ author = Chris McDonough, Agendaless Consulting author_email = pylons-devel@googlegroups.com [options] package_dir = =src packages = find: python_requires = >=3.7 [options.packages.find] where = src [options.extras_require] testing = pytest pytest-cov coverage docs = Sphinx>=4.3.2 repoze.sphinx.autointerface pylons-sphinx-themes sphinx-copybutton [bdist_wheel] universal = 0 [tool:pytest] python_files = test_*.py testpaths = tests addopts = -W always --cov --cov-report=term-missing --ignore=tests/fixtures/ [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/setup.py0000644000175000001440000000014714723200770014326 0ustar00chrismusers""" Necessary for pip install -e, and python setup.py check """ from setuptools import setup setup() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8450139 venusian-3.1.1/src/0000755000175000001440000000000014723213705013403 5ustar00chrismusers././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.854014 venusian-3.1.1/src/venusian/0000755000175000001440000000000014723213705015233 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/src/venusian/__init__.py0000644000175000001440000005344114723200770017351 0ustar00chrismusersimport sys from inspect import getmembers, getmro, isclass from pkgutil import iter_modules from venusian.advice import getFrameInfo from venusian.compat import compat_find_loader 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 = compat_find_loader(importer, 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/src/venusian/advice.py0000644000175000001440000000545014723200770017042 0ustar00chrismusers############################################################################## # # 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/src/venusian/compat.py0000644000175000001440000000044014723200770017064 0ustar00chrismusersimport sys if sys.version_info[0] == 3 and sys.version_info[1] < 10: def compat_find_loader(importer, modname): return importer.find_module(modname) else: def compat_find_loader(importer, modname): spec = importer.find_spec(modname) return spec.loader ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.866014 venusian-3.1.1/src/venusian.egg-info/0000755000175000001440000000000014723213705016725 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105604.0 venusian-3.1.1/src/venusian.egg-info/PKG-INFO0000644000175000001440000002414514723213704020027 0ustar00chrismusersMetadata-Version: 2.1 Name: venusian Version: 3.1.1 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 (Repoze) Keywords: web wsgi zope Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: License :: Repoze Public License Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE.txt Provides-Extra: testing Requires-Dist: pytest; extra == "testing" Requires-Dist: pytest-cov; extra == "testing" Requires-Dist: coverage; extra == "testing" Provides-Extra: docs Requires-Dist: Sphinx>=4.3.2; extra == "docs" Requires-Dist: repoze.sphinx.autointerface; extra == "docs" Requires-Dist: pylons-sphinx-themes; extra == "docs" Requires-Dist: sphinx-copybutton; extra == "docs" venusian ======== .. image:: https://github.com/Pylons/venusian/workflows/Build%20and%20test/badge.svg :target: https://github.com/Pylons/venusian/actions?query=workflow%3A%22Build+and+test%22 .. 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.1.1 (2024-12-01) ------------------ - Add support for Python 3.13 (thanks to musicinmybrain). - Fix GitHub test actions. 3.1.0 (2023-11-06) ------------------ - Remove support for Python 3.5 and 3.6 - Add support for Python 3.9, 3.10, 3.11 and 3.12. - Use GitHub Actions instead of Travis. 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105604.0 venusian-3.1.1/src/venusian.egg-info/SOURCES.txt0000644000175000001440000000437414723213704020620 0ustar00chrismusers.coveragerc .readthedocs.yaml CHANGES.rst CONTRIBUTING.rst CONTRIBUTORS.txt COPYRIGHT.txt LICENSE.txt MANIFEST.in README.rst pyproject.toml setup.cfg setup.py tox.ini .github/dependabot.yml .github/workflows/ci-tests.yml docs/Makefile docs/api.rst docs/conf.py docs/glossary.rst docs/index.rst src/venusian/__init__.py src/venusian/advice.py src/venusian/compat.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././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105604.0 venusian-3.1.1/src/venusian.egg-info/dependency_links.txt0000644000175000001440000000000114723213704022772 0ustar00chrismusers ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105604.0 venusian-3.1.1/src/venusian.egg-info/requires.txt0000644000175000001440000000017714723213704021331 0ustar00chrismusers [docs] Sphinx>=4.3.2 repoze.sphinx.autointerface pylons-sphinx-themes sphinx-copybutton [testing] pytest pytest-cov coverage ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105604.0 venusian-3.1.1/src/venusian.egg-info/top_level.txt0000644000175000001440000000001114723213704021446 0ustar00chrismusersvenusian ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.856014 venusian-3.1.1/tests/0000755000175000001440000000000014723213705013756 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/__init__.py0000644000175000001440000000001214723200770016056 0ustar00chrismusers# package ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.858014 venusian-3.1.1/tests/fixtures/0000755000175000001440000000000014723213705015627 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/__init__.py0000644000175000001440000000150214723200770017734 0ustar00chrismusersimport 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" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.859014 venusian-3.1.1/tests/fixtures/attrerror/0000755000175000001440000000000014723213705017653 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/attrerror/__init__.py0000644000175000001440000000017714723200770021767 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/attrerror/will_cause_import_error.py0000644000175000001440000000002514723200770025152 0ustar00chrismusersraise AttributeError ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.859014 venusian-3.1.1/tests/fixtures/attrerror_package/0000755000175000001440000000000014723213705021326 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/attrerror_package/__init__.py0000644000175000001440000000017714723200770023442 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.859014 venusian-3.1.1/tests/fixtures/attrerror_package/will_cause_import_error/0000755000175000001440000000000014723213705026260 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/attrerror_package/will_cause_import_error/__init__.py0000644000175000001440000000002514723200770030364 0ustar00chrismusersraise AttributeError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/category.py0000644000175000001440000000040714723200770020015 0ustar00chrismusersfrom tests.fixtures import categorydecorator, categorydecorator2 @categorydecorator(function=True) def function(request): # pragma: no cover return request @categorydecorator2(function=True) def function2(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/class_and_method.py0000644000175000001440000000027214723200770021467 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(class_=True) class ClassWithMethod(object): @decorator(method=True) def method_on_class(self): # pragma: no cover pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/classdecorator.py0000644000175000001440000000024714723200770021212 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(superclass=True) class SuperClass(object): pass @decorator(subclass=True) class SubClass(SuperClass): pass ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.860014 venusian-3.1.1/tests/fixtures/import_and_scan/0000755000175000001440000000000014723213705020767 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/import_and_scan/__init__.py0000644000175000001440000000001214723200770023067 0ustar00chrismusers# package ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/import_and_scan/mock.py0000644000175000001440000000137014723200770022271 0ustar00chrismusersclass _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() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/import_and_scan/one.py0000644000175000001440000000035714723200770022125 0ustar00chrismusersfrom 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/import_and_scan/two.py0000644000175000001440000000020214723200770022142 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def twofunction(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8610141 venusian-3.1.1/tests/fixtures/importerror/0000755000175000001440000000000014723213705020213 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importerror/__init__.py0000644000175000001440000000017714723200770022327 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importerror/will_cause_import_error.py0000644000175000001440000000002414723200770025511 0ustar00chrismusersimport doesnt.exist ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8610141 venusian-3.1.1/tests/fixtures/importerror_package/0000755000175000001440000000000014723213705021666 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importerror_package/__init__.py0000644000175000001440000000017714723200770024002 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8610141 venusian-3.1.1/tests/fixtures/importerror_package/will_cause_import_error/0000755000175000001440000000000014723213705026620 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importerror_package/will_cause_import_error/__init__.py0000644000175000001440000000002414723200770030723 0ustar00chrismusersimport doesnt.exist ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.862014 venusian-3.1.1/tests/fixtures/importonly/0000755000175000001440000000000014723213705020043 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importonly/__init__.py0000644000175000001440000000001214723200770022143 0ustar00chrismusers# package ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importonly/one.py0000644000175000001440000000032114723200770021170 0ustar00chrismusersfrom 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/importonly/two.py0000644000175000001440000000020214723200770021216 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def twofunction(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/inheritance.py0000644000175000001440000000016314723200770020470 0ustar00chrismusersfrom tests.fixtures import decorator @decorator() class Parent(object): pass class Child(Parent): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/lifting1.py0000644000175000001440000000121114723200770017707 0ustar00chrismusersfrom tests.fixtures import decorator from venusian import lift 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/lifting2.py0000644000175000001440000000127514723200770017722 0ustar00chrismusersfrom tests.fixtures import decorator from venusian import lift, onlyliftedfrom @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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/lifting3.py0000644000175000001440000000134514723200770017721 0ustar00chrismusersfrom tests.fixtures import decorator from venusian import lift, onlyliftedfrom @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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/lifting4.py0000644000175000001440000000073514723200770017724 0ustar00chrismusersfrom tests.fixtures import categorydecorator, categorydecorator2 from venusian import lift, onlyliftedfrom @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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/lifting5.py0000644000175000001440000000131514723200770017720 0ustar00chrismusersfrom tests.fixtures import decorator from venusian import lift 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.862014 venusian-3.1.1/tests/fixtures/nested/0000755000175000001440000000000014723213705017111 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/nested/__init__.py0000644000175000001440000000017714723200770021225 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.862014 venusian-3.1.1/tests/fixtures/nested/sub1/0000755000175000001440000000000014723213705017763 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/nested/sub1/__init__.py0000644000175000001440000000017714723200770022077 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.862014 venusian-3.1.1/tests/fixtures/nested/sub1/subsub1/0000755000175000001440000000000014723213705021347 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/nested/sub1/subsub1/__init__.py0000644000175000001440000000017714723200770023463 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.863014 venusian-3.1.1/tests/fixtures/nested/sub2/0000755000175000001440000000000014723213705017764 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/nested/sub2/__init__.py0000644000175000001440000000017714723200770022100 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.863014 venusian-3.1.1/tests/fixtures/nested/sub2/subsub2/0000755000175000001440000000000014723213705021351 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/nested/sub2/subsub2/__init__.py0000644000175000001440000000017714723200770023465 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8640141 venusian-3.1.1/tests/fixtures/one/0000755000175000001440000000000014723213705016410 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/one/__init__.py0000644000175000001440000000001214723200770020510 0ustar00chrismusers# package ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/one/module.py0000644000175000001440000000063414723200770020250 0ustar00chrismusersfrom 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/one/module2.py0000644000175000001440000000063414723200770020332 0ustar00chrismusersfrom 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8640141 venusian-3.1.1/tests/fixtures/pyc/0000755000175000001440000000000014723213705016422 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/pyc/__init__.py0000644000175000001440000000000614723200770020525 0ustar00chrismusers# pkg ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/pyc/module.py0000644000175000001440000000063414723200770020262 0ustar00chrismusersfrom 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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1733105604.8640141 venusian-3.1.1/tests/fixtures/pyc/subpackage/0000755000175000001440000000000014723213705020527 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/pyc/subpackage/__init__.py0000644000175000001440000000020214723200770022630 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def pkgfunction(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/subclassing.py0000644000175000001440000000047414723200770020521 0ustar00chrismusersfrom 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.865014 venusian-3.1.1/tests/fixtures/subpackages/0000755000175000001440000000000014723213705020117 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/subpackages/__init__.py0000644000175000001440000000017714723200770022233 0ustar00chrismusersfrom tests.fixtures import decorator @decorator(function=True) def function(request): # pragma: no cover return request ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.865014 venusian-3.1.1/tests/fixtures/subpackages/childpackage/0000755000175000001440000000000014723213705022516 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/subpackages/childpackage/__init__.py0000644000175000001440000000000014723200770024613 0ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/subpackages/childpackage/will_cause_import_error.py0000644000175000001440000000002414723200770030014 0ustar00chrismusersimport doesnt.exist ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/subpackages/mod2.py0000644000175000001440000000002514723200770021325 0ustar00chrismusersraise AttributeError ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1733105604.866014 venusian-3.1.1/tests/fixtures/two/0000755000175000001440000000000014723213705016440 5ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/two/__init__.py0000644000175000001440000000000014723200770020535 0ustar00chrismusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/two/mod1.py0000644000175000001440000000012214723200770017643 0ustar00chrismusersfrom tests.fixtures import decorator @decorator() class Class(object): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/two/mod2.py0000644000175000001440000000003014723200770017642 0ustar00chrismusersfrom .mod1 import Class ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/fixtures/zipped.zip0000644000175000001440000000157414723200770017653 0ustar00chrismusersPK2cOtcmoduleinzip.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  ȿ]$ȿ]ux PK 2cOpackageinzip/__init__.pyUT  ȿ] ȿ]ux PK 2cO$packageinzip/moduleinpackageinzip.pyUT  ȿ] ȿ]ux PK2cOtcmoduleinzip.pyUT ȿ]ux PK 2cO Apackageinzip/UT ȿ]ux PK 2cOGpackageinzip/__init__.pyUT ȿ]ux PK 2cO$packageinzip/moduleinpackageinzip.pyUT ȿ]ux PKo././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733103549.0 venusian-3.1.1/tests/test_advice.py0000644000175000001440000000651514723207675016642 0ustar00chrismusers############################################################################## # # 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 sys import unittest 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") frame = sys._getframe() self.assertEqual(f_locals, frame.f_locals) self.assertEqual(f_locals, locals()) for d in module.__dict__, f_globals: self.assertTrue(d is globals()) self.assertEqual(len(codeinfo), 4) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733100024.0 venusian-3.1.1/tests/test_venusian.py0000644000175000001440000011620714723200770017224 0ustar00chrismusersimport contextlib import os import re import sys import unittest @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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 from tests.fixtures.one.module2 import Class as Class2 from tests.fixtures.one.module2 import function as func2 from tests.fixtures.one.module2 import inst as inst2 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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 from tests.fixtures.one.module2 import Class as Class2 from tests.fixtures.one.module2 import function as func2 from tests.fixtures.one.module2 import inst as inst2 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 import subpackage from tests.fixtures.pyc.module import Class as Class1 from tests.fixtures.pyc.module import function as func1 from tests.fixtures.pyc.module import inst as inst1 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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 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, 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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 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 Class as Class1 from tests.fixtures.one.module import function as func1 from tests.fixtures.one.module import inst as inst1 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 Class as Class1 from tests.fixtures.one.module import function as func1 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] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1733105029.0 venusian-3.1.1/tox.ini0000644000175000001440000000245414723212605014132 0ustar00chrismusers[tox] envlist = lint, py37,py38,py39,py310,py311,py312,py313,pypy3 docs, coverage isolated_build = True [testenv] commands = python --version python -mpytest {posargs:} extras = testing setenv = COVERAGE_FILE=.coverage.{envname} [testenv:coverage] commands = coverage combine coverage xml coverage report --show-missing deps = coverage setenv = COVERAGE_FILE=.coverage [testenv:lint] skip_install = True commands = isort --check-only --df . black --check --diff . check-manifest # build sdist/wheel python -m build . twine check dist/* deps = black build check-manifest isort readme_renderer twine [testenv:docs] allowlist_externals = make commands = make -C docs html BUILDDIR={envdir} SPHINXOPTS="-W -E" extras = docs [testenv:format] skip_install = true commands = isort . black . deps = black isort [testenv:build] skip_install = true 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 build . # Verify all is well twine check dist/* deps = build check-manifest readme_renderer twine