From 75504294b2024935d6a65ca63ca1ff71b71250c6 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Sat, 14 Nov 2020 04:21:27 +0800 Subject: [PATCH 01/20] Fix test for Python 3.9 Python 3.9 has neither dummy_thread nor _dummy_thread anymore. Using _thread.allocate_lock passes all tests here. --- src/aspectlib/test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aspectlib/test.py b/src/aspectlib/test.py index 5146f6c..e8c5464 100644 --- a/src/aspectlib/test.py +++ b/src/aspectlib/test.py @@ -29,7 +29,10 @@ try: from dummy_thread import allocate_lock except ImportError: - from _dummy_thread import allocate_lock + try: + from _dummy_thread import allocate_lock + except ImportError: + from _thread import allocate_lock try: from collections import OrderedDict except ImportError: From 117dfdd738132c0f7a5d134de46d5dceb745d92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sun, 15 Nov 2020 12:55:40 +0200 Subject: [PATCH 02/20] Update project skel (include py 3.9 in CI). --- .appveyor.yml | 45 ++++++++++++++++++++++++++++++++++++++ .cookiecutterrc | 9 +++----- .editorconfig | 6 ++++- .gitignore | 5 ++++- .pre-commit-config.yaml | 3 ++- .travis.yml | 22 ++++++++++++++----- CONTRIBUTING.rst | 6 ++--- ci/requirements.txt | 2 +- ci/templates/.appveyor.yml | 1 + ci/templates/.travis.yml | 9 ++++---- ci/templates/tox.ini | 5 ++--- docs/requirements.txt | 3 +-- setup.cfg | 6 ++--- setup.py | 1 + tox.ini | 41 +++++++++++++++++++++++++++++++--- 15 files changed, 130 insertions(+), 34 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 970b5a3..b9054e6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,5 +1,6 @@ version: '{branch}-{build}' build: off +image: Visual Studio 2019 environment: global: COVERALLS_EXTRAS: '-v' @@ -234,6 +235,50 @@ environment: PYTHON_VERSION: '3.8' PYTHON_ARCH: '64' WHEEL_PATH: .tox/dist + - TOXENV: py39-cover,codecov,coveralls + TOXPYTHON: C:\Python39\python.exe + PYTHON_HOME: C:\Python39 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '32' + - TOXENV: py39-cover,codecov,coveralls + TOXPYTHON: C:\Python39-x64\python.exe + PYTHON_HOME: C:\Python39-x64 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '64' + - TOXENV: py39-cover-debug,codecov,coveralls + TOXPYTHON: C:\Python39\python.exe + PYTHON_HOME: C:\Python39 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '32' + - TOXENV: py39-cover-debug,codecov,coveralls + TOXPYTHON: C:\Python39-x64\python.exe + PYTHON_HOME: C:\Python39-x64 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '64' + - TOXENV: py39-nocov,codecov,coveralls + TOXPYTHON: C:\Python39\python.exe + PYTHON_HOME: C:\Python39 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '32' + WHEEL_PATH: .tox/dist + - TOXENV: py39-nocov,codecov,coveralls + TOXPYTHON: C:\Python39-x64\python.exe + PYTHON_HOME: C:\Python39-x64 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '64' + WHEEL_PATH: .tox/dist + - TOXENV: py39-nocov-debug,codecov,coveralls + TOXPYTHON: C:\Python39\python.exe + PYTHON_HOME: C:\Python39 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '32' + WHEEL_PATH: .tox/dist + - TOXENV: py39-nocov-debug,codecov,coveralls + TOXPYTHON: C:\Python39-x64\python.exe + PYTHON_HOME: C:\Python39-x64 + PYTHON_VERSION: '3.9' + PYTHON_ARCH: '64' + WHEEL_PATH: .tox/dist init: - ps: echo $env:TOXENV - ps: ls C:\Python* diff --git a/.cookiecutterrc b/.cookiecutterrc index 113ace3..48358a2 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,9 +1,6 @@ # Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) -cookiecutter: - _extensions: - - jinja2_time.TimeExtension - _template: /home/ionel/open-source/cookiecutter-pylibrary +default_context: allow_tests_inside_package: no appveyor: yes c_extension_function: '-' @@ -32,7 +29,7 @@ cookiecutter: project_short_description: '``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing' pypi_badge: yes pypi_disable_upload: no - release_date: '2016-05-10' + release_date: '2020-06-11' repo_hosting: github.com repo_hosting_domain: github.com repo_name: python-aspectlib @@ -50,7 +47,7 @@ cookiecutter: test_runner: pytest travis: yes travis_osx: no - version: 1.4.2 + version: 1.5.1 website: http://blog.ionelmc.ro year_from: '2014' year_to: '2020' diff --git a/.editorconfig b/.editorconfig index a9c7977..586c736 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,10 +2,11 @@ root = true [*] +# Use Unix-style newlines for most files (except Windows files, see below). end_of_line = lf trim_trailing_whitespace = true -insert_final_newline = true indent_style = space +insert_final_newline = true indent_size = 4 charset = utf-8 @@ -14,3 +15,6 @@ end_of_line = crlf [*.{yml,yaml}] indent_size = 2 + +[*.tsv] +indent_style = tab diff --git a/.gitignore b/.gitignore index dfe5838..83a43fd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,11 +39,14 @@ htmlcov # Translations *.mo -# Mr Developer +# Buildout .mr.developer.cfg + +# IDE project files .project .pydevproject .idea +.vscode *.iml *.komodoproject diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0946b3..6e974cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,13 +2,14 @@ # pre-commit install # To update the pre-commit hooks run: # pre-commit install-hooks -exclude: '^(.tox|ci/templates|.bumpversion.cfg)(/|$)' +exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: master hooks: - id: trailing-whitespace - id: end-of-file-fixer + - id: debug-statements - repo: https://github.com/timothycrosley/isort rev: master hooks: diff --git a/.travis.yml b/.travis.yml index 9a50bbc..4df3c8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: python dist: xenial +virt: lxd cache: false env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all + - LANG=en_US.UTF-8 matrix: include: - python: '3.6' @@ -73,6 +75,18 @@ matrix: - env: - TOXENV=py38-nocov-debug python: '3.8' + - env: + - TOXENV=py39-cover,codecov,coveralls + python: '3.9' + - env: + - TOXENV=py39-cover-debug,codecov,coveralls + python: '3.9' + - env: + - TOXENV=py39-nocov + python: '3.9' + - env: + - TOXENV=py39-nocov-debug + python: '3.9' - env: - TOXENV=pypy-cover,codecov,coveralls python: 'pypy' @@ -87,19 +101,15 @@ matrix: python: 'pypy' - env: - TOXENV=pypy3-cover,codecov,coveralls - - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-cover-debug,codecov,coveralls - - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-nocov - - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-nocov-debug - - TOXPYTHON=pypy3 python: 'pypy3' before_install: - python --version @@ -114,8 +124,8 @@ install: script: - tox -v after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat + - cat .tox/log/* + - cat .tox/*/log/* notifications: email: on_success: never diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 9b57832..a27c87f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -41,7 +41,7 @@ To set up `python-aspectlib` for local development: (look for the "Fork" button). 2. Clone your fork locally:: - git clone git@github.com:ionelmc/python-aspectlib.git + git clone git@github.com:YOURGITHUBNAME/python-aspectlib.git 3. Create a branch for local development:: @@ -85,6 +85,6 @@ To run a subset of tests:: tox -e envname -- pytest -k test_myfeature -To run all the test environments in *parallel* (you need to ``pip install detox``):: +To run all the test environments in *parallel*:: - detox + tox -p auto diff --git a/ci/requirements.txt b/ci/requirements.txt index b2a21e5..d7f5177 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1,4 +1,4 @@ virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 -six>=1.12.0 +six>=1.14.0 diff --git a/ci/templates/.appveyor.yml b/ci/templates/.appveyor.yml index 4c6502e..00704b9 100644 --- a/ci/templates/.appveyor.yml +++ b/ci/templates/.appveyor.yml @@ -1,5 +1,6 @@ version: '{branch}-{build}' build: off +image: Visual Studio 2019 environment: global: COVERALLS_EXTRAS: '-v' diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml index 84171cd..5f3e48a 100644 --- a/ci/templates/.travis.yml +++ b/ci/templates/.travis.yml @@ -1,10 +1,12 @@ language: python dist: xenial +virt: lxd cache: false env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all + - LANG=en_US.UTF-8 matrix: include: - python: '3.6' @@ -17,13 +19,12 @@ matrix: - env: - TOXENV={{ env }}{% if config.cover %},codecov,coveralls{% endif %} {%- if env.startswith('pypy3') %}{{ '' }} - - TOXPYTHON=pypy3 python: 'pypy3' {%- elif env.startswith('pypy') %}{{ '' }} python: 'pypy' {%- else %}{{ '' }} python: '{{ '{0[2]}.{0[3]}'.format(env) }}' -{%- endif %} +{%- endif %}{{ '' }} {%- endfor %}{{ '' }} before_install: - python --version @@ -38,8 +39,8 @@ install: script: - tox -v after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat + - cat .tox/log/* + - cat .tox/*/log/* notifications: email: on_success: never diff --git a/ci/templates/tox.ini b/ci/templates/tox.ini index 338859f..eaef583 100644 --- a/ci/templates/tox.ini +++ b/ci/templates/tox.ini @@ -10,8 +10,7 @@ envlist = [testenv] basepython = - docs: {env:TOXPYTHON:python3.6} - {bootstrap,clean,check,report,codecov,coveralls}: {env:TOXPYTHON:python3} + {bootstrap,clean,check,report,docs,codecov,coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes @@ -52,7 +51,7 @@ commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 - isort --verbose --check-only --diff --recursive + isort --verbose --check-only --diff --filter-files . [testenv:docs] diff --git a/docs/requirements.txt b/docs/requirements.txt index f53d26e..62bc14e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,2 @@ -sphinx +sphinx>=1.3 sphinx-py3doc-enhanced-theme --e . diff --git a/setup.cfg b/setup.cfg index 14d07c4..4e608d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ universal = 1 [flake8] max-line-length = 140 -exclude = .tox,ci/templates +exclude = .tox,.eggs,ci/templates,build,dist [tool:pytest] # If a pytest section is found in one of the possible config files @@ -36,8 +36,7 @@ line_length = 120 known_first_party = aspectlib default_section = THIRDPARTY forced_separate = test_aspectlib -not_skip = __init__.py -skip = .tox,ci/templates +skip = .tox,.eggs,ci/templates,build,dist [matrix] # This is the configuration for the `./bootstrap.py` script. @@ -66,6 +65,7 @@ python_versions = py36 py37 py38 + py39 pypy pypy3 diff --git a/setup.py b/setup.py index 58aca1f..db77bd8 100755 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ def read(*names, **kwargs): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', # uncomment if you test on these interpreters: diff --git a/tox.ini b/tox.ini index 86f64f6..8c607de 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,10 @@ envlist = py38-cover-debug, py38-nocov, py38-nocov-debug, + py39-cover, + py39-cover-debug, + py39-nocov, + py39-nocov-debug, pypy-cover, pypy-cover-debug, pypy-nocov, @@ -35,8 +39,7 @@ envlist = [testenv] basepython = - docs: {env:TOXPYTHON:python3.6} - {bootstrap,clean,check,report,codecov,coveralls}: {env:TOXPYTHON:python3} + {bootstrap,clean,check,report,docs,codecov,coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes @@ -77,7 +80,7 @@ commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 - isort --verbose --check-only --diff --recursive + isort --verbose --check-only --diff --filter-files . [testenv:docs] @@ -285,6 +288,38 @@ setenv = {[testenv]setenv} ASPECTLIB_DEBUG=yes +[testenv:py39-cover] +basepython = {env:TOXPYTHON:python3.9} +setenv = + {[testenv]setenv} +usedevelop = true +commands = + {posargs:pytest --cov --cov-report=term-missing -vv} +deps = + {[testenv]deps} + pytest-cov + +[testenv:py39-cover-debug] +basepython = {env:TOXPYTHON:python3.9} +setenv = + {[testenv]setenv} + ASPECTLIB_DEBUG=yes +usedevelop = true +commands = + {posargs:pytest --cov --cov-report=term-missing -vv} +deps = + {[testenv]deps} + pytest-cov + +[testenv:py39-nocov] +basepython = {env:TOXPYTHON:python3.9} + +[testenv:py39-nocov-debug] +basepython = {env:TOXPYTHON:python3.9} +setenv = + {[testenv]setenv} + ASPECTLIB_DEBUG=yes + [testenv:pypy-cover] basepython = {env:TOXPYTHON:pypy} setenv = From 8fae25b1dd8c60f9be0aae3f1d2f11bd2c5d12c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sun, 15 Nov 2020 12:58:01 +0200 Subject: [PATCH 03/20] Update changelog. --- AUTHORS.rst | 3 ++- CHANGELOG.rst | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 24b749f..b290b69 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -3,4 +3,5 @@ Authors ======= * Ionel Cristian Mărieș - https://blog.ionelmc.ro -* Jonas Maurus - https://github.com/jdelic/ +* Jonas Maurus - https://github.com/jdelic +* Felix Yan - https://github.com/felixonmars diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c12fcc7..163e7e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +1.5.2 (2020-11-15) +------------------ + +* Fixed broken import on Python 3.9. + Contributed by Felix Yan in `#19 `_. + 1.5.1 (2020-06-11) ------------------ From 889055f773581872b71469779b1fa0d5a539098f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sun, 15 Nov 2020 13:35:52 +0200 Subject: [PATCH 04/20] Fix pypy3 CI. --- .travis.yml | 4 ++++ ci/templates/.travis.yml | 1 + 2 files changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4df3c8f..120712f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,15 +101,19 @@ matrix: python: 'pypy' - env: - TOXENV=pypy3-cover,codecov,coveralls + - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-cover-debug,codecov,coveralls + - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-nocov + - TOXPYTHON=pypy3 python: 'pypy3' - env: - TOXENV=pypy3-nocov-debug + - TOXPYTHON=pypy3 python: 'pypy3' before_install: - python --version diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml index 5f3e48a..2b41139 100644 --- a/ci/templates/.travis.yml +++ b/ci/templates/.travis.yml @@ -19,6 +19,7 @@ matrix: - env: - TOXENV={{ env }}{% if config.cover %},codecov,coveralls{% endif %} {%- if env.startswith('pypy3') %}{{ '' }} + - TOXPYTHON=pypy3 python: 'pypy3' {%- elif env.startswith('pypy') %}{{ '' }} python: 'pypy' From 483fd5c3aeb285a2ee35e6ee059168e8b13da184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sun, 15 Nov 2020 21:28:49 +0200 Subject: [PATCH 05/20] =?UTF-8?q?Bump=20version:=201.5.1=20=E2=86=92=201.5?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- setup.py | 2 +- src/aspectlib/__init__.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 71cc681..60faa92 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.5.1 +current_version = 1.5.2 commit = True tag = True diff --git a/README.rst b/README.rst index 950704f..c6bf3a6 100644 --- a/README.rst +++ b/README.rst @@ -55,9 +55,9 @@ Overview :alt: Supported implementations :target: https://pypi.org/project/aspectlib -.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-aspectlib/v1.5.1.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-aspectlib/v1.5.2.svg :alt: Commits since latest release - :target: https://github.com/ionelmc/python-aspectlib/compare/v1.5.1...master + :target: https://github.com/ionelmc/python-aspectlib/compare/v1.5.2...master diff --git a/docs/conf.py b/docs/conf.py index 7b9b64b..51c53db 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = '2014-2020' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) -version = release = '1.5.1' +version = release = '1.5.2' pygments_style = 'trac' templates_path = ['.'] diff --git a/setup.py b/setup.py index db77bd8..e4f6d12 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def read(*names, **kwargs): setup( name='aspectlib', - version='1.5.1', + version='1.5.2', license='BSD-2-Clause', description='``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing', long_description='%s\n%s' % ( diff --git a/src/aspectlib/__init__.py b/src/aspectlib/__init__.py index defd9d2..b85f1dd 100644 --- a/src/aspectlib/__init__.py +++ b/src/aspectlib/__init__.py @@ -57,7 +57,7 @@ def isasyncfunction(obj): isasyncfunction = None __all__ = 'weave', 'Aspect', 'Proceed', 'Return', 'ALL_METHODS', 'NORMAL_METHODS', 'ABSOLUTELY_ALL_METHODS' -__version__ = '1.5.1' +__version__ = '1.5.2' logger = getLogger(__name__) logdebug = logf(logger.debug) From 24e6e276896a764e7b1888e6e9cc783a50d98ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Wed, 17 Mar 2021 18:02:04 +0200 Subject: [PATCH 06/20] Update skel and drop 3.5 --- .appveyor.yml | 44 -------------------------------------------- .bumpversion.cfg | 5 ++--- .cookiecutterrc | 8 ++++---- .travis.yml | 12 ------------ CHANGELOG.rst | 1 + CONTRIBUTING.rst | 3 ++- LICENSE | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- setup.cfg | 1 - setup.py | 3 +-- tox.ini | 36 ------------------------------------ 12 files changed, 14 insertions(+), 107 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b9054e6..83262dd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -59,50 +59,6 @@ environment: PYTHON_ARCH: '64' WHEEL_PATH: .tox/dist WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py35-cover,codecov,coveralls - TOXPYTHON: C:\Python35\python.exe - PYTHON_HOME: C:\Python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - - TOXENV: py35-cover,codecov,coveralls - TOXPYTHON: C:\Python35-x64\python.exe - PYTHON_HOME: C:\Python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - - TOXENV: py35-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python35\python.exe - PYTHON_HOME: C:\Python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - - TOXENV: py35-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python35-x64\python.exe - PYTHON_HOME: C:\Python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - - TOXENV: py35-nocov,codecov,coveralls - TOXPYTHON: C:\Python35\python.exe - PYTHON_HOME: C:\Python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py35-nocov,codecov,coveralls - TOXPYTHON: C:\Python35-x64\python.exe - PYTHON_HOME: C:\Python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py35-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python35\python.exe - PYTHON_HOME: C:\Python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py35-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python35-x64\python.exe - PYTHON_HOME: C:\Python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - TOXENV: py36-cover,codecov,coveralls TOXPYTHON: C:\Python36\python.exe PYTHON_HOME: C:\Python36 diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 60faa92..9ca0bcb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -8,8 +8,8 @@ search = version='{current_version}' replace = version='{new_version}' [bumpversion:file:README.rst] -search = v{current_version}. -replace = v{new_version}. +search = /v{current_version}.svg +replace = /v{new_version}.svg [bumpversion:file:docs/conf.py] search = version = release = '{current_version}' @@ -18,4 +18,3 @@ replace = version = release = '{new_version}' [bumpversion:file:src/aspectlib/__init__.py] search = __version__ = '{current_version}' replace = __version__ = '{new_version}' - diff --git a/.cookiecutterrc b/.cookiecutterrc index 48358a2..41c2e1e 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -20,7 +20,7 @@ default_context: distribution_name: aspectlib email: contact@ionelmc.ro full_name: Ionel Cristian Mărieș - landscape: no + legacy_python: yes license: BSD 2-Clause License linter: flake8 package_name: aspectlib @@ -29,7 +29,7 @@ default_context: project_short_description: '``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing' pypi_badge: yes pypi_disable_upload: no - release_date: '2020-06-11' + release_date: '2020-11-15' repo_hosting: github.com repo_hosting_domain: github.com repo_name: python-aspectlib @@ -47,7 +47,7 @@ default_context: test_runner: pytest travis: yes travis_osx: no - version: 1.5.1 + version: 1.5.2 website: http://blog.ionelmc.ro year_from: '2014' - year_to: '2020' + year_to: '2021' diff --git a/.travis.yml b/.travis.yml index 120712f..86af895 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,18 +27,6 @@ matrix: - env: - TOXENV=py27-nocov-debug python: '2.7' - - env: - - TOXENV=py35-cover,codecov,coveralls - python: '3.5' - - env: - - TOXENV=py35-cover-debug,codecov,coveralls - python: '3.5' - - env: - - TOXENV=py35-nocov - python: '3.5' - - env: - - TOXENV=py35-nocov-debug - python: '3.5' - env: - TOXENV=py36-cover,codecov,coveralls python: '3.6' diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 163e7e5..c4de5e2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,4 @@ + Changelog ========= diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a27c87f..87647a9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -74,7 +74,8 @@ For merging, you should: 4. Add yourself to ``AUTHORS.rst``. .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will - `run the tests `_ for each change you add in the pull request. + `run the tests `_ + for each change you add in the pull request. It will be slower though ... diff --git a/LICENSE b/LICENSE index 69350bf..176891a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2014-2020, Ionel Cristian Mărieș. All rights reserved. +Copyright (c) 2014-2021, Ionel Cristian Mărieș. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.rst b/README.rst index c6bf3a6..2a8c89a 100644 --- a/README.rst +++ b/README.rst @@ -19,9 +19,9 @@ Overview :target: https://readthedocs.org/projects/python-aspectlib :alt: Documentation Status -.. |travis| image:: https://api.travis-ci.org/ionelmc/python-aspectlib.svg?branch=master +.. |travis| image:: https://api.travis-ci.com/ionelmc/python-aspectlib.svg?branch=master :alt: Travis-CI Build Status - :target: https://travis-ci.org/ionelmc/python-aspectlib + :target: https://travis-ci.com/github/ionelmc/python-aspectlib .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/ionelmc/python-aspectlib?branch=master&svg=true :alt: AppVeyor Build Status diff --git a/docs/conf.py b/docs/conf.py index 51c53db..a0b738d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ source_suffix = '.rst' master_doc = 'index' project = 'Aspectlib' -year = '2014-2020' +year = '2014-2021' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) version = release = '1.5.2' diff --git a/setup.cfg b/setup.cfg index 4e608d1..a4f0d99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,7 +61,6 @@ skip = .tox,.eggs,ci/templates,build,dist python_versions = py27 - py35 py36 py37 py38 diff --git a/setup.py b/setup.py index e4f6d12..a9f305e 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,6 @@ def read(*names, **kwargs): 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -74,7 +73,7 @@ def read(*names, **kwargs): 'tests', 'mock', 'capture', 'replay', 'capture-replay', 'debugging', 'patching', 'monkeypatching', 'record', 'recording', 'mocking', 'logger' ], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', install_requires=[ 'fields' ], diff --git a/tox.ini b/tox.ini index 8c607de..c9cbc83 100644 --- a/tox.ini +++ b/tox.ini @@ -7,10 +7,6 @@ envlist = py27-cover-debug, py27-nocov, py27-nocov-debug, - py35-cover, - py35-cover-debug, - py35-nocov, - py35-nocov-debug, py36-cover, py36-cover-debug, py36-nocov, @@ -160,38 +156,6 @@ deps = {[testenv]deps} trollius -[testenv:py35-cover] -basepython = {env:TOXPYTHON:python3.5} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py35-cover-debug] -basepython = {env:TOXPYTHON:python3.5} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py35-nocov] -basepython = {env:TOXPYTHON:python3.5} - -[testenv:py35-nocov-debug] -basepython = {env:TOXPYTHON:python3.5} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes - [testenv:py36-cover] basepython = {env:TOXPYTHON:python3.6} setenv = From 7dccb198dfb426f529b81a28a755f3c02f8b50cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sun, 2 May 2021 09:50:43 +0300 Subject: [PATCH 07/20] Remove tornado<6 test constraint. Ref #15. --- tests/test_integrations_py3.py | 5 ++++- tox.ini | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_integrations_py3.py b/tests/test_integrations_py3.py index e025eac..596589d 100644 --- a/tests/test_integrations_py3.py +++ b/tests/test_integrations_py3.py @@ -36,7 +36,10 @@ def test_decorate_tornado_coroutine(): @gen.coroutine @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) def coro(): - yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) + if hasattr(gen, 'Task'): + yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) + else: + yield gen.sleep(0.01) return "result" loop = ioloop.IOLoop.current() diff --git a/tox.ini b/tox.ini index c9cbc83..1bd1d1c 100644 --- a/tox.ini +++ b/tox.ini @@ -51,7 +51,8 @@ deps = pytest-clarity pytest-cov pytest-travis-fold - tornado<6.0 + six + tornado commands = {posargs:pytest --cov --cov-report=term-missing -vv --ignore=src} From ed4b036b9e69e195226faab719c470f77ad11cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 18:56:56 +0300 Subject: [PATCH 08/20] Drop support for old pythons and switch to github actions. --- .appveyor.yml | 255 -------------- .bumpversion.cfg | 6 +- .cookiecutterrc | 58 ++-- .coveragerc | 6 +- .github/workflows/github-actions.yml | 209 ++++++++++++ .pre-commit-config.yaml | 10 +- .travis.yml | 124 ------- CONTRIBUTING.rst | 6 +- LICENSE | 2 +- MANIFEST.in | 10 +- README.rst | 29 +- ci/appveyor-with-compiler.cmd | 23 -- ci/bootstrap.py | 47 ++- ci/requirements.txt | 1 + ci/templates/.appveyor.yml | 53 --- .../.github/workflows/github-actions.yml | 63 ++++ ci/templates/.travis.yml | 48 --- ci/templates/tox.ini | 119 ------- conftest.py | 7 +- docs/conf.py | 10 +- docs/spelling_wordlist.txt | 3 + pyproject.toml | 10 + pytest.ini | 38 +++ setup.cfg | 70 ---- setup.py | 52 +-- src/aspectlib/__init__.py | 201 +++-------- src/aspectlib/py2chainmap.py | 106 ------ src/aspectlib/py2ordereddict.py | 128 ------- src/aspectlib/py35support.py | 4 +- src/aspectlib/py3support.py | 3 +- src/aspectlib/test.py | 235 +++++++------ src/aspectlib/utils.py | 37 +- tests/mymod.py | 3 - tests/test_aspectlib.py | 69 ++-- tests/test_aspectlib_debug.py | 84 +++-- tests/test_aspectlib_py23.py | 169 ---------- tests/test_aspectlib_py3.py | 2 +- tests/test_aspectlib_py37.py | 5 +- tests/test_aspectlib_test.py | 256 ++++++++------ tests/test_integrations.py | 80 +++-- tests/test_integrations_py2.py | 64 ---- tests/test_integrations_py3.py | 48 --- tests/test_integrations_py37.py | 23 -- tox.ini | 315 ++---------------- 44 files changed, 954 insertions(+), 2137 deletions(-) delete mode 100644 .appveyor.yml create mode 100644 .github/workflows/github-actions.yml delete mode 100644 .travis.yml delete mode 100644 ci/appveyor-with-compiler.cmd delete mode 100644 ci/templates/.appveyor.yml create mode 100644 ci/templates/.github/workflows/github-actions.yml delete mode 100644 ci/templates/.travis.yml delete mode 100644 ci/templates/tox.ini create mode 100644 pyproject.toml create mode 100644 pytest.ini delete mode 100644 src/aspectlib/py2chainmap.py delete mode 100644 src/aspectlib/py2ordereddict.py delete mode 100644 tests/test_aspectlib_py23.py delete mode 100644 tests/test_integrations_py2.py delete mode 100644 tests/test_integrations_py3.py delete mode 100644 tests/test_integrations_py37.py diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 83262dd..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,255 +0,0 @@ -version: '{branch}-{build}' -build: off -image: Visual Studio 2019 -environment: - global: - COVERALLS_EXTRAS: '-v' - COVERALLS_REPO_TOKEN: mHSWktPkrw65WSlziG8NnK9m1xj2k1kOQ - matrix: - - TOXENV: check - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - - TOXENV: py27-cover,codecov,coveralls - TOXPYTHON: C:\Python27\python.exe - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - - TOXENV: py27-cover,codecov,coveralls - TOXPYTHON: C:\Python27-x64\python.exe - PYTHON_HOME: C:\Python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py27-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python27\python.exe - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - - TOXENV: py27-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python27-x64\python.exe - PYTHON_HOME: C:\Python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py27-nocov,codecov,coveralls - TOXPYTHON: C:\Python27\python.exe - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py27-nocov,codecov,coveralls - TOXPYTHON: C:\Python27-x64\python.exe - PYTHON_HOME: C:\Python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py27-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python27\python.exe - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py27-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python27-x64\python.exe - PYTHON_HOME: C:\Python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py36-cover,codecov,coveralls - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - - TOXENV: py36-cover,codecov,coveralls - TOXPYTHON: C:\Python36-x64\python.exe - PYTHON_HOME: C:\Python36-x64 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '64' - - TOXENV: py36-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - - TOXENV: py36-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python36-x64\python.exe - PYTHON_HOME: C:\Python36-x64 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '64' - - TOXENV: py36-nocov,codecov,coveralls - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py36-nocov,codecov,coveralls - TOXPYTHON: C:\Python36-x64\python.exe - PYTHON_HOME: C:\Python36-x64 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py36-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py36-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python36-x64\python.exe - PYTHON_HOME: C:\Python36-x64 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py37-cover,codecov,coveralls - TOXPYTHON: C:\Python37\python.exe - PYTHON_HOME: C:\Python37 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '32' - - TOXENV: py37-cover,codecov,coveralls - TOXPYTHON: C:\Python37-x64\python.exe - PYTHON_HOME: C:\Python37-x64 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '64' - - TOXENV: py37-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python37\python.exe - PYTHON_HOME: C:\Python37 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '32' - - TOXENV: py37-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python37-x64\python.exe - PYTHON_HOME: C:\Python37-x64 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '64' - - TOXENV: py37-nocov,codecov,coveralls - TOXPYTHON: C:\Python37\python.exe - PYTHON_HOME: C:\Python37 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py37-nocov,codecov,coveralls - TOXPYTHON: C:\Python37-x64\python.exe - PYTHON_HOME: C:\Python37-x64 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py37-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python37\python.exe - PYTHON_HOME: C:\Python37 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py37-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python37-x64\python.exe - PYTHON_HOME: C:\Python37-x64 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py38-cover,codecov,coveralls - TOXPYTHON: C:\Python38\python.exe - PYTHON_HOME: C:\Python38 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '32' - - TOXENV: py38-cover,codecov,coveralls - TOXPYTHON: C:\Python38-x64\python.exe - PYTHON_HOME: C:\Python38-x64 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '64' - - TOXENV: py38-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python38\python.exe - PYTHON_HOME: C:\Python38 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '32' - - TOXENV: py38-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python38-x64\python.exe - PYTHON_HOME: C:\Python38-x64 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '64' - - TOXENV: py38-nocov,codecov,coveralls - TOXPYTHON: C:\Python38\python.exe - PYTHON_HOME: C:\Python38 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py38-nocov,codecov,coveralls - TOXPYTHON: C:\Python38-x64\python.exe - PYTHON_HOME: C:\Python38-x64 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py38-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python38\python.exe - PYTHON_HOME: C:\Python38 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py38-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python38-x64\python.exe - PYTHON_HOME: C:\Python38-x64 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py39-cover,codecov,coveralls - TOXPYTHON: C:\Python39\python.exe - PYTHON_HOME: C:\Python39 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '32' - - TOXENV: py39-cover,codecov,coveralls - TOXPYTHON: C:\Python39-x64\python.exe - PYTHON_HOME: C:\Python39-x64 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '64' - - TOXENV: py39-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python39\python.exe - PYTHON_HOME: C:\Python39 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '32' - - TOXENV: py39-cover-debug,codecov,coveralls - TOXPYTHON: C:\Python39-x64\python.exe - PYTHON_HOME: C:\Python39-x64 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '64' - - TOXENV: py39-nocov,codecov,coveralls - TOXPYTHON: C:\Python39\python.exe - PYTHON_HOME: C:\Python39 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py39-nocov,codecov,coveralls - TOXPYTHON: C:\Python39-x64\python.exe - PYTHON_HOME: C:\Python39-x64 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist - - TOXENV: py39-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python39\python.exe - PYTHON_HOME: C:\Python39 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '32' - WHEEL_PATH: .tox/dist - - TOXENV: py39-nocov-debug,codecov,coveralls - TOXPYTHON: C:\Python39-x64\python.exe - PYTHON_HOME: C:\Python39-x64 - PYTHON_VERSION: '3.9' - PYTHON_ARCH: '64' - WHEEL_PATH: .tox/dist -init: - - ps: echo $env:TOXENV - - ps: ls C:\Python* -install: - - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - - '%PYTHON_HOME%\Scripts\virtualenv --version' - - '%PYTHON_HOME%\Scripts\easy_install --version' - - '%PYTHON_HOME%\Scripts\pip --version' - - '%PYTHON_HOME%\Scripts\tox --version' -test_script: - - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox -on_failure: - - ps: dir "env:" - - ps: get-content .tox\*\log\* - -### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): -# on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9ca0bcb..8f991ed 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -7,10 +7,14 @@ tag = True search = version='{current_version}' replace = version='{new_version}' -[bumpversion:file:README.rst] +[bumpversion:file (badge):README.rst] search = /v{current_version}.svg replace = /v{new_version}.svg +[bumpversion:file (link):README.rst] +search = /v{current_version}...main +replace = /v{new_version}...main + [bumpversion:file:docs/conf.py] search = version = release = '{current_version}' replace = version = release = '{new_version}' diff --git a/.cookiecutterrc b/.cookiecutterrc index 41c2e1e..2351690 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,53 +1,57 @@ # Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) default_context: - allow_tests_inside_package: no - appveyor: yes + allow_tests_inside_package: 'no' + appveyor: 'no' c_extension_function: '-' c_extension_module: '-' - c_extension_optional: no - c_extension_support: no - c_extension_test_pypi: no + c_extension_optional: 'no' + c_extension_support: 'no' + c_extension_test_pypi: 'no' c_extension_test_pypi_username: ionelmc - codacy: no + codacy: 'no' codacy_projectid: '-' - codeclimate: no - codecov: yes - command_line_interface: no + codeclimate: 'no' + codecov: 'yes' + command_line_interface: 'no' command_line_interface_bin_name: '-' - coveralls: yes - coveralls_token: mHSWktPkrw65WSlziG8NnK9m1xj2k1kOQ + coveralls: 'yes' distribution_name: aspectlib email: contact@ionelmc.ro full_name: Ionel Cristian Mărieș - legacy_python: yes + github_actions: 'yes' + github_actions_osx: 'no' + github_actions_windows: 'no' + legacy_python: 'no' license: BSD 2-Clause License linter: flake8 package_name: aspectlib - pre_commit: yes + pre_commit: 'yes' + pre_commit_formatter: black project_name: Aspectlib project_short_description: '``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing' - pypi_badge: yes - pypi_disable_upload: no + pypi_badge: 'yes' + pypi_disable_upload: 'no' release_date: '2020-11-15' repo_hosting: github.com repo_hosting_domain: github.com + repo_main_branch: main repo_name: python-aspectlib repo_username: ionelmc - requiresio: yes - scrutinizer: no - setup_py_uses_setuptools_scm: no - setup_py_uses_test_runner: no - sphinx_docs: yes + requiresio: 'yes' + scrutinizer: 'no' + setup_py_uses_pytest_runner: 'no' + setup_py_uses_setuptools_scm: 'no' + sphinx_docs: 'yes' sphinx_docs_hosting: https://python-aspectlib.readthedocs.io/ - sphinx_doctest: yes + sphinx_doctest: 'yes' sphinx_theme: sphinx-py3doc-enhanced-theme - test_matrix_configurator: yes - test_matrix_separate_coverage: no - test_runner: pytest - travis: yes - travis_osx: no + test_matrix_configurator: 'no' + test_matrix_separate_coverage: 'yes' + travis: 'no' + travis_osx: 'no' version: 1.5.2 + version_manager: bump2version website: http://blog.ionelmc.ro year_from: '2014' - year_to: '2021' + year_to: '2022' diff --git a/.coveragerc b/.coveragerc index 79a44ff..770e36e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,10 @@ [paths] -source = - src - */site-packages +source = src [run] branch = true source = - aspectlib + src tests parallel = true diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..4adf1ad --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,209 @@ +name: build +on: [push, pull_request] +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' + - name: 'py37-cover-release (ubuntu)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'py37-cover-debug (ubuntu)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'py37-nocov-release (ubuntu)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov-release' + os: 'ubuntu-latest' + - name: 'py37-nocov-debug (ubuntu)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov-debug' + os: 'ubuntu-latest' + - name: 'py38-cover-release (ubuntu)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'py38-cover-debug (ubuntu)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'py38-nocov-release (ubuntu)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov-release' + os: 'ubuntu-latest' + - name: 'py38-nocov-debug (ubuntu)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov-debug' + os: 'ubuntu-latest' + - name: 'py39-cover-release (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'py39-cover-debug (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'py39-nocov-release (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov-release' + os: 'ubuntu-latest' + - name: 'py39-nocov-debug (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov-debug' + os: 'ubuntu-latest' + - name: 'py310-cover-release (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'py310-cover-debug (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'py310-nocov-release (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov-release' + os: 'ubuntu-latest' + - name: 'py310-nocov-debug (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov-debug' + os: 'ubuntu-latest' + - name: 'pypy37-cover-release (ubuntu)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'pypy37-cover-debug (ubuntu)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'pypy37-nocov-release (ubuntu)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-nocov-release' + os: 'ubuntu-latest' + - name: 'pypy37-nocov-debug (ubuntu)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-nocov-debug' + os: 'ubuntu-latest' + - name: 'pypy38-cover-release (ubuntu)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'pypy38-cover-debug (ubuntu)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'pypy38-nocov-release (ubuntu)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-nocov-release' + os: 'ubuntu-latest' + - name: 'pypy38-nocov-debug (ubuntu)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-nocov-debug' + os: 'ubuntu-latest' + - name: 'pypy39-cover-release (ubuntu)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-cover-release,codecov' + os: 'ubuntu-latest' + - name: 'pypy39-cover-debug (ubuntu)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-cover-debug,codecov' + os: 'ubuntu-latest' + - name: 'pypy39-nocov-release (ubuntu)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-nocov-release' + os: 'ubuntu-latest' + - name: 'pypy39-nocov-debug (ubuntu)' + python: 'pypy-3.9' + toxpython: 'pypy3.9' + python_arch: 'x64' + tox_env: 'pypy39-nocov-debug' + os: 'ubuntu-latest' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.python_arch }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: test + env: + TOXPYTHON: '${{ matrix.toxpython }}' + run: > + tox -e ${{ matrix.tox_env }} -v diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e974cb..8be60d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,16 +5,20 @@ exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: master + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - repo: https://github.com/timothycrosley/isort - rev: master + rev: 5.10.1 hooks: - id: isort + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: master + rev: 3.9.2 hooks: - id: flake8 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 86af895..0000000 --- a/.travis.yml +++ /dev/null @@ -1,124 +0,0 @@ -language: python -dist: xenial -virt: lxd -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all - - LANG=en_US.UTF-8 -matrix: - include: - - python: '3.6' - env: - - TOXENV=check - - python: '3.6' - env: - - TOXENV=docs - - env: - - TOXENV=py27-cover,codecov,coveralls - python: '2.7' - - env: - - TOXENV=py27-cover-debug,codecov,coveralls - python: '2.7' - - env: - - TOXENV=py27-nocov - python: '2.7' - - env: - - TOXENV=py27-nocov-debug - python: '2.7' - - env: - - TOXENV=py36-cover,codecov,coveralls - python: '3.6' - - env: - - TOXENV=py36-cover-debug,codecov,coveralls - python: '3.6' - - env: - - TOXENV=py36-nocov - python: '3.6' - - env: - - TOXENV=py36-nocov-debug - python: '3.6' - - env: - - TOXENV=py37-cover,codecov,coveralls - python: '3.7' - - env: - - TOXENV=py37-cover-debug,codecov,coveralls - python: '3.7' - - env: - - TOXENV=py37-nocov - python: '3.7' - - env: - - TOXENV=py37-nocov-debug - python: '3.7' - - env: - - TOXENV=py38-cover,codecov,coveralls - python: '3.8' - - env: - - TOXENV=py38-cover-debug,codecov,coveralls - python: '3.8' - - env: - - TOXENV=py38-nocov - python: '3.8' - - env: - - TOXENV=py38-nocov-debug - python: '3.8' - - env: - - TOXENV=py39-cover,codecov,coveralls - python: '3.9' - - env: - - TOXENV=py39-cover-debug,codecov,coveralls - python: '3.9' - - env: - - TOXENV=py39-nocov - python: '3.9' - - env: - - TOXENV=py39-nocov-debug - python: '3.9' - - env: - - TOXENV=pypy-cover,codecov,coveralls - python: 'pypy' - - env: - - TOXENV=pypy-cover-debug,codecov,coveralls - python: 'pypy' - - env: - - TOXENV=pypy-nocov - python: 'pypy' - - env: - - TOXENV=pypy-nocov-debug - python: 'pypy' - - env: - - TOXENV=pypy3-cover,codecov,coveralls - - TOXPYTHON=pypy3 - python: 'pypy3' - - env: - - TOXENV=pypy3-cover-debug,codecov,coveralls - - TOXPYTHON=pypy3 - python: 'pypy3' - - env: - - TOXENV=pypy3-nocov - - TOXPYTHON=pypy3 - python: 'pypy3' - - env: - - TOXENV=pypy3-nocov-debug - - TOXPYTHON=pypy3 - python: 'pypy3' -before_install: - - python --version - - uname -a - - lsb_release -a || true -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - cat .tox/log/* - - cat .tox/*/log/* -notifications: - email: - on_success: never - on_failure: always diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 87647a9..47c71ee 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -68,16 +68,12 @@ If you need some code review or feedback while you're developing the code just m For merging, you should: -1. Include passing tests (run ``tox``) [1]_. +1. Include passing tests (run ``tox``). 2. Update documentation when there's new API, functionality etc. 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. -.. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will - `run the tests `_ - for each change you add in the pull request. - It will be slower though ... Tips ---- diff --git a/LICENSE b/LICENSE index 176891a..82c46d1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2014-2021, Ionel Cristian Mărieș. All rights reserved. +Copyright (c) 2014-2022, Ionel Cristian Mărieș. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/MANIFEST.in b/MANIFEST.in index 942b8c3..d0dac9c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,9 +4,14 @@ graft ci graft tests include .bumpversion.cfg -include .coveragerc include .cookiecutterrc +include .coveragerc include .editorconfig +include .github/workflows/github-actions.yml +include .pre-commit-config.yaml +include .readthedocs.yml +include pytest.ini +include tox.ini include AUTHORS.rst include CHANGELOG.rst @@ -14,7 +19,4 @@ include CONTRIBUTING.rst include LICENSE include README.rst -include tox.ini .travis.yml .appveyor.yml .readthedocs.yml .pre-commit-config.yaml -include conftest.py - global-exclude *.py[cod] __pycache__/* *.so *.dylib diff --git a/README.rst b/README.rst index 2a8c89a..d89249c 100644 --- a/README.rst +++ b/README.rst @@ -10,32 +10,28 @@ Overview * - docs - |docs| * - tests - - | |travis| |appveyor| |requires| + - | |github-actions| |requires| | |coveralls| |codecov| * - package - | |version| |wheel| |supported-versions| |supported-implementations| | |commits-since| .. |docs| image:: https://readthedocs.org/projects/python-aspectlib/badge/?style=flat - :target: https://readthedocs.org/projects/python-aspectlib + :target: https://python-aspectlib.readthedocs.io/ :alt: Documentation Status -.. |travis| image:: https://api.travis-ci.com/ionelmc/python-aspectlib.svg?branch=master - :alt: Travis-CI Build Status - :target: https://travis-ci.com/github/ionelmc/python-aspectlib +.. |github-actions| image:: https://github.com/ionelmc/python-aspectlib/actions/workflows/github-actions.yml/badge.svg + :alt: GitHub Actions Build Status + :target: https://github.com/ionelmc/python-aspectlib/actions -.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/ionelmc/python-aspectlib?branch=master&svg=true - :alt: AppVeyor Build Status - :target: https://ci.appveyor.com/project/ionelmc/python-aspectlib - -.. |requires| image:: https://requires.io/github/ionelmc/python-aspectlib/requirements.svg?branch=master +.. |requires| image:: https://requires.io/github/ionelmc/python-aspectlib/requirements.svg?branch=main :alt: Requirements Status - :target: https://requires.io/github/ionelmc/python-aspectlib/requirements/?branch=master + :target: https://requires.io/github/ionelmc/python-aspectlib/requirements/?branch=main -.. |coveralls| image:: https://coveralls.io/repos/ionelmc/python-aspectlib/badge.svg?branch=master&service=github +.. |coveralls| image:: https://coveralls.io/repos/ionelmc/python-aspectlib/badge.svg?branch=main&service=github :alt: Coverage Status :target: https://coveralls.io/r/ionelmc/python-aspectlib -.. |codecov| image:: https://codecov.io/gh/ionelmc/python-aspectlib/branch/master/graphs/badge.svg?branch=master +.. |codecov| image:: https://codecov.io/gh/ionelmc/python-aspectlib/branch/main/graphs/badge.svg?branch=main :alt: Coverage Status :target: https://codecov.io/github/ionelmc/python-aspectlib @@ -57,7 +53,7 @@ Overview .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-aspectlib/v1.5.2.svg :alt: Commits since latest release - :target: https://github.com/ionelmc/python-aspectlib/compare/v1.5.2...master + :target: https://github.com/ionelmc/python-aspectlib/compare/v1.5.2...main @@ -76,6 +72,11 @@ Installation pip install aspectlib +You can also install the in-development version with:: + + pip install https://github.com/ionelmc/python-aspectlib/archive/main.zip + + Documentation ============= diff --git a/ci/appveyor-with-compiler.cmd b/ci/appveyor-with-compiler.cmd deleted file mode 100644 index 289585f..0000000 --- a/ci/appveyor-with-compiler.cmd +++ /dev/null @@ -1,23 +0,0 @@ -:: Very simple setup: -:: - if WINDOWS_SDK_VERSION is set then activate the SDK. -:: - disable the WDK if it's around. - -SET COMMAND_TO_RUN=%* -SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows -SET WIN_WDK="c:\Program Files (x86)\Windows Kits\10\Include\wdf" -ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH% - -IF EXIST %WIN_WDK% ( - REM See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ - REN %WIN_WDK% 0wdf -) -IF "%WINDOWS_SDK_VERSION%"=="" GOTO main - -SET DISTUTILS_USE_SDK=1 -SET MSSdk=1 -"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% -CALL "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release - -:main -ECHO Executing: %COMMAND_TO_RUN% -CALL %COMMAND_TO_RUN% || EXIT 1 diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 7e42276..cc5ce2f 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals + import os import subprocess @@ -11,8 +9,10 @@ from os.path import dirname from os.path import exists from os.path import join +from os.path import relpath base_path = dirname(dirname(abspath(__file__))) +templates_path = join(base_path, "ci", "templates") def check_call(args): @@ -38,7 +38,7 @@ def exec_in_env(): except subprocess.CalledProcessError: check_call(["virtualenv", env_path]) print("Installing `jinja2` into bootstrap environment...") - check_call([join(bin_path, "pip"), "install", "jinja2", "tox", "matrix"]) + check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) python_executable = join(bin_path, "python") if not os.path.exists(python_executable): python_executable += '.exe' @@ -50,34 +50,33 @@ def exec_in_env(): def main(): import jinja2 - import matrix print("Project path: {0}".format(base_path)) jinja = jinja2.Environment( - loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")), + loader=jinja2.FileSystemLoader(templates_path), trim_blocks=True, lstrip_blocks=True, - keep_trailing_newline=True + keep_trailing_newline=True, ) - tox_environments = {} - for (alias, conf) in matrix.from_file(join(base_path, "setup.cfg")).items(): - deps = conf["dependencies"] - tox_environments[alias] = { - "deps": deps.split(), - } - if "coverage_flags" in conf: - cover = {"false": False, "true": True}[conf["coverage_flags"].lower()] - tox_environments[alias].update(cover=cover) - if "environment_variables" in conf: - env_vars = conf["environment_variables"] - tox_environments[alias].update(env_vars=env_vars.split()) - - for name in os.listdir(join("ci", "templates")): - with open(join(base_path, name), "w") as fh: - fh.write(jinja.get_template(name).render(tox_environments=tox_environments)) - print("Wrote {}".format(name)) + tox_environments = [ + line.strip() + # 'tox' need not be installed globally, but must be importable + # by the Python that is running this script. + # This uses sys.executable the same way that the call in + # cookiecutter-pylibrary/hooks/post_gen_project.py + # invokes this bootstrap.py itself. + for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], universal_newlines=True).splitlines() + ] + tox_environments = [line for line in tox_environments if line.startswith('py')] + + for root, _, files in os.walk(templates_path): + for name in files: + relative = relpath(root, templates_path) + with open(join(base_path, relative, name), "w") as fh: + fh.write(jinja.get_template(join(relative, name)).render(tox_environments=tox_environments)) + print("Wrote {}".format(name)) print("DONE.") diff --git a/ci/requirements.txt b/ci/requirements.txt index d7f5177..a0ef106 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -2,3 +2,4 @@ virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 six>=1.14.0 +tox diff --git a/ci/templates/.appveyor.yml b/ci/templates/.appveyor.yml deleted file mode 100644 index 00704b9..0000000 --- a/ci/templates/.appveyor.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: '{branch}-{build}' -build: off -image: Visual Studio 2019 -environment: - global: - COVERALLS_EXTRAS: '-v' - COVERALLS_REPO_TOKEN: mHSWktPkrw65WSlziG8NnK9m1xj2k1kOQ - matrix: - - TOXENV: check - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' -{% for env, config in tox_environments|dictsort %} -{% if env.startswith(('py2', 'py3')) %} - - TOXENV: {{ env }},codecov,coveralls{{ "" }} - TOXPYTHON: C:\Python{{ env[2:4] }}\python.exe - PYTHON_HOME: C:\Python{{ env[2:4] }} - PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' - PYTHON_ARCH: '32' -{% if 'nocov' in env %} - WHEEL_PATH: .tox/dist -{% endif %} - - TOXENV: {{ env }},codecov,coveralls{{ "" }} - TOXPYTHON: C:\Python{{ env[2:4] }}-x64\python.exe - PYTHON_HOME: C:\Python{{ env[2:4] }}-x64 - PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' - PYTHON_ARCH: '64' -{% if 'nocov' in env %} - WHEEL_PATH: .tox/dist -{% endif %} -{% if env.startswith('py2') %} - WINDOWS_SDK_VERSION: v7.0 -{% endif %} -{% endif %}{% endfor %} -init: - - ps: echo $env:TOXENV - - ps: ls C:\Python* -install: - - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - - '%PYTHON_HOME%\Scripts\virtualenv --version' - - '%PYTHON_HOME%\Scripts\easy_install --version' - - '%PYTHON_HOME%\Scripts\pip --version' - - '%PYTHON_HOME%\Scripts\tox --version' -test_script: - - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox -on_failure: - - ps: dir "env:" - - ps: get-content .tox\*\log\* - -### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): -# on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml new file mode 100644 index 0000000..b4a7ae2 --- /dev/null +++ b/ci/templates/.github/workflows/github-actions.yml @@ -0,0 +1,63 @@ +name: build +on: [push, pull_request] +jobs: + test: + name: {{ '${{ matrix.name }}' }} + runs-on: {{ '${{ matrix.os }}' }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' +{% for env in tox_environments %} +{% set prefix = env.split('-')[0] -%} +{% if prefix.startswith('pypy') %} +{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set cpython %}pp{{ prefix[4:5] }}{% endset %} +{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% else %} +{% set python %}{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} +{% set cpython %}cp{{ prefix[2:] }}{% endset %} +{% set toxpython %}python{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} +{% endif %} +{% for os, python_arch in [ + ['ubuntu', 'x64'], +] %} + - name: '{{ env }} ({{ os }})' + python: '{{ python }}' + toxpython: '{{ toxpython }}' + python_arch: '{{ python_arch }}' + tox_env: '{{ env }}{% if 'cover' in env %},codecov{% endif %}' + os: '{{ os }}-latest' +{% endfor %} +{% endfor %} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: {{ '${{ matrix.python }}' }} + architecture: {{ '${{ matrix.python_arch }}' }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: test + env: + TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' + run: > + tox -e {{ '${{ matrix.tox_env }}' }} -v diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml deleted file mode 100644 index 2b41139..0000000 --- a/ci/templates/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -language: python -dist: xenial -virt: lxd -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all - - LANG=en_US.UTF-8 -matrix: - include: - - python: '3.6' - env: - - TOXENV=check - - python: '3.6' - env: - - TOXENV=docs -{%- for env, config in tox_environments|dictsort %}{{ '' }} - - env: - - TOXENV={{ env }}{% if config.cover %},codecov,coveralls{% endif %} -{%- if env.startswith('pypy3') %}{{ '' }} - - TOXPYTHON=pypy3 - python: 'pypy3' -{%- elif env.startswith('pypy') %}{{ '' }} - python: 'pypy' -{%- else %}{{ '' }} - python: '{{ '{0[2]}.{0[3]}'.format(env) }}' -{%- endif %}{{ '' }} -{%- endfor %}{{ '' }} -before_install: - - python --version - - uname -a - - lsb_release -a || true -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - cat .tox/log/* - - cat .tox/*/log/* -notifications: - email: - on_success: never - on_failure: always diff --git a/ci/templates/tox.ini b/ci/templates/tox.ini deleted file mode 100644 index eaef583..0000000 --- a/ci/templates/tox.ini +++ /dev/null @@ -1,119 +0,0 @@ -[tox] -envlist = - clean, - check, - docs, -{% for env in tox_environments|sort %} - {{ env }}, -{% endfor %} - report - -[testenv] -basepython = - {bootstrap,clean,check,report,docs,codecov,coveralls}: {env:TOXPYTHON:python3} -setenv = - PYTHONPATH={toxinidir}/tests - PYTHONUNBUFFERED=yes -passenv = - * -deps = - hunter - mock - nose - process-tests - pytest - pytest-catchlog - pytest-clarity - pytest-cov - pytest-travis-fold - tornado<6.0 -commands = - {posargs:pytest --cov --cov-report=term-missing -vv --ignore=src} - -[testenv:bootstrap] -deps = - jinja2 - matrix -skip_install = true -commands = - python ci/bootstrap.py --no-env - -[testenv:check] -deps = - docutils - check-manifest - flake8 - readme-renderer - pygments - isort -skip_install = true -commands = - python setup.py check --strict --metadata --restructuredtext - check-manifest {toxinidir} - flake8 - isort --verbose --check-only --diff --filter-files . - - -[testenv:docs] -usedevelop = true -deps = - -r{toxinidir}/docs/requirements.txt -commands = - sphinx-build {posargs:-E} -b doctest docs dist/docs - sphinx-build {posargs:-E} -b html docs dist/docs - sphinx-build -b linkcheck docs dist/docs - -[testenv:coveralls] -deps = - coveralls -skip_install = true -commands = - coveralls [] - - - -[testenv:codecov] -deps = - codecov -skip_install = true -commands = - codecov [] - -[testenv:report] -deps = coverage -skip_install = true -commands = - coverage report - coverage html - -[testenv:clean] -commands = coverage erase -skip_install = true -deps = coverage -{% for env, config in tox_environments|dictsort %} - -[testenv:{{ env }}] -basepython = {env:TOXPYTHON:{{ env.split("-")[0] if env.startswith("pypy") else "python{0[2]}.{0[3]}".format(env) }}} -{% if config.cover or config.env_vars %} -setenv = - {[testenv]setenv} -{% endif %} -{% for var in config.env_vars %} - {{ var }} -{% endfor %} -{% if config.cover %} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -{% endif %} -{% if config.cover or config.deps %} -deps = - {[testenv]deps} -{% endif %} -{% if config.cover %} - pytest-cov -{% endif %} -{% for dep in config.deps %} - {{ dep }} -{% endfor -%} -{% endfor -%} diff --git a/conftest.py b/conftest.py index e237da2..ee54d9d 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,5 @@ -import sys - -PY3 = sys.version_info[0] >= 3 - - def pytest_ignore_collect(path, config): basename = path.basename - if not PY3 and "py3" in basename or PY3 and "py2" in basename or 'pytest' in basename: + if 'pytestsupport' in basename: return True diff --git a/docs/conf.py b/docs/conf.py index a0b738d..cd0320c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + import sphinx_py3doc_enhanced_theme @@ -17,7 +17,7 @@ source_suffix = '.rst' master_doc = 'index' project = 'Aspectlib' -year = '2014-2021' +year = '2014-2022' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) version = release = '1.5.2' @@ -28,17 +28,17 @@ 'issue': ('https://github.com/ionelmc/python-aspectlib/issues/%s', '#'), 'pr': ('https://github.com/ionelmc/python-aspectlib/pull/%s', 'PR #'), } -html_theme = "sphinx_py3doc_enhanced_theme" +html_theme = 'sphinx_py3doc_enhanced_theme' html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] html_theme_options = { - 'githuburl': 'https://github.com/ionelmc/python-aspectlib/' + 'githuburl': 'https://github.com/ionelmc/python-aspectlib/', } html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = False html_sidebars = { - '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], + '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } html_short_title = '%s-%s' % (project, version) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 89586e1..d3b0b2f 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,5 +1,8 @@ aspectlib builtin +builtins +classmethod +staticmethod classmethods staticmethods mumbo diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fc9d53d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = [ + "setuptools>=30.3.0", + "wheel", +] + +[tool.black] +line-length = 140 +target-version = ['py37'] +skip-string-normalization = true diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..de53382 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,38 @@ +[pytest] +# If a pytest section is found in one of the possible config files +# (pytest.ini, tox.ini or setup.cfg), then pytest will not look for any others, +# so if you add a pytest config section elsewhere, +# you will need to delete this section from setup.cfg. +norecursedirs = + .git + .tox + .env + dist + build + migrations + +python_files = + test_*.py + *_test.py + tests.py +addopts = + -ra + --strict-markers + --ignore=docs/conf.py + --ignore=setup.py + --ignore=ci + --ignore=.eggs + --doctest-modules + --doctest-glob=\*.rst + --tb=short +testpaths = + tests + +# Idea from: https://til.simonwillison.net/pytest/treat-warnings-as-errors +filterwarnings = + error + ignore:Setting test_aspectlib.MissingGlobal to . There was no previous definition, probably patching the wrong module. +# You can add exclusions, some examples: +# ignore:'aspectlib' defines default_app_config:PendingDeprecationWarning:: +# ignore:The {{% if::: +# ignore:Coverage disabled via --no-cov switch! diff --git a/setup.cfg b/setup.cfg index a4f0d99..e617fb4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,35 +1,7 @@ -[bdist_wheel] -universal = 1 - [flake8] max-line-length = 140 exclude = .tox,.eggs,ci/templates,build,dist -[tool:pytest] -# If a pytest section is found in one of the possible config files -# (pytest.ini, tox.ini or setup.cfg), then pytest will not look for any others, -# so if you add a pytest config section elsewhere, -# you will need to delete this section from setup.cfg. -norecursedirs = - migrations - -python_files = - test_*.py - *_test.py - tests.py -addopts = - -ra - --strict - --ignore=docs/conf.py - --ignore=setup.py - --ignore=ci - --ignore=.eggs - --doctest-modules - --doctest-glob=\*.rst - --tb=short -testpaths = - tests - [tool:isort] force_single_line = True line_length = 120 @@ -37,45 +9,3 @@ known_first_party = aspectlib default_section = THIRDPARTY forced_separate = test_aspectlib skip = .tox,.eggs,ci/templates,build,dist - -[matrix] -# This is the configuration for the `./bootstrap.py` script. -# It generates `.travis.yml`, `tox.ini` and `.appveyor.yml`. -# -# Syntax: [alias:] value [!variable[glob]] [&variable[glob]] -# -# alias: -# - is used to generate the tox environment -# - it's optional -# - if not present the alias will be computed from the `value` -# value: -# - a value of "-" means empty -# !variable[glob]: -# - exclude the combination of the current `value` with -# any value matching the `glob` in `variable` -# - can use as many you want -# &variable[glob]: -# - only include the combination of the current `value` -# when there's a value matching `glob` in `variable` -# - can use as many you want - -python_versions = - py27 - py36 - py37 - py38 - py39 - pypy - pypy3 - -dependencies = - : trollius !python_versions[py3*] !python_versions[pypy3] - : !python_versions[py2*] !python_versions[pypy] - -coverage_flags = - cover: true - nocov: false - -environment_variables = - debug: ASPECTLIB_DEBUG=yes - - diff --git a/setup.py b/setup.py index a9f305e..053f6b6 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function import io import re @@ -16,10 +14,7 @@ def read(*names, **kwargs): - with io.open( - join(dirname(__file__), *names), - encoding=kwargs.get('encoding', 'utf8') - ) as fh: + with io.open(join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8')) as fh: return fh.read() @@ -28,9 +23,9 @@ def read(*names, **kwargs): version='1.5.2', license='BSD-2-Clause', description='``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing', - long_description='%s\n%s' % ( + long_description='{}\n{}'.format( re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')), - re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')) + re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')), ), author='Ionel Cristian Mărieș', author_email='contact@ionelmc.ro', @@ -49,12 +44,12 @@ def read(*names, **kwargs): 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', # uncomment if you test on these interpreters: @@ -69,19 +64,32 @@ def read(*names, **kwargs): 'Issue Tracker': 'https://github.com/ionelmc/python-aspectlib/issues', }, keywords=[ - 'aop', 'aspects', 'aspect oriented programming', 'decorators', 'patch', 'monkeypatch', 'weave', 'debug', 'log', - 'tests', 'mock', 'capture', 'replay', 'capture-replay', 'debugging', 'patching', 'monkeypatching', 'record', - 'recording', 'mocking', 'logger' + 'aop', + 'aspects', + 'aspect oriented programming', + 'decorators', + 'patch', + 'monkeypatch', + 'weave', + 'debug', + 'log', + 'tests', + 'mock', + 'capture', + 'replay', + 'capture-replay', + 'debugging', + 'patching', + 'monkeypatching', + 'record', + 'recording', + 'mocking', + 'logger', ], python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', - install_requires=[ - 'fields' - ], - extras_require={ - }, + install_requires=['fields'], + extras_require={}, entry_points={ - 'pytest11': [ - 'aspectlib = aspectlib.pytestsupport' - ], - } + 'pytest11': ['aspectlib = aspectlib.pytestsupport'], + }, ) diff --git a/src/aspectlib/__init__.py b/src/aspectlib/__init__.py index b85f1dd..a62ef18 100644 --- a/src/aspectlib/__init__.py +++ b/src/aspectlib/__init__.py @@ -1,5 +1,4 @@ -from __future__ import print_function - +import builtins import re import sys import warnings @@ -15,9 +14,7 @@ from inspect import isroutine from logging import getLogger -from .utils import PY2 from .utils import PY3 -from .utils import PY37plus from .utils import Sentinel from .utils import basestring from .utils import force_bind @@ -30,10 +27,6 @@ except ImportError: InstanceType = None -try: - import __builtin__ -except ImportError: - import builtins as __builtin__ # pylint: disable=F0401 try: from types import ClassType @@ -53,6 +46,7 @@ def isasyncfunction(obj): return iscoroutinefunction(obj) else: return isasyncgenfunction(obj) or iscoroutinefunction(obj) + except ImportError: isasyncfunction = None @@ -97,6 +91,7 @@ class Proceed(object): If not used as an instance then the default args and kwargs are used. """ + __slots__ = 'args', 'kwargs' def __init__(self, *args, **kwargs): @@ -110,7 +105,8 @@ class Return(object): If not used as an instance then ``None`` is returned. """ - __slots__ = 'value', + + __slots__ = ('value',) def __init__(self, value): self.value = value @@ -158,6 +154,7 @@ class Aspect(object): ... and the result is: None """ + __slots__ = 'advising_function', 'bind' def __new__(cls, advising_function=UNSPECIFIED, bind=False): @@ -177,98 +174,14 @@ def __init__(self, advising_function, bind=False): def __call__(self, cutpoint_function): if isasyncfunction is not None and isasyncfunction(cutpoint_function): from aspectlib.py35support import decorate_advising_asyncgenerator_py35 + return decorate_advising_asyncgenerator_py35(self.advising_function, cutpoint_function, self.bind) elif isgeneratorfunction(cutpoint_function): - if PY37plus: - from aspectlib.py35support import decorate_advising_generator_py35 - return decorate_advising_generator_py35(self.advising_function, cutpoint_function, self.bind) - elif PY3: - from aspectlib.py3support import decorate_advising_generator_py3 - return decorate_advising_generator_py3(self.advising_function, cutpoint_function, self.bind) - else: - def advising_generator_wrapper(*args, **kwargs): - if self.bind: - advisor = self.advising_function(cutpoint_function, *args, **kwargs) - else: - advisor = self.advising_function(*args, **kwargs) - if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) - try: - advice = next(advisor) - while True: - logdebug('Got advice %r from %s', advice, self.advising_function) - if advice is Proceed or advice is None or isinstance(advice, Proceed): - if isinstance(advice, Proceed): - args = advice.args - kwargs = advice.kwargs - gen = cutpoint_function(*args, **kwargs) - try: - try: - generated = next(gen) - except StopIteration as exc: - logexception("The cutpoint has been exhausted (early).") - result = exc.args - if result: - if len(result) == 1: - result = exc.args[0] - else: - result = None - else: - while True: - try: - sent = yield generated - except GeneratorExit as exc: - logexception("Got GeneratorExit while consuming the cutpoint") - gen.close() - raise exc - except BaseException as exc: - logexception("Got exception %r. Throwing it the cutpoint", exc) - try: - generated = gen.throw(*sys.exc_info()) - except StopIteration as exc: - logexception("The cutpoint has been exhausted.") - result = exc.args - if result: - if len(result) == 1: - result = exc.args[0] - else: - result = None - break - else: - try: - if sent is None: - generated = next(gen) - else: - generated = gen.send(sent) - except StopIteration as exc: - logexception("The cutpoint has been exhausted.") - result = exc.args - if result: - if len(result) == 1: - result = exc.args[0] - else: - result = None - break - except BaseException as exc: # noqa - advice = advisor.throw(*sys.exc_info()) - else: - try: - advice = advisor.send(result) - except StopIteration: - raise StopIteration(result) - finally: - gen.close() - elif advice is Return: - return - elif isinstance(advice, Return): - raise StopIteration(advice.value) - else: - raise UnacceptableAdvice("Unknown advice %s" % advice) - finally: - advisor.close() + from aspectlib.py35support import decorate_advising_generator_py35 - return mimic(advising_generator_wrapper, cutpoint_function) + return decorate_advising_generator_py35(self.advising_function, cutpoint_function, self.bind) else: + def advising_function_wrapper(*args, **kwargs): if self.bind: advisor = self.advising_function(cutpoint_function, *args, **kwargs) @@ -313,6 +226,7 @@ class Rollback(object): """ When called, rollbacks all the patches and changes the :func:`weave` has done. """ + __slots__ = '_rollbacks' def __init__(self, rollback=None): @@ -383,9 +297,7 @@ def _check_name(name): if not VALID_IDENTIFIER.match(name): raise SyntaxError( "Could not match %r to %r. It should be a string of " - "letters, numbers and underscore that starts with a letter or underscore." % ( - name, VALID_IDENTIFIER.pattern - ) + "letters, numbers and underscore that starts with a letter or underscore." % (name, VALID_IDENTIFIER.pattern) ) @@ -434,9 +346,7 @@ def weave(target, aspects, **options): bag = options.setdefault('bag', ObjectBag()) if isinstance(target, (list, tuple)): - return Rollback([ - weave(item, aspects, **options) for item in target - ]) + return Rollback([weave(item, aspects, **options) for item in target]) elif isinstance(target, basestring): parts = target.split('.') for part in parts: @@ -467,10 +377,7 @@ def weave(target, aspects, **options): if isinstance(obj, (type, ClassType)): logdebug(" .. as a class %r.", obj) - return weave_class( - obj, aspects, - owner=owner, name=name, **options - ) + return weave_class(obj, aspects, owner=owner, name=name, **options) elif callable(obj): # or isinstance(obj, FunctionType) ?? logdebug(" .. as a callable %r.", obj) if bag.has(obj): @@ -480,10 +387,10 @@ def weave(target, aspects, **options): return weave(obj, aspects, **options) name = getattr(target, '__name__', None) - if name and getattr(__builtin__, name, None) is target: + if name and getattr(builtins, name, None) is target: if bag.has(target): return Nothing - return patch_module_function(__builtin__, target, aspects, **options) + return patch_module_function(builtins, target, aspects, **options) elif PY3 and ismethod(target): if bag.has(target): return Nothing @@ -504,24 +411,6 @@ def weave(target, aspects, **options): logdebug("@ patching %r (%s) as a property.", target, name) func = owner.__dict__[name] return patch_module(owner, name, _checked_apply(aspects, func), func, **options) - elif PY2 and isfunction(target): - if bag.has(target): - return Nothing - return patch_module_function(_import_module(target.__module__), target, aspects, **options) - elif PY2 and ismethod(target): - if target.im_self: - if bag.has(target): - return Nothing - inst = target.im_self - name = target.__name__ - logdebug("@ patching %r (%s) as instance method.", target, name) - func = target.im_func - setattr(inst, name, _checked_apply(aspects, func).__get__(inst, type(inst))) - return Rollback(lambda: delattr(inst, name)) - else: - klass = target.im_class - name = target.__name__ - return weave(klass, aspects, methods='%s$' % name, **options) elif isclass(target): return weave_class(target, aspects, **options) elif ismodule(target): @@ -560,8 +449,7 @@ def weave_instance(instance, aspect, methods=NORMAL_METHODS, lazy=False, bag=Bro entanglement = Rollback() method_matches = make_method_matcher(methods) - logdebug("weave_instance (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", - instance, aspect, methods, lazy, options) + logdebug("weave_instance (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", instance, aspect, methods, lazy, options) def fixup(func): return func.__get__(instance, type(instance)) @@ -576,9 +464,7 @@ def fixup(func): realfunc = func.__func__ else: realfunc = func.im_func - entanglement.merge( - patch_module(instance, attr, _checked_apply(fixed_aspect, realfunc, module=None), **options) - ) + entanglement.merge(patch_module(instance, attr, _checked_apply(fixed_aspect, realfunc, module=None), **options)) return entanglement @@ -595,8 +481,7 @@ def weave_module(module, aspect, methods=NORMAL_METHODS, lazy=False, bag=BrokenB entanglement = Rollback() method_matches = make_method_matcher(methods) - logdebug("weave_module (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", - module, aspect, methods, lazy, options) + logdebug("weave_module (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", module, aspect, methods, lazy, options) for attr in dir(module): if method_matches(attr): @@ -613,8 +498,9 @@ def weave_module(module, aspect, methods=NORMAL_METHODS, lazy=False, bag=BrokenB return entanglement -def weave_class(klass, aspect, methods=NORMAL_METHODS, subclasses=True, lazy=False, - owner=None, name=None, aliases=True, bases=True, bag=BrokenBag): +def weave_class( + klass, aspect, methods=NORMAL_METHODS, subclasses=True, lazy=False, owner=None, name=None, aliases=True, bases=True, bag=BrokenBag +): """ Low-level weaver for classes. @@ -627,8 +513,17 @@ def weave_class(klass, aspect, methods=NORMAL_METHODS, subclasses=True, lazy=Fal entanglement = Rollback() method_matches = make_method_matcher(methods) - logdebug("weave_class (klass=%r, methods=%s, subclasses=%s, lazy=%s, owner=%s, name=%s, aliases=%s, bases=%s)", - klass, methods, subclasses, lazy, owner, name, aliases, bases) + logdebug( + "weave_class (klass=%r, methods=%s, subclasses=%s, lazy=%s, owner=%s, name=%s, aliases=%s, bases=%s)", + klass, + methods, + subclasses, + lazy, + owner, + name, + aliases, + bases, + ) if subclasses and hasattr(klass, '__subclasses__'): sub_targets = klass.__subclasses__() @@ -636,9 +531,9 @@ def weave_class(klass, aspect, methods=NORMAL_METHODS, subclasses=True, lazy=Fal logdebug("~ weaving subclasses: %s", sub_targets) for sub_class in sub_targets: if not issubclass(sub_class, Fabric): - entanglement.merge(weave_class(sub_class, aspect, - methods=methods, subclasses=subclasses, lazy=lazy, bag=bag)) + entanglement.merge(weave_class(sub_class, aspect, methods=methods, subclasses=subclasses, lazy=lazy, bag=bag)) if lazy: + def __init__(self, *args, **kwargs): super(SubClass, self).__init__(*args, **kwargs) for attr in dir(self): @@ -647,9 +542,7 @@ def __init__(self, *args, **kwargs): if isroutine(func): setattr(self, attr, _checked_apply(aspect, force_bind(func)).__get__(self, SubClass)) - wrappers = { - '__init__': _checked_apply(aspect, __init__) if method_matches('__init__') else __init__ - } + wrappers = {'__init__': _checked_apply(aspect, __init__) if method_matches('__init__') else __init__} for attr, func in klass.__dict__.items(): if method_matches(attr): if ismethoddescriptor(func): @@ -671,9 +564,7 @@ def __init__(self, *args, **kwargs): else: continue original[attr] = func - entanglement.merge(lambda: deque(( - setattr(klass, attr, func) for attr, func in original.items() - ), maxlen=0)) + entanglement.merge(lambda: deque((setattr(klass, attr, func) for attr, func in original.items()), maxlen=0)) if bases: super_original = set() for sklass in _find_super_classes(klass): @@ -681,15 +572,12 @@ def __init__(self, *args, **kwargs): for attr, func in sklass.__dict__.items(): if method_matches(attr) and attr not in original and attr not in super_original: if isroutine(func): - logdebug("@ patching attribute %r (from superclass: %s, original: %r).", - attr, sklass.__name__, func) + logdebug("@ patching attribute %r (from superclass: %s, original: %r).", attr, sklass.__name__, func) setattr(klass, attr, _rewrap_method(func, sklass, aspect)) else: continue super_original.add(attr) - entanglement.merge(lambda: deque(( - delattr(klass, attr) for attr in super_original - ), maxlen=0)) + entanglement.merge(lambda: deque((delattr(klass, attr) for attr in super_original), maxlen=0)) return entanglement @@ -753,9 +641,9 @@ def patch_module(module, name, replacement, original=UNSPECIFIED, aliases=True, raise AssertionError("%s.%s = %s is not %s." % (module, alias, obj, original)) if not seen: - warnings.warn('Setting %s.%s to %s. There was no previous definition, probably patching the wrong module.' % ( - target, name, replacement - )) + warnings.warn( + 'Setting %s.%s to %s. There was no previous definition, probably patching the wrong module.' % (target, name, replacement) + ) logdebug("= saving %s on %s.%s ...", replacement, target, name) setattr(module, name, replacement) rollback.merge(lambda: setattr(module, name, original)) @@ -770,7 +658,8 @@ def patch_module_function(module, target, aspect, force_name=None, bag=BrokenBag :returns: An :obj:`aspectlib.Rollback` object. """ - logdebug("patch_module_function (module=%s, target=%s, aspect=%s, force_name=%s, **options=%s", - module, target, aspect, force_name, options) + logdebug( + "patch_module_function (module=%s, target=%s, aspect=%s, force_name=%s, **options=%s", module, target, aspect, force_name, options + ) name = force_name or target.__name__ return patch_module(module, name, _checked_apply(aspect, target, module=module), original=target, **options) diff --git a/src/aspectlib/py2chainmap.py b/src/aspectlib/py2chainmap.py deleted file mode 100644 index 70d56c6..0000000 --- a/src/aspectlib/py2chainmap.py +++ /dev/null @@ -1,106 +0,0 @@ -from collections import MutableMapping - -__all__ = 'ChainMap', - - -class ChainMap(MutableMapping): - ''' A ChainMap groups multiple dicts (or other mappings) together - to create a single, updateable view. - - The underlying mappings are stored in a list. That list is public and can - accessed or updated using the *maps* attribute. There is no other state. - - Lookups search the underlying mappings successively until a key is found. - In contrast, writes, updates, and deletions only operate on the first - mapping. - - ''' - - def __init__(self, *maps): - '''Initialize a ChainMap by setting *maps* to the given mappings. - If no mappings are provided, a single empty dictionary is used. - - ''' - self.maps = list(maps) or [{}] # always at least one map - - def __missing__(self, key): - raise KeyError(key) - - def __getitem__(self, key): - for mapping in self.maps: - try: - return mapping[key] # can't use 'key in mapping' with defaultdict - except KeyError: - pass - return self.__missing__(key) # support subclasses that define __missing__ - - def get(self, key, default=None): - return self[key] if key in self else default - - def __len__(self): - return len(set().union(*self.maps)) # reuses stored hash values if possible - - def __iter__(self): - return iter(set().union(*self.maps)) - - def __contains__(self, key): - return any(key in m for m in self.maps) - - def __bool__(self): - return any(self.maps) - - def __repr__(self): - return '{0.__class__.__name__}({1})'.format( - self, ', '.join(map(repr, self.maps))) - - @classmethod - def fromkeys(cls, iterable, *args): - 'Create a ChainMap with a single dict created from the iterable.' - return cls(dict.fromkeys(iterable, *args)) - - def copy(self): - 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' - return self.__class__(self.maps[0].copy(), *self.maps[1:]) - - __copy__ = copy - - def new_child(self, m=None): # like Django's Context.push() - ''' - New ChainMap with a new map followed by all previous maps. If no - map is provided, an empty dict is used. - ''' - if m is None: - m = {} - return self.__class__(m, *self.maps) - - @property - def parents(self): # like Django's Context.pop() - 'New ChainMap from maps[1:].' - return self.__class__(*self.maps[1:]) - - def __setitem__(self, key, value): - self.maps[0][key] = value - - def __delitem__(self, key): - try: - del self.maps[0][key] - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def popitem(self): - 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' - try: - return self.maps[0].popitem() - except KeyError: - raise KeyError('No keys found in the first mapping.') - - def pop(self, key, *args): - 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' - try: - return self.maps[0].pop(key, *args) - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def clear(self): - 'Clear maps[0], leaving maps[1:] intact.' - self.maps[0].clear() diff --git a/src/aspectlib/py2ordereddict.py b/src/aspectlib/py2ordereddict.py deleted file mode 100644 index 2d1d813..0000000 --- a/src/aspectlib/py2ordereddict.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -from UserDict import DictMixin - - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/src/aspectlib/py35support.py b/src/aspectlib/py35support.py index 27e2c9a..22f8b85 100644 --- a/src/aspectlib/py35support.py +++ b/src/aspectlib/py35support.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys from inspect import iscoroutinefunction from inspect import isgenerator @@ -60,6 +58,7 @@ async def advising_asyncgenerator_wrapper_py35(*args, **kwargs): raise UnacceptableAdvice("Unknown advice %s" % advice) finally: advisor.close() + return mimic(advising_asyncgenerator_wrapper_py35, cutpoint_function) @@ -101,4 +100,5 @@ def advising_generator_wrapper_py35(*args, **kwargs): raise UnacceptableAdvice("Unknown advice %s" % advice) finally: advisor.close() + return mimic(advising_generator_wrapper_py35, cutpoint_function) diff --git a/src/aspectlib/py3support.py b/src/aspectlib/py3support.py index db9f907..beaee11 100644 --- a/src/aspectlib/py3support.py +++ b/src/aspectlib/py3support.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys from inspect import isgenerator from inspect import isgeneratorfunction @@ -54,4 +52,5 @@ def advising_generator_wrapper_py3(*args, **kwargs): raise UnacceptableAdvice("Unknown advice %s" % advice) finally: advisor.close() + return mimic(advising_generator_wrapper_py3, cutpoint_function) diff --git a/src/aspectlib/test.py b/src/aspectlib/test.py index e8c5464..8f27181 100644 --- a/src/aspectlib/test.py +++ b/src/aspectlib/test.py @@ -33,14 +33,9 @@ from _dummy_thread import allocate_lock except ImportError: from _thread import allocate_lock -try: - from collections import OrderedDict -except ImportError: - from .py2ordereddict import OrderedDict -try: - from collections import ChainMap -except ImportError: - from .py2chainmap import ChainMap + +from collections import ChainMap +from collections import OrderedDict __all__ = 'mock', 'record', "Story" @@ -111,6 +106,7 @@ class LogCapture(object): Added ``messages`` property. Changed ``calls`` to retrun the level as a string (instead of int). """ + def __init__(self, logger, level='DEBUG'): self._logger = logger self._level = nameToLevel[level] @@ -121,7 +117,7 @@ def __enter__(self): self._rollback = weave( self._logger, record(callback=self._callback, extended=True, iscalled=True), - methods=('debug', 'info', 'warning', 'error', 'exception', 'critical', 'log') + methods=('debug', 'info', 'warning', 'error', 'exception', 'critical', 'log'), ) return self @@ -144,12 +140,7 @@ def _callback(self, _binding, qualname, args, _kwargs): message, args = args[0], () if level >= self._level: - self._calls.append(( - message % args if args else message, - message, - args, - getLevelName(level) - )) + self._calls.append((message % args if args else message, message, args, getLevelName(level))) @property def calls(self): @@ -164,19 +155,16 @@ def has(self, message, *args, **kwargs): assert not kwargs, "Unexpected arguments: %s" % kwargs for call_final_message, call_message, call_args, call_level in self._calls: if level is None or level == call_level: - if ( - message == call_message and args == call_args - if args else - message == call_final_message or message == call_message - ): + if message == call_message and args == call_args if args else message == call_final_message or message == call_message: return True return False def assertLogged(self, message, *args, **kwargs): if not self.has(message, *args, **kwargs): - raise AssertionError("There's no such message %r (with args %r) logged on %s. Logged messages where: %s" % ( - message, args, self._logger, self.calls - )) + raise AssertionError( + "There's no such message %r (with args %r) logged on %s. Logged messages where: %s" + % (message, args, self._logger, self.calls) + ) class _RecordingFunctionWrapper(object): @@ -186,8 +174,7 @@ class _RecordingFunctionWrapper(object): See :obj:`aspectlib.test.record` for arguments. """ - def __init__(self, wrapped, iscalled=True, calls=None, callback=None, extended=False, results=False, - recurse_lock=None, binding=None): + def __init__(self, wrapped, iscalled=True, calls=None, callback=None, extended=False, results=False, recurse_lock=None, binding=None): assert not results or iscalled, "`iscalled` must be True if `results` is True" mimic(self, wrapped) self.__wrapped = wrapped @@ -228,13 +215,9 @@ def __record(self, args, kwargs, *response): self.__callback(self.__binding, qualname(self), args, kwargs, *response) if self.calls is not None: if self.__extended: - self.calls.append((ResultEx if response else CallEx)( - self.__binding, qualname(self), args, kwargs, *response - )) + self.calls.append((ResultEx if response else CallEx)(self.__binding, qualname(self), args, kwargs, *response)) else: - self.calls.append((Result if response else Call)( - self.__binding, args, kwargs, *response - )) + self.calls.append((Result if response else Call)(self.__binding, args, kwargs, *response)) def __get__(self, instance, owner): return _RecordingFunctionWrapper( @@ -307,11 +290,7 @@ def record(func=None, recurse_lock_factory=allocate_lock, **options): Added `extended` option. """ if func: - return _RecordingFunctionWrapper( - func, - recurse_lock=recurse_lock_factory(), - **options - ) + return _RecordingFunctionWrapper(func, recurse_lock=recurse_lock_factory(), **options) else: return partial(record, **options) @@ -334,14 +313,73 @@ def __unsupported__(self, *args): raise TypeError("Unsupported operation. Only `==` (for results) and `**` (for exceptions) can be used.") for mm in ( - '__add__', '__sub__', '__mul__', '__floordiv__', '__mod__', '__divmod__', '__lshift__', '__rshift__', '__and__', - '__xor__', '__or__', '__div__', '__truediv__', '__radd__', '__rsub__', '__rmul__', '__rdiv__', '__rtruediv__', - '__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', '__rlshift__', '__rrshift__', '__rand__', '__rxor__', - '__ror__', '__iadd__', '__isub__', '__imul__', '__idiv__', '__itruediv__', '__ifloordiv__', '__imod__', - '__ipow__', '__ilshift__', '__irshift__', '__iand__', '__ixor__', '__ior__', '__neg__', '__pos__', '__abs__', - '__invert__', '__complex__', '__int__', '__long__', '__float__', '__oct__', '__hex__', '__index__', - '__coerce__', '__getslice__', '__setslice__', '__delslice__', '__len__', '__getitem__', '__reversed__', - '__contains__', '__call__', '__lt__', '__le__', '__ne__', '__gt__', '__ge__', '__cmp__', '__rcmp__', + '__add__', + '__sub__', + '__mul__', + '__floordiv__', + '__mod__', + '__divmod__', + '__lshift__', + '__rshift__', + '__and__', + '__xor__', + '__or__', + '__div__', + '__truediv__', + '__radd__', + '__rsub__', + '__rmul__', + '__rdiv__', + '__rtruediv__', + '__rfloordiv__', + '__rmod__', + '__rdivmod__', + '__rpow__', + '__rlshift__', + '__rrshift__', + '__rand__', + '__rxor__', + '__ror__', + '__iadd__', + '__isub__', + '__imul__', + '__idiv__', + '__itruediv__', + '__ifloordiv__', + '__imod__', + '__ipow__', + '__ilshift__', + '__irshift__', + '__iand__', + '__ixor__', + '__ior__', + '__neg__', + '__pos__', + '__abs__', + '__invert__', + '__complex__', + '__int__', + '__long__', + '__float__', + '__oct__', + '__hex__', + '__index__', + '__coerce__', + '__getslice__', + '__setslice__', + '__delslice__', + '__len__', + '__getitem__', + '__reversed__', + '__contains__', + '__call__', + '__lt__', + '__le__', + '__ne__', + '__gt__', + '__ge__', + '__cmp__', + '__rcmp__', '__nonzero__', ): exec("%s = __unsupported__" % mm) @@ -369,12 +407,15 @@ def __call__(self, *args, **kwargs): return StoryResultWrapper(partial(self._handle, self._binding, self._name, args, kwargs)) def __get__(self, binding, owner): - return mimic(type(self)( - self._wrapped.__get__(binding, owner) if hasattr(self._wrapped, '__get__') else self._wrapped, - handle=self._handle, - binding=binding, - owner=owner, - ), self) + return mimic( + type(self)( + self._wrapped.__get__(binding, owner) if hasattr(self._wrapped, '__get__') else self._wrapped, + handle=self._handle, + binding=binding, + owner=owner, + ), + self, + ) class _ReplayFunctionWrapper(_StoryFunctionWrapper): @@ -402,12 +443,7 @@ def __init__(self, target, **options): def _make_key(self, binding, name, args, kwargs): if binding is not None: binding, _ = self._ids[id(binding)] - return ( - binding, - name, - ', '.join(repr_ex(i) for i in args), - ', '.join("%s=%s" % (k, repr_ex(v)) for k, v in kwargs.items()) - ) + return (binding, name, ', '.join(repr_ex(i) for i in args), ', '.join("%s=%s" % (k, repr_ex(v)) for k, v in kwargs.items())) def _tag_result(self, name, result): if isinstance(result, _Binds): @@ -423,21 +459,20 @@ def _tag_result(self, name, result): def _handle(self, binding, name, args, kwargs, result): pk = self._make_key(binding, name, args, kwargs) result = self._tag_result(name, result) - assert pk not in self._calls or self._calls[pk] == result, ( - "Story creation inconsistency. There is already a result cached for " - "binding:%r name:%r args:%r kwargs:%r and it's: %r." % ( - binding, name, args, kwargs, self._calls[pk] - ) + assert ( + pk not in self._calls or self._calls[pk] == result + ), "Story creation inconsistency. There is already a result cached for " "binding:%r name:%r args:%r kwargs:%r and it's: %r." % ( + binding, + name, + args, + kwargs, + self._calls[pk], ) self._calls[pk] = result def __enter__(self): self._options.setdefault('methods', ALL_METHODS) - self.__entanglement = weave( - self._target, - partial(self._FunctionWrapper, handle=self._handle), - **self._options - ) + self.__entanglement = weave(self._target, partial(self._FunctionWrapper, handle=self._handle), **self._options) return self def __exit__(self, *args): @@ -452,46 +487,47 @@ def __exit__(self, *args): class Story(_RecordingBase): """ - This a simple yet flexible tool that can do "capture-replay mocking" or "test doubles" [1]_. It leverages - ``aspectlib``'s powerful :obj:`weaver `. + This a simple yet flexible tool that can do "capture-replay mocking" or "test doubles" [1]_. It leverages + ``aspectlib``'s powerful :obj:`weaver `. - Args: - target (same as for :obj:`aspectlib.weave`): - Targets to weave in the `story`/`replay` transactions. - subclasses (bool): - If ``True``, subclasses of target are weaved. *Only available for classes* - aliases (bool): - If ``True``, aliases of target are replaced. - lazy (bool): - If ``True`` only target's ``__init__`` method is patched, the rest of the methods are patched after ``__init__`` - is called. *Only available for classes*. - methods (list or regex or string): Methods from target to patch. *Only available for classes* + Args: + target (same as for :obj:`aspectlib.weave`): + Targets to weave in the `story`/`replay` transactions. + subclasses (bool): + If ``True``, subclasses of target are weaved. *Only available for classes* + aliases (bool): + If ``True``, aliases of target are replaced. + lazy (bool): + If ``True`` only target's ``__init__`` method is patched, the rest of the methods are patched after ``__init__`` + is called. *Only available for classes*. + methods (list or regex or string): Methods from target to patch. *Only available for classes* - The ``Story`` allows some testing patterns that are hard to do with other tools: + The ``Story`` allows some testing patterns that are hard to do with other tools: - * **Proxied mocks**: partially mock `objects` and `modules` so they are called normally if the request is unknown. - * **Stubs**: completely mock `objects` and `modules`. Raise errors if the request is unknown. + * **Proxied mocks**: partially mock `objects` and `modules` so they are called normally if the request is unknown. + * **Stubs**: completely mock `objects` and `modules`. Raise errors if the request is unknown. - The ``Story`` works in two of transactions: + The ``Story`` works in two of transactions: - * **The story**: You describe what calls you want to mocked. Initially you don't need to write this. Example: + * **The story**: You describe what calls you want to mocked. Initially you don't need to write this. Example: - :: + :: - >>> import mymod - >>> with Story(mymod) as story: - ... mymod.func('some arg') == 'some result' - ... mymod.func('bad arg') ** ValueError("can't use this") + >>> import mymod + >>> with Story(mymod) as story: + ... mymod.func('some arg') == 'some result' + ... mymod.func('bad arg') ** ValueError("can't use this") - * **The replay**: You run the code uses the interfaces mocked in the `story`. The :obj:`replay - ` always starts from a `story` instance. + * **The replay**: You run the code uses the interfaces mocked in the `story`. The :obj:`replay + ` always starts from a `story` instance. - .. versionchanged:: 0.9.0 + .. versionchanged:: 0.9.0 - Added in. + Added in. - .. [1] http://www.martinfowler.com/bliki/TestDouble.html + .. [1] http://www.martinfowler.com/bliki/TestDouble.html """ + _FunctionWrapper = _StoryFunctionWrapper def __init__(self, *args, **kwargs): @@ -548,10 +584,7 @@ def logged_eval(value, context): try: return eval(value, *context) except: # noqa - logexception("Failed to evaluate %r.\nContext:\n%s", value, ''.join(format_stack( - f=_getframe(1), - limit=15 - ))) + logexception("Failed to evaluate %r.\nContext:\n%s", value, ''.join(format_stack(f=_getframe(1), limit=15))) raise @@ -562,6 +595,7 @@ class Replay(_RecordingBase): This object should be created by :obj:`Story `'s :obj:`replay ` method. """ + _FunctionWrapper = _ReplayFunctionWrapper def __init__(self, play, proxy=True, strict=True, dump=True, recurse_lock=False, **options): @@ -613,10 +647,7 @@ def _unexpected(self, _missing=False): expected, actual = self._actual, self._expected else: actual, expected = self._actual, self._expected - return ''.join(_format_calls(OrderedDict( - (pk, val) for pk, val in actual.items() - if pk not in expected or val != expected.get(pk) - ))) + return ''.join(_format_calls(OrderedDict((pk, val) for pk, val in actual.items() if pk not in expected or val != expected.get(pk)))) @property def unexpected(self): diff --git a/src/aspectlib/utils.py b/src/aspectlib/utils.py index 9e0837e..c936911 100644 --- a/src/aspectlib/utils.py +++ b/src/aspectlib/utils.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import logging import os import platform @@ -12,9 +10,7 @@ RegexType = type(re.compile("")) PY3 = sys.version_info[0] == 3 -PY37plus = PY3 and sys.version_info[1] >= 7 -PY2 = sys.version_info[0] == 2 -PY26 = PY2 and sys.version_info[1] == 6 +PY310 = PY3 and sys.version_info[1] >= 10 PYPY = platform.python_implementation() == 'PyPy' if PY3: @@ -43,6 +39,7 @@ def log_wrapper(*args): logging.logProcesses = logProcesses logging.logThreads = logThreads logging.logMultiprocessing = logMultiprocessing + return log_wrapper @@ -61,6 +58,7 @@ def qualname(obj): def force_bind(func): def bound(self, *args, **kwargs): # pylint: disable=W0613 return func(*args, **kwargs) + bound.__name__ = func.__name__ bound.__doc__ = func.__doc__ return bound @@ -87,6 +85,7 @@ def __repr__(self): return "%s" % self.name else: return "%s: %s" % (self.name, self.__doc__) + __str__ = __repr__ @@ -94,13 +93,17 @@ def container(name): def __init__(self, value): self.value = value - return type(name, (object,), { - '__slots__': 'value', - '__init__': __init__, - '__str__': lambda self: "%s(%s)" % (name, self.value), - '__repr__': lambda self: "%s(%r)" % (name, self.value), - '__eq__': lambda self, other: type(self) is type(other) and self.value == other.value, - }) + return type( + name, + (object,), + { + '__slots__': 'value', + '__init__': __init__, + '__str__': lambda self: "%s(%s)" % (name, self.value), + '__repr__': lambda self: "%s(%r)" % (name, self.value), + '__eq__': lambda self, other: type(self) is type(other) and self.value == other.value, + }, + ) def mimic(wrapper, func, module=None): @@ -125,9 +128,8 @@ def mimic(wrapper, func, module=None): set: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj), frozenset: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj), deque: lambda obj, aliases: "collections.deque([%s])" % ', '.join(repr_ex(i) for i in obj), - dict: lambda obj, aliases: "{%s}" % ', '.join( - "%s: %s" % (repr_ex(k), repr_ex(v)) for k, v in (obj.items() if PY3 else obj.iteritems()) - ), + dict: lambda obj, aliases: "{%s}" + % ', '.join("%s: %s" % (repr_ex(k), repr_ex(v)) for k, v in (obj.items() if PY3 else obj.iteritems())), } @@ -135,10 +137,7 @@ def _make_fixups(): for obj in ('os.stat_result', 'grp.struct_group', 'pwd.struct_passwd'): mod, attr = obj.split('.') try: - yield getattr(__import__(mod), attr), lambda obj, aliases, prefix=obj: "%s(%r)" % ( - prefix, - obj.__reduce__()[1][0] - ) + yield getattr(__import__(mod), attr), lambda obj, aliases, prefix=obj: "%s(%r)" % (prefix, obj.__reduce__()[1][0]) except ImportError: continue diff --git a/tests/mymod.py b/tests/mymod.py index cc8a3d8..9855e10 100644 --- a/tests/mymod.py +++ b/tests/mymod.py @@ -1,6 +1,3 @@ -from __future__ import print_function - - def func(arg): print("Got", arg, "in the real code!") diff --git a/tests/test_aspectlib.py b/tests/test_aspectlib.py index 6146e86..a50558f 100644 --- a/tests/test_aspectlib.py +++ b/tests/test_aspectlib.py @@ -1,7 +1,4 @@ # encoding: utf8 -from __future__ import print_function - -import pytest from pytest import raises import aspectlib @@ -401,27 +398,33 @@ def test_weave_str_bad_target(): def test_weave_str_target(): with aspectlib.weave('test_pkg1.test_pkg2.test_mod.target', mock('foobar')): from test_pkg1.test_pkg2.test_mod import target + assert target() == 'foobar' from test_pkg1.test_pkg2.test_mod import target + assert target() is None def test_weave_str_class_target(): with aspectlib.weave('test_pkg1.test_pkg2.test_mod.Stuff', mock('foobar')): from test_pkg1.test_pkg2.test_mod import Stuff + assert Stuff().meth() == 'foobar' from test_pkg1.test_pkg2.test_mod import Stuff + assert Stuff().meth() is None def test_weave_str_class_meth_target(): with aspectlib.weave('test_pkg1.test_pkg2.test_mod.Stuff.meth', mock('foobar')): from test_pkg1.test_pkg2.test_mod import Stuff + assert Stuff().meth() == 'foobar' from test_pkg1.test_pkg2.test_mod import Stuff + assert Stuff().meth() is None @@ -439,10 +442,14 @@ def test_weave_wrong_module(): with aspectlib.weave('warnings.warn', record(calls=calls)): aspectlib.weave(AliasedGlobal, mock('stuff'), lazy=True) assert calls == [ - (None, - ("Setting test_aspectlib.MissingGlobal to . " - "There was no previous definition, probably patching the wrong module.",), - {}) + ( + None, + ( + "Setting test_aspectlib.MissingGlobal to . " + "There was no previous definition, probably patching the wrong module.", + ), + {}, + ) ] @@ -457,19 +464,6 @@ def test_weave_no_aliases(): assert module_func2 is module_func3 -@pytest.mark.skipif('aspectlib.PY3') -def test_weave_class_meth_no_aliases(): - with aspectlib.weave(Global.meth, mock('stuff'), aliases=False, lazy=True): - assert Global().meth() == 'stuff' - assert Global2 is not Global - assert Global2().meth() == 'base' - - assert Global().meth() == 'base' - assert Global2 is Global - assert Global2().meth() == 'base' - - -@pytest.mark.skipif('aspectlib.PY2') def test_weave_class_meth_no_aliases_unsupported_on_py3(): with aspectlib.weave(Global.meth, mock('stuff')): assert Global().meth() == 'stuff' @@ -540,7 +534,6 @@ def aspect(self): assert inst.foo == 'stuff' -@pytest.mark.skipif('aspectlib.PY2') def test_weave_legacy_instance(): @aspectlib.Aspect def aspect(self): @@ -657,7 +650,7 @@ def test_weave_class(): @aspectlib.Aspect def aspect(*args): history.append(args) - args += ':)', + args += (':)',) yield aspectlib.Proceed(*args) yield aspectlib.Return('bar') @@ -754,7 +747,7 @@ def test_weave_class_slots(): @aspectlib.Aspect def aspect(*args): history.append(args) - args += ':)', + args += (':)',) yield aspectlib.Proceed(*args) yield aspectlib.Return('bar') @@ -860,7 +853,7 @@ def test_weave_class_on_init(): @aspectlib.Aspect def aspect(*args): history.append(args) - args += ':)', + args += (':)',) yield aspectlib.Proceed(*args) yield aspectlib.Return('bar') @@ -953,7 +946,7 @@ def test_weave_class_old_style(): @aspectlib.Aspect def aspect(*args): history.append(args) - args += ':)', + args += (':)',) yield aspectlib.Proceed(*args) yield aspectlib.Return('bar') @@ -1190,15 +1183,6 @@ def test_weave_subclass_meth_manual(): assert Sub().meth() == 'base' -@pytest.mark.skipif('aspectlib.PY3') -def test_weave_subclass_meth_auto(): - with aspectlib.weave(Sub.meth, mock('foobar'), lazy=True): - assert Sub().meth() == 'foobar' - - assert Sub().meth() == 'base' - - -@pytest.mark.skipif('aspectlib.PY2') def test_weave_subclass_meth_auto2(): with aspectlib.weave(Sub.meth, mock('foobar')): assert Sub().meth() == 'foobar' @@ -1227,22 +1211,13 @@ def _internal(): pass -if aspectlib.PY3: - exec(u"""# encoding: utf8 - def ăbc(): pass + def test_ăbc(): with aspectlib.weave('test_aspectlib.ăbc', mock('stuff')): assert ăbc() == 'stuff' -""") -else: - def test_py2_invalid_unicode_in_string_target(): - raises(SyntaxError, aspectlib.weave, 'os.ăa', mock(None)) - raises(SyntaxError, aspectlib.weave, u'os.ăa', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.aă', mock(None)) - raises(SyntaxError, aspectlib.weave, u'os.aă', mock(None)) def test_invalid_string_target(): @@ -1471,14 +1446,12 @@ def func(): def test_weave_module(strmod=None): calls = [] from test_pkg1.test_pkg2 import test_mod + with aspectlib.weave(strmod or test_mod, record(calls=calls, extended=True)): test_mod.target() obj = test_mod.Stuff() obj.meth() - assert calls == [ - (None, 'test_pkg1.test_pkg2.test_mod.target', (), {}), - (obj, 'test_pkg1.test_pkg2.test_mod.meth', (), {}) - ] + assert calls == [(None, 'test_pkg1.test_pkg2.test_mod.target', (), {}), (obj, 'test_pkg1.test_pkg2.test_mod.meth', (), {})] def test_weave_module_as_str(): diff --git a/tests/test_aspectlib_debug.py b/tests/test_aspectlib_debug.py index 94eff55..4d91c46 100644 --- a/tests/test_aspectlib_debug.py +++ b/tests/test_aspectlib_debug.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import logging import re import sys @@ -15,10 +13,12 @@ except ImportError: from io import StringIO -LOG_TEST_SIMPLE = r'''^some_meth\(1, 2, 3, a=4\) +<<< .*tests/test_aspectlib_debug.py:\d+:test_simple.* -some_meth => \.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. !"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@''' \ -r'''ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~\.+ +LOG_TEST_SIMPLE = ( + r'''^some_meth\(1, 2, 3, a=4\) +<<< .*tests/test_aspectlib_debug.py:\d+:test_simple.* +some_meth => \.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. !"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@''' + r'''ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~\.+ $''' +) def some_meth(*_args, **_kwargs): @@ -61,6 +61,7 @@ def test_fail_to_log(): @aspectlib.debug.log(print_to="crap") def foo(): pass + foo() @@ -73,66 +74,75 @@ def test_logging_works(): @aspectlib.debug.log def foo(): pass + foo() assert re.match(r'foo\(\) +<<<.*\nfoo => None\n', buf.getvalue()) def test_attributes(): buf = StringIO() - with aspectlib.weave(MyStuff, aspectlib.debug.log( - print_to=buf, - stacktrace=10, - attributes=('foo', 'bar()') - ), methods='(?!bar)(?!__.*__$)'): + with aspectlib.weave( + MyStuff, aspectlib.debug.log(print_to=buf, stacktrace=10, attributes=('foo', 'bar()')), methods='(?!bar)(?!__.*__$)' + ): MyStuff('bar').stuff() print(buf.getvalue()) - assert re.match(r"^\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" - r"test_attributes.*\n\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff => bar\n$", buf.getvalue()) + assert re.match( + r"^\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" + r"test_attributes.*\n\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff => bar\n$", + buf.getvalue(), + ) MyStuff('bar').stuff() - assert re.match(r"^\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" - r"test_attributes.*\n\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff => bar\n$", buf.getvalue()) + assert re.match( + r"^\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" + r"test_attributes.*\n\{test_aspectlib_debug.MyStuff foo='bar' bar='foo'\}.stuff => bar\n$", + buf.getvalue(), + ) def test_no_stack(): buf = StringIO() - with aspectlib.weave(MyStuff, aspectlib.debug.log( - print_to=buf, - stacktrace=None, - attributes=('foo', 'bar()') - ), methods='(?!bar)(?!__.*__$)'): + with aspectlib.weave( + MyStuff, aspectlib.debug.log(print_to=buf, stacktrace=None, attributes=('foo', 'bar()')), methods='(?!bar)(?!__.*__$)' + ): MyStuff('bar').stuff() print(buf.getvalue()) - assert "{test_aspectlib_debug.MyStuff foo='bar' bar='foo'}.stuff()\n" \ - "{test_aspectlib_debug.MyStuff foo='bar' bar='foo'}.stuff => bar\n" == buf.getvalue() + assert ( + "{test_aspectlib_debug.MyStuff foo='bar' bar='foo'}.stuff()\n" + "{test_aspectlib_debug.MyStuff foo='bar' bar='foo'}.stuff => bar\n" == buf.getvalue() + ) def test_attributes_old_style(): buf = StringIO() - with aspectlib.weave(OldStuff, aspectlib.debug.log( - print_to=buf, - stacktrace=10, - attributes=('foo', 'bar()') - ), methods='(?!bar)(?!__.*__$)'): + with aspectlib.weave( + OldStuff, aspectlib.debug.log(print_to=buf, stacktrace=10, attributes=('foo', 'bar()')), methods='(?!bar)(?!__.*__$)' + ): OldStuff('bar').stuff() print(repr(buf.getvalue())) - assert re.match(r"^\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" - r"test_attributes.*\n\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff => bar\n$", buf.getvalue()) + assert re.match( + r"^\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" + r"test_attributes.*\n\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff => bar\n$", + buf.getvalue(), + ) MyStuff('bar').stuff() - assert re.match(r"^\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" - r"test_attributes.*\n\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff => bar\n$", buf.getvalue()) + assert re.match( + r"^\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff\(\) +<<< .*tests/test_aspectlib_debug.py:\d+:" + r"test_attributes.*\n\{test_aspectlib_debug.OldStuff foo='bar' bar='foo'\}.stuff => bar\n$", + buf.getvalue(), + ) def test_no_stack_old_style(): buf = StringIO() - with aspectlib.weave(OldStuff, aspectlib.debug.log( - print_to=buf, - stacktrace=None, - attributes=('foo', 'bar()') - ), methods='(?!bar)(?!__.*__$)'): + with aspectlib.weave( + OldStuff, aspectlib.debug.log(print_to=buf, stacktrace=None, attributes=('foo', 'bar()')), methods='(?!bar)(?!__.*__$)' + ): OldStuff('bar').stuff() print(buf.getvalue()) - assert "{test_aspectlib_debug.OldStuff foo='bar' bar='foo'}.stuff()\n" \ - "{test_aspectlib_debug.OldStuff foo='bar' bar='foo'}.stuff => bar\n" == buf.getvalue() + assert ( + "{test_aspectlib_debug.OldStuff foo='bar' bar='foo'}.stuff()\n" + "{test_aspectlib_debug.OldStuff foo='bar' bar='foo'}.stuff => bar\n" == buf.getvalue() + ) @pytest.mark.skipif(sys.version_info < (2, 7), reason="No weakref.WeakSet on Python<=2.6") diff --git a/tests/test_aspectlib_py23.py b/tests/test_aspectlib_py23.py deleted file mode 100644 index 6a65209..0000000 --- a/tests/test_aspectlib_py23.py +++ /dev/null @@ -1,169 +0,0 @@ -# encoding: utf8 -from __future__ import print_function - -import pytest - -import aspectlib -from aspectlib.utils import PY37plus - -pytestmark = pytest.mark.skipif(PY37plus, reason="Tests are incompatible with PEP-479") - - -def test_aspect_on_generator_result(): - result = [] - - @aspectlib.Aspect - def aspect(): - result.append((yield aspectlib.Proceed)) - - @aspect - def func(): - yield 'something' - raise StopIteration('value') - - assert list(func()) == ['something'] - assert result == ['value'] - - -def test_aspect_on_coroutine(): - hist = [] - - @aspectlib.Aspect - def aspect(): - try: - hist.append('before') - hist.append((yield aspectlib.Proceed)) - hist.append('after') - except Exception: - hist.append('error') - finally: - hist.append('finally') - try: - hist.append((yield aspectlib.Return)) - except GeneratorExit: - hist.append('closed') - raise - else: - hist.append('consumed') - hist.append('bad-suffix') - - @aspect - def func(): - val = 99 - for _ in range(3): - print("YIELD", val + 1) - val = yield val + 1 - print("GOT", val) - raise StopIteration("the-return-value") - - gen = func() - data = [] - try: - for i in [None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]: - data.append(gen.send(i)) - except StopIteration: - data.append('done') - print(data) - assert data == [100, 1, 2, 'done'], hist - print(hist) - assert hist == ['before', 'the-return-value', 'after', 'finally', 'closed'] - - -def test_aspect_chain_on_generator(): - @aspectlib.Aspect - def foo(arg): - result = yield aspectlib.Proceed(arg + 1) - yield aspectlib.Return(result - 1) - - @foo - @foo - @foo - def func(a): - assert a == 3 - raise StopIteration(a) - yield - - gen = func(0) - result = pytest.raises(StopIteration, gen.__next__ if hasattr(gen, '__next__') else gen.next) - assert result.value.args == (0,) - - -def test_aspect_chain_on_generator_no_return_advice(): - @aspectlib.Aspect - def foo(arg): - yield aspectlib.Proceed(arg + 1) - - @foo - @foo - @foo - def func(a): - assert a == 3 - raise StopIteration(a) - yield - - gen = func(0) - if hasattr(gen, '__next__'): - result = pytest.raises(StopIteration, gen.__next__) - else: - result = pytest.raises(StopIteration, gen.next) - assert result.value.args == (3,) - - -def test_aspect_on_generator_raise_stopiteration(): - result = [] - - @aspectlib.Aspect - def aspect(): - val = yield aspectlib.Proceed - result.append(val) - - @aspect - def func(): - raise StopIteration('something') - yield - - assert list(func()) == [] - assert result == ['something'] - - -def test_aspect_on_generator_result_from_aspect(): - @aspectlib.Aspect - def aspect(): - yield aspectlib.Proceed - yield aspectlib.Return('result') - - @aspect - def func(): - yield 'something' - - gen = func() - try: - while 1: - next(gen) - except StopIteration as exc: - assert exc.args == ('result',) - else: - raise AssertionError("did not raise StopIteration") - - -def test_aspect_chain_on_generator_no_return(): - @aspectlib.Aspect - def foo(arg): - result = yield aspectlib.Proceed(arg + 1) - yield aspectlib.Return(result) - - @foo - @foo - @foo - def func(a): - assert a == 3 - yield - - gen = func(0) - if hasattr(gen, '__next__'): - assert gen.__next__() is None - result = pytest.raises(StopIteration, gen.__next__) - else: - assert gen.next() is None - result = pytest.raises(StopIteration, gen.next) - assert result.value.args == (None,) diff --git a/tests/test_aspectlib_py3.py b/tests/test_aspectlib_py3.py index db9864d..40c056b 100644 --- a/tests/test_aspectlib_py3.py +++ b/tests/test_aspectlib_py3.py @@ -1,5 +1,5 @@ # encoding: utf8 -from __future__ import print_function + import pytest diff --git a/tests/test_aspectlib_py37.py b/tests/test_aspectlib_py37.py index 43e340b..96b112b 100644 --- a/tests/test_aspectlib_py37.py +++ b/tests/test_aspectlib_py37.py @@ -1,15 +1,12 @@ # encoding: utf8 -from __future__ import print_function + import pytest import aspectlib -from aspectlib.utils import PY37plus from test_aspectlib_py3 import consume -pytestmark = pytest.mark.skipif(not PY37plus, reason="Tests only work with PEP-479") - def test_aspect_on_generator_result(): result = [] diff --git a/tests/test_aspectlib_test.py b/tests/test_aspectlib_test.py index 05e2c25..16c8e9c 100644 --- a/tests/test_aspectlib_test.py +++ b/tests/test_aspectlib_test.py @@ -1,9 +1,6 @@ -from __future__ import print_function - from pytest import raises from test_pkg1.test_pkg2 import test_mod -from aspectlib import PY2 from aspectlib.test import OrderedDict from aspectlib.test import Story from aspectlib.test import StoryResultWrapper @@ -13,10 +10,10 @@ from aspectlib.test import _Returns from aspectlib.test import mock from aspectlib.test import record -from aspectlib.utils import PY26 +from aspectlib.utils import PY310 from aspectlib.utils import repr_ex -pytest_plugins = 'pytester', +pytest_plugins = ('pytester',) def format_calls(calls): @@ -49,7 +46,7 @@ def test_record(): assert fun(3, b=4) == (3, 4) assert fun.calls == [ (None, (2, 3), {}), - (None, (3, ), {'b': 4}), + (None, (3,), {'b': 4}), ] @@ -60,7 +57,7 @@ def test_record_result(): assert fun(3, b=4) == (3, 4) assert fun.calls == [ (None, (2, 3), {}, (2, 3), None), - (None, (3, ), {'b': 4}, (3, 4), None), + (None, (3,), {'b': 4}, (3, 4), None), ] @@ -82,7 +79,7 @@ def test_record_result_callback(): assert fun(3, b=4) == (3, 4) assert calls == [ (None, 'test_aspectlib_test.nfun', (2, 3), {}, (2, 3), None), - (None, 'test_aspectlib_test.nfun', (3, ), {'b': 4}, (3, 4), None), + (None, 'test_aspectlib_test.nfun', (3,), {'b': 4}, (3, 4), None), ] @@ -106,7 +103,7 @@ def test_record_callback(): assert fun(3, b=4) == (3, 4) assert calls == [ (None, 'test_aspectlib_test.nfun', (2, 3), {}), - (None, 'test_aspectlib_test.nfun', (3, ), {'b': 4}), + (None, 'test_aspectlib_test.nfun', (3,), {'b': 4}), ] @@ -145,7 +142,7 @@ def test_record_as_context(): assert history.calls == [ (None, (2, 3), {}), - (None, (3, ), {'b': 4}), + (None, (3,), {'b': 4}), ] del history.calls[:] @@ -217,13 +214,23 @@ def test_story_empty_play_proxy(): assert test_mod.target() is None raises(TypeError, test_mod.target, 123) - assert format_calls(replay._actual) == format_calls(OrderedDict([ - ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), - ((None, 'test_pkg1.test_pkg2.test_mod.target', '123', ''), _Raises(repr_ex(TypeError( - 'target() takes no arguments (1 given)' if PY2 else - 'target() takes 0 positional arguments but 1 was given', - )))) - ])) + assert format_calls(replay._actual) == format_calls( + OrderedDict( + [ + ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), + ( + (None, 'test_pkg1.test_pkg2.test_mod.target', '123', ''), + _Raises( + repr_ex( + TypeError( + 'target() takes 0 positional arguments but 1 was given', + ) + ) + ), + ), + ] + ) + ) def test_story_empty_play_noproxy_class(): @@ -237,9 +244,7 @@ def test_story_empty_play_error_on_init(): with Story(test_mod).replay(strict=False) as replay: raises(ValueError, test_mod.Stuff, "error") print(replay._actual) - assert replay._actual == OrderedDict([ - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'error'", ''), _Raises('ValueError()')) - ]) + assert replay._actual == OrderedDict([((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'error'", ''), _Raises('ValueError()'))]) def test_story_half_play_noproxy_class(): @@ -303,36 +308,27 @@ def test_story_text_helpers(): test_mod.target(1) print(replay.missing) - assert replay.missing == """stuff_1.meth('b') == 'y' # returns + assert ( + replay.missing + == """stuff_1.meth('b') == 'y' # returns stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(2, 3) stuff_2.meth('c') == 'z' # returns test_pkg1.test_pkg2.test_mod.target(2) == 3 # returns """ + ) print(replay.unexpected) - assert replay.unexpected == """stuff_1.meth() == None # returns + assert ( + replay.unexpected + == """stuff_1.meth() == None # returns stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(4, 4) stuff_2.meth() == None # returns test_pkg1.test_pkg2.test_mod.func(5) == None # returns """ + ) print(replay.diff) - if PY26: - assert replay.diff == """--- expected """ """ -+++ actual """ """ -@@ -1,7 +1,7 @@ - stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2) - stuff_1.meth('a') == 'x' # returns --stuff_1.meth('b') == 'y' # returns --stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(2, 3) --stuff_2.meth('c') == 'z' # returns -+stuff_1.meth() == None # returns -+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(4, 4) -+stuff_2.meth() == None # returns -+test_pkg1.test_pkg2.test_mod.func(5) == None # returns - test_pkg1.test_pkg2.test_mod.target(1) == 2 # returns --test_pkg1.test_pkg2.test_mod.target(2) == 3 # returns -""" - else: - assert replay.diff == """--- expected + assert ( + replay.diff + == """--- expected +++ actual @@ -1,7 +1,7 @@ stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2) @@ -347,6 +343,7 @@ def test_story_text_helpers(): test_pkg1.test_pkg2.test_mod.target(1) == 2 # returns -test_pkg1.test_pkg2.test_mod.target(2) == 3 # returns """ + ) def test_story_empty_play_proxy_class_missing_report(LineMatcher): @@ -367,29 +364,31 @@ def test_story_empty_play_proxy_class_missing_report(LineMatcher): obj.mix() obj.meth() obj.mix(10) - LineMatcher(replay.diff.splitlines()).fnmatch_lines([ - "--- expected", - "+++ actual", - "@@ -0,0 +1,18 @@", - "+stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)", - "+stuff_1.mix(3, 4) == (1, 2, 3, 4) # returns", - "+stuff_1.mix('a', 'b') == (1, 2, 'a', 'b') # returns", - "+stuff_1.raises(123) ** ValueError((123,)*) # raises", - "+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)", - "+stuff_2.mix('a', 'b') == (0, 1, 'a', 'b') # returns", - "+stuff_2.mix(3, 4) == (0, 1, 3, 4) # returns", - "+test_pkg1.test_pkg2.test_mod.target() == None # returns", - "+test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',)*) # raises", - "+stuff_2.raises(123) ** ValueError((123,)*) # raises", - "+that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)", - "+that_long_stuf_1.mix(2) == (1, 2) # returns", - "+that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)", - "+that_long_stuf_2.mix(4) == (3, 4) # returns", - "+that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)", - "+that_long_stuf_3.mix() == (2,) # returns", - "+that_long_stuf_3.meth() == None # returns", - "+that_long_stuf_3.mix(10) == (2, 10) # returns", - ]) + LineMatcher(replay.diff.splitlines()).fnmatch_lines( + [ + "--- expected", + "+++ actual", + "@@ -0,0 +1,18 @@", + "+stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)", + "+stuff_1.mix(3, 4) == (1, 2, 3, 4) # returns", + "+stuff_1.mix('a', 'b') == (1, 2, 'a', 'b') # returns", + "+stuff_1.raises(123) ** ValueError((123,)*) # raises", + "+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)", + "+stuff_2.mix('a', 'b') == (0, 1, 'a', 'b') # returns", + "+stuff_2.mix(3, 4) == (0, 1, 3, 4) # returns", + "+test_pkg1.test_pkg2.test_mod.target() == None # returns", + "+test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',)*) # raises", + "+stuff_2.raises(123) ** ValueError((123,)*) # raises", + "+that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)", + "+that_long_stuf_1.mix(2) == (1, 2) # returns", + "+that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)", + "+that_long_stuf_2.mix(4) == (3, 4) # returns", + "+that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)", + "+that_long_stuf_3.mix() == (2,) # returns", + "+that_long_stuf_3.meth() == None # returns", + "+that_long_stuf_3.mix(10) == (2, 10) # returns", + ] + ) def test_story_empty_play_proxy_class(): @@ -408,22 +407,42 @@ def test_story_empty_play_proxy_class(): raises(TypeError, obj.meth, 123) - assert format_calls(replay._actual) == format_calls(OrderedDict([ - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "1, 2", ''), _Binds('stuff_1')), - (('stuff_1', 'mix', "3, 4", ''), _Returns("(1, 2, 3, 4)")), - (('stuff_1', 'mix', "'a', 'b'", ''), _Returns("(1, 2, 'a', 'b')")), - (('stuff_1', 'meth', "123", ''), _Raises(repr_ex(TypeError( - 'meth() takes exactly 1 argument (2 given)' if PY2 else - 'meth() takes 1 positional argument but 2 were given' - )))), - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "0, 1", ''), _Binds('stuff_2')), - (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), - (('stuff_2', 'mix', "3, 4", ''), _Returns("(0, 1, 3, 4)")), - (('stuff_2', 'meth', "123", ''), _Raises(repr_ex(TypeError( - 'meth() takes exactly 1 argument (2 given)' if PY2 else - 'meth() takes 1 positional argument but 2 were given' - )))) - ])) + assert format_calls(replay._actual) == format_calls( + OrderedDict( + [ + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "1, 2", ''), _Binds('stuff_1')), + (('stuff_1', 'mix', "3, 4", ''), _Returns("(1, 2, 3, 4)")), + (('stuff_1', 'mix', "'a', 'b'", ''), _Returns("(1, 2, 'a', 'b')")), + ( + ('stuff_1', 'meth', "123", ''), + _Raises( + repr_ex( + TypeError( + 'Stuff.meth() takes 1 positional argument but 2 were given' + if PY310 + else 'meth() takes 1 positional argument but 2 were given' + ) + ) + ), + ), + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "0, 1", ''), _Binds('stuff_2')), + (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), + (('stuff_2', 'mix', "3, 4", ''), _Returns("(0, 1, 3, 4)")), + ( + ('stuff_2', 'meth', "123", ''), + _Raises( + repr_ex( + TypeError( + 'Stuff.meth() takes 1 positional argument but 2 were given' + if PY310 + else 'meth() takes 1 positional argument but 2 were given' + ) + ) + ), + ), + ] + ) + ) def test_story_half_play_proxy_class(): @@ -445,20 +464,40 @@ def test_story_half_play_proxy_class(): assert obj.mix(3, 4) == (0, 1, 3, 4) raises(TypeError, obj.meth, 123) - assert replay.unexpected == format_calls(OrderedDict([ - (('stuff_1', 'meth', '', ''), _Returns('None')), - (('stuff_1', 'meth', '123', ''), _Raises(repr_ex(TypeError( - 'meth() takes exactly 1 argument (2 given)' if PY2 else - 'meth() takes 1 positional argument but 2 were given' - )))), - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '0, 1', ''), _Binds("stuff_2")), - (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), - (('stuff_2', 'mix', '3, 4', ''), _Returns('(0, 1, 3, 4)')), - (('stuff_2', 'meth', '123', ''), _Raises(repr_ex(TypeError( - 'meth() takes exactly 1 argument (2 given)' if PY2 else - 'meth() takes 1 positional argument but 2 were given' - )))) - ])) + assert replay.unexpected == format_calls( + OrderedDict( + [ + (('stuff_1', 'meth', '', ''), _Returns('None')), + ( + ('stuff_1', 'meth', '123', ''), + _Raises( + repr_ex( + TypeError( + 'Stuff.meth() takes 1 positional argument but 2 were given' + if PY310 + else 'meth() takes 1 positional argument but 2 were given' + ) + ) + ), + ), + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '0, 1', ''), _Binds("stuff_2")), + (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), + (('stuff_2', 'mix', '3, 4', ''), _Returns('(0, 1, 3, 4)')), + ( + ('stuff_2', 'meth', '123', ''), + _Raises( + repr_ex( + TypeError( + 'Stuff.meth() takes 1 positional argument but 2 were given' + if PY310 + else 'meth() takes 1 positional argument but 2 were given' + ) + ) + ), + ), + ] + ) + ) def test_story_full_play_noproxy(): @@ -498,14 +537,23 @@ def test_story_full_play_proxy(): raises(ValueError, test_mod.target, 1234) raises(TypeError, test_mod.target, 'asdf') - assert replay.unexpected == format_calls(OrderedDict([ - ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), - ((None, 'test_pkg1.test_pkg2.test_mod.target', "'asdf'", ''), _Raises(repr_ex(TypeError( - 'target() takes no arguments (1 given)' - if PY2 - else 'target() takes 0 positional arguments but 1 was given',) - ))) - ])) + assert replay.unexpected == format_calls( + OrderedDict( + [ + ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), + ( + (None, 'test_pkg1.test_pkg2.test_mod.target', "'asdf'", ''), + _Raises( + repr_ex( + TypeError( + 'target() takes 0 positional arguments but 1 was given', + ) + ) + ), + ), + ] + ) + ) def test_story_result_wrapper(): @@ -521,8 +569,8 @@ def test_story_result_wrapper(): def test_story_result_wrapper_bad_exception(): x = StoryResultWrapper(lambda *a: None) - raises(RuntimeError, lambda: x ** 1) - x ** Exception + raises(RuntimeError, lambda: x**1) + x**Exception x ** Exception('boom!') @@ -538,7 +586,7 @@ def test_story_create(): # from pprint import pprint as print # print (dict(story._calls)) assert dict(story._calls) == { - (None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'stuff'", ''): _Binds('stuff_1'), + (None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'stuff'", ''): _Binds('stuff_1'), ('stuff_1', 'meth', "'other', 1, 2", ''): _Returns("123"), ('stuff_1', 'mix', "'other'", ''): _Returns("'mixymix'"), (None, 'test_pkg1.test_pkg2.test_mod.target', '', ''): _Raises("Exception"), diff --git a/tests/test_integrations.py b/tests/test_integrations.py index e4dc004..08f6e13 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -1,15 +1,18 @@ -from __future__ import print_function - +import asyncio import os import re import socket import warnings +from datetime import timedelta import pytest from process_tests import dump_on_error from process_tests import wait_for_strings +from tornado import gen +from tornado import ioloop import aspectlib +from aspectlib import debug from aspectlib.test import mock from aspectlib.test import record from aspectlib.utils import PYPY @@ -60,11 +63,7 @@ def test_fork(): def test_socket(target=socket.socket): buf = StringIO() - with aspectlib.weave(target, aspectlib.debug.log( - print_to=buf, - stacktrace=4, - module=False - ), lazy=True): + with aspectlib.weave(target, aspectlib.debug.log(print_to=buf, stacktrace=4, module=False), lazy=True): s = socket.socket() try: s.connect(('127.0.0.1', 1)) @@ -106,12 +105,7 @@ def test_socket_meth_as_string_target(): def test_socket_all_methods(): buf = StringIO() - with aspectlib.weave( - socket.socket, - aspectlib.debug.log(print_to=buf, stacktrace=False), - lazy=True, - methods=aspectlib.ALL_METHODS - ): + with aspectlib.weave(socket.socket, aspectlib.debug.log(print_to=buf, stacktrace=False), lazy=True, methods=aspectlib.ALL_METHODS): socket.socket() assert "}.__init__ => None" in buf.getvalue() @@ -128,8 +122,7 @@ def test_realsocket_makefile(): if pid: with aspectlib.weave( - ['socket._fileobject' if aspectlib.PY2 else 'socket.SocketIO'] + - (['socket.socket', 'socket._realsocket'] if aspectlib.PY2 else ['socket.socket']), + ['socket.SocketIO', 'socket.socket'], aspectlib.debug.log(print_to=buf, stacktrace=False), lazy=True, methods=aspectlib.ALL_METHODS, @@ -137,16 +130,14 @@ def test_realsocket_makefile(): s = socket.socket() s.settimeout(1) s.connect(p.getsockname()) - if aspectlib.PY3: - fh = s.makefile('rwb', buffering=0) - else: - fh = s.makefile(bufsize=0) + fh = s.makefile('rwb', buffering=0) fh.write(b"STUFF\n") fh.readline() with dump_on_error(buf.getvalue): wait_for_strings( - buf.getvalue, 0, + buf.getvalue, + 0, "}.connect", "}.makefile", "}.write(", @@ -160,10 +151,7 @@ def test_realsocket_makefile(): try: c, _ = p.accept() c.settimeout(1) - if aspectlib.PY3: - f = c.makefile('rw', buffering=1) - else: - f = c.makefile(bufsize=1) + f = c.makefile('rw', buffering=1) while f.readline(): f.write('-\n') finally: @@ -177,7 +165,43 @@ def test_weave_os_module(): os.getenv('BUBU', 'bubu') os.walk('.') - assert calls == [ - (None, 'os.getenv', ('BUBU', 'bubu'), {}), - (None, 'os.walk', ('.',), {}) - ] + assert calls == [(None, 'os.getenv', ('BUBU', 'bubu'), {}), (None, 'os.walk', ('.',), {})] + + +def test_decorate_asyncio_coroutine(): + buf = StringIO() + + @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) + async def coro(): + await asyncio.sleep(0.01) + return "result" + + loop = asyncio.new_event_loop() + loop.run_until_complete(coro()) + output = buf.getvalue() + print(output) + assert 'coro => %r' % 'result' in output + + +def test_decorate_tornado_coroutine(): + buf = StringIO() + + @gen.coroutine + @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) + def coro(): + if hasattr(gen, 'Task'): + yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) + else: + yield gen.sleep(0.01) + return "result" + + asyncio_loop = asyncio.new_event_loop() + try: + get_event_loop = asyncio.get_event_loop + asyncio.get_event_loop = lambda: asyncio_loop + loop = ioloop.IOLoop.current() + loop.run_sync(coro) + finally: + asyncio.get_event_loop = get_event_loop + output = buf.getvalue() + assert 'coro => %r' % 'result' in output diff --git a/tests/test_integrations_py2.py b/tests/test_integrations_py2.py deleted file mode 100644 index fc05188..0000000 --- a/tests/test_integrations_py2.py +++ /dev/null @@ -1,64 +0,0 @@ -try: - from StringIO import StringIO -except ImportError: - from io import StringIO -from datetime import timedelta - -import pytest -from tornado import gen -from tornado import ioloop - -from aspectlib import debug -from aspectlib.test import Story - -asyncio = pytest.importorskip("trollius") - -try: - import MySQLdb -except ImportError: - MySQLdb = None - - -def test_decorate_asyncio_coroutine(): - buf = StringIO() - - @asyncio.coroutine - @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) - def coro(): - yield asyncio.From(asyncio.sleep(0.01)) - raise StopIteration("result") - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - output = buf.getvalue() - assert 'coro => %r' % 'result' in output - - -def test_decorate_tornado_coroutine(): - buf = StringIO() - - @gen.coroutine - @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) - def coro(): - yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) - raise StopIteration("result") - - loop = ioloop.IOLoop.current() - loop.run_sync(coro) - output = buf.getvalue() - assert 'coro => %r' % 'result' in output - - -@pytest.mark.skipif(not MySQLdb, reason="No MySQLdb installed") -def test_mysql(): - with Story(['MySQLdb.cursors.BaseCursor', 'MySQLdb.connections.Connection']) as story: - pass - rows = [] - with story.replay(strict=False) as replay: - import MySQLdb - con = MySQLdb.connect('localhost', 'root', '') - con.select_db('mysql') - cursor = con.cursor() - cursor.execute('show tables') - rows.extend(cursor.fetchall()) - assert '== (%s)' % ', '.join(repr(row) for row in rows) in replay.unexpected diff --git a/tests/test_integrations_py3.py b/tests/test_integrations_py3.py deleted file mode 100644 index 596589d..0000000 --- a/tests/test_integrations_py3.py +++ /dev/null @@ -1,48 +0,0 @@ -import asyncio -from datetime import timedelta - -import pytest -from tornado import gen -from tornado import ioloop - -from aspectlib import PY37plus -from aspectlib import debug - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - - -@pytest.mark.skipif(PY37plus, reason="Test is incompatible with PEP-479") -def test_decorate_asyncio_coroutine(): - buf = StringIO() - - @asyncio.coroutine - @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) - def coro(): - yield from asyncio.sleep(0.01) - return "result" - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - output = buf.getvalue() - assert 'coro => %r' % 'result' in output - - -def test_decorate_tornado_coroutine(): - buf = StringIO() - - @gen.coroutine - @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) - def coro(): - if hasattr(gen, 'Task'): - yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) - else: - yield gen.sleep(0.01) - return "result" - - loop = ioloop.IOLoop.current() - loop.run_sync(coro) - output = buf.getvalue() - assert 'coro => %r' % 'result' in output diff --git a/tests/test_integrations_py37.py b/tests/test_integrations_py37.py deleted file mode 100644 index 2e77762..0000000 --- a/tests/test_integrations_py37.py +++ /dev/null @@ -1,23 +0,0 @@ -import asyncio - -from aspectlib import debug - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - - -def test_decorate_asyncio_coroutine(): - buf = StringIO() - - @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) - async def coro(): - await asyncio.sleep(0.01) - return "result" - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - output = buf.getvalue() - print(output) - assert 'coro => %r' % 'result' in output diff --git a/tox.ini b/tox.ini index 1bd1d1c..0b23773 100644 --- a/tox.ini +++ b/tox.ini @@ -1,68 +1,54 @@ +[testenv:bootstrap] +deps = + jinja2 + tox +skip_install = true +commands = + python ci/bootstrap.py --no-env +passenv = + * +; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist + [tox] envlist = clean, check, docs, - py27-cover, - py27-cover-debug, - py27-nocov, - py27-nocov-debug, - py36-cover, - py36-cover-debug, - py36-nocov, - py36-nocov-debug, - py37-cover, - py37-cover-debug, - py37-nocov, - py37-nocov-debug, - py38-cover, - py38-cover-debug, - py38-nocov, - py38-nocov-debug, - py39-cover, - py39-cover-debug, - py39-nocov, - py39-nocov-debug, - pypy-cover, - pypy-cover-debug, - pypy-nocov, - pypy-nocov-debug, - pypy3-cover, - pypy3-cover-debug, - pypy3-nocov, - pypy3-nocov-debug, + {py37,py38,py39,py310,pypy37,pypy38,pypy39}-{cover,nocov}-{release,debug}, report +ignore_basepython_conflict = true [testenv] basepython = + pypy37: {env:TOXPYTHON:pypy3.7} + pypy38: {env:TOXPYTHON:pypy3.8} + pypy39: {env:TOXPYTHON:pypy3.9} + py37: {env:TOXPYTHON:python3.7} + py38: {env:TOXPYTHON:python3.8} + py39: {env:TOXPYTHON:python3.9} + py310: {env:TOXPYTHON:python3.10} {bootstrap,clean,check,report,docs,codecov,coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes + debug: ASPECTLIB_DEBUG=yes passenv = * +usedevelop = + cover: true + nocov: false deps = hunter mock nose process-tests pytest - pytest-catchlog - pytest-clarity - pytest-cov - pytest-travis-fold six tornado + cover: pytest-cov commands = - {posargs:pytest --cov --cov-report=term-missing -vv --ignore=src} - -[testenv:bootstrap] -deps = - jinja2 - matrix -skip_install = true -commands = - python ci/bootstrap.py --no-env + nocov: {posargs:pytest -vv --ignore=src} + cover: {posargs:pytest --cov --cov-report=term-missing -vv} [testenv:check] deps = @@ -79,7 +65,6 @@ commands = flake8 isort --verbose --check-only --diff --filter-files . - [testenv:docs] usedevelop = true deps = @@ -96,8 +81,6 @@ skip_install = true commands = coveralls [] - - [testenv:codecov] deps = codecov @@ -106,7 +89,8 @@ commands = codecov [] [testenv:report] -deps = coverage +deps = + coverage skip_install = true commands = coverage report @@ -115,244 +99,5 @@ commands = [testenv:clean] commands = coverage erase skip_install = true -deps = coverage - -[testenv:py27-cover] -basepython = {env:TOXPYTHON:python2.7} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - trollius - -[testenv:py27-cover-debug] -basepython = {env:TOXPYTHON:python2.7} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - trollius - -[testenv:py27-nocov] -basepython = {env:TOXPYTHON:python2.7} -deps = - {[testenv]deps} - trollius - -[testenv:py27-nocov-debug] -basepython = {env:TOXPYTHON:python2.7} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -deps = - {[testenv]deps} - trollius - -[testenv:py36-cover] -basepython = {env:TOXPYTHON:python3.6} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py36-cover-debug] -basepython = {env:TOXPYTHON:python3.6} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py36-nocov] -basepython = {env:TOXPYTHON:python3.6} - -[testenv:py36-nocov-debug] -basepython = {env:TOXPYTHON:python3.6} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes - -[testenv:py37-cover] -basepython = {env:TOXPYTHON:python3.7} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py37-cover-debug] -basepython = {env:TOXPYTHON:python3.7} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} deps = - {[testenv]deps} - pytest-cov - -[testenv:py37-nocov] -basepython = {env:TOXPYTHON:python3.7} - -[testenv:py37-nocov-debug] -basepython = {env:TOXPYTHON:python3.7} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes - -[testenv:py38-cover] -basepython = {env:TOXPYTHON:python3.8} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py38-cover-debug] -basepython = {env:TOXPYTHON:python3.8} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py38-nocov] -basepython = {env:TOXPYTHON:python3.8} - -[testenv:py38-nocov-debug] -basepython = {env:TOXPYTHON:python3.8} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes - -[testenv:py39-cover] -basepython = {env:TOXPYTHON:python3.9} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py39-cover-debug] -basepython = {env:TOXPYTHON:python3.9} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:py39-nocov] -basepython = {env:TOXPYTHON:python3.9} - -[testenv:py39-nocov-debug] -basepython = {env:TOXPYTHON:python3.9} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes - -[testenv:pypy-cover] -basepython = {env:TOXPYTHON:pypy} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - trollius - -[testenv:pypy-cover-debug] -basepython = {env:TOXPYTHON:pypy} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - trollius - -[testenv:pypy-nocov] -basepython = {env:TOXPYTHON:pypy} -deps = - {[testenv]deps} - trollius - -[testenv:pypy-nocov-debug] -basepython = {env:TOXPYTHON:pypy} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -deps = - {[testenv]deps} - trollius - -[testenv:pypy3-cover] -basepython = {env:TOXPYTHON:pypy3} -setenv = - {[testenv]setenv} -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:pypy3-cover-debug] -basepython = {env:TOXPYTHON:pypy3} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes -usedevelop = true -commands = - {posargs:pytest --cov --cov-report=term-missing -vv} -deps = - {[testenv]deps} - pytest-cov - -[testenv:pypy3-nocov] -basepython = {env:TOXPYTHON:pypy3} - -[testenv:pypy3-nocov-debug] -basepython = {env:TOXPYTHON:pypy3} -setenv = - {[testenv]setenv} - ASPECTLIB_DEBUG=yes + coverage From 3153065a20d83a4c3591cc119bc886c8544faa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 19:03:02 +0300 Subject: [PATCH 09/20] Remove some dead code and remove those runtime imports for what was py3 support. --- ci/bootstrap.py | 2 - setup.py | 2 +- src/aspectlib/__init__.py | 80 +++++++++++++++++++++++++-- src/aspectlib/py35support.py | 104 ----------------------------------- src/aspectlib/py3support.py | 56 ------------------- 5 files changed, 77 insertions(+), 167 deletions(-) delete mode 100644 src/aspectlib/py35support.py delete mode 100644 src/aspectlib/py3support.py diff --git a/ci/bootstrap.py b/ci/bootstrap.py index cc5ce2f..bf9bbd6 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - - import os import subprocess import sys diff --git a/setup.py b/setup.py index 053f6b6..46606a7 100755 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ def read(*names, **kwargs): 'mocking', 'logger', ], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', + python_requires='>=3.7', install_requires=['fields'], extras_require={}, entry_points={ diff --git a/src/aspectlib/__init__.py b/src/aspectlib/__init__.py index a62ef18..396ec91 100644 --- a/src/aspectlib/__init__.py +++ b/src/aspectlib/__init__.py @@ -173,13 +173,85 @@ def __init__(self, advising_function, bind=False): def __call__(self, cutpoint_function): if isasyncfunction is not None and isasyncfunction(cutpoint_function): - from aspectlib.py35support import decorate_advising_asyncgenerator_py35 + assert isasyncgenfunction(cutpoint_function) or iscoroutinefunction(cutpoint_function) - return decorate_advising_asyncgenerator_py35(self.advising_function, cutpoint_function, self.bind) + async def advising_asyncgenerator_wrapper_py35(*args, **kwargs): + if self.bind: + advisor = self.advising_function(cutpoint_function, *args, **kwargs) + else: + advisor = self.advising_function(*args, **kwargs) + if not isgenerator(advisor): + raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) + try: + advice = next(advisor) + while True: + logdebug('Got advice %r from %s', advice, self.advising_function) + if advice is Proceed or advice is None or isinstance(advice, Proceed): + if isinstance(advice, Proceed): + args = advice.args + kwargs = advice.kwargs + gen = cutpoint_function(*args, **kwargs) + try: + result = await gen + except BaseException: + advice = advisor.throw(*sys.exc_info()) + else: + try: + advice = advisor.send(result) + except StopIteration: + return result + finally: + gen.close() + elif advice is Return: + return + elif isinstance(advice, Return): + return advice.value + else: + raise UnacceptableAdvice("Unknown advice %s" % advice) + finally: + advisor.close() + + return mimic(advising_asyncgenerator_wrapper_py35, cutpoint_function) elif isgeneratorfunction(cutpoint_function): - from aspectlib.py35support import decorate_advising_generator_py35 + assert isgeneratorfunction(cutpoint_function) + + def advising_generator_wrapper_py35(*args, **kwargs): + if self.bind: + advisor = self.advising_function(cutpoint_function, *args, **kwargs) + else: + advisor = self.advising_function(*args, **kwargs) + if not isgenerator(advisor): + raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) + try: + advice = next(advisor) + while True: + logdebug('Got advice %r from %s', advice, self.advising_function) + if advice is Proceed or advice is None or isinstance(advice, Proceed): + if isinstance(advice, Proceed): + args = advice.args + kwargs = advice.kwargs + gen = cutpoint_function(*args, **kwargs) + try: + result = yield from gen + except BaseException: + advice = advisor.throw(*sys.exc_info()) + else: + try: + advice = advisor.send(result) + except StopIteration: + return result + finally: + gen.close() + elif advice is Return: + return + elif isinstance(advice, Return): + return advice.value + else: + raise UnacceptableAdvice("Unknown advice %s" % advice) + finally: + advisor.close() - return decorate_advising_generator_py35(self.advising_function, cutpoint_function, self.bind) + return mimic(advising_generator_wrapper_py35, cutpoint_function) else: def advising_function_wrapper(*args, **kwargs): diff --git a/src/aspectlib/py35support.py b/src/aspectlib/py35support.py deleted file mode 100644 index 22f8b85..0000000 --- a/src/aspectlib/py35support.py +++ /dev/null @@ -1,104 +0,0 @@ -import sys -from inspect import iscoroutinefunction -from inspect import isgenerator -from inspect import isgeneratorfunction -from logging import getLogger - -from aspectlib import ExpectedGenerator -from aspectlib import Proceed -from aspectlib import Return -from aspectlib import UnacceptableAdvice -from aspectlib import mimic -from aspectlib.utils import logf - -try: - from inspect import isasyncgenfunction -except ImportError: - isasyncgenfunction = iscoroutinefunction - -logger = getLogger(__name__) -logdebug = logf(logger.debug) - - -def decorate_advising_asyncgenerator_py35(advising_function, cutpoint_function, bind): - assert isasyncgenfunction(cutpoint_function) or iscoroutinefunction(cutpoint_function) - - async def advising_asyncgenerator_wrapper_py35(*args, **kwargs): - if bind: - advisor = advising_function(cutpoint_function, *args, **kwargs) - else: - advisor = advising_function(*args, **kwargs) - if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % advising_function) - try: - advice = next(advisor) - while True: - logdebug('Got advice %r from %s', advice, advising_function) - if advice is Proceed or advice is None or isinstance(advice, Proceed): - if isinstance(advice, Proceed): - args = advice.args - kwargs = advice.kwargs - gen = cutpoint_function(*args, **kwargs) - try: - result = await gen - except BaseException: - advice = advisor.throw(*sys.exc_info()) - else: - try: - advice = advisor.send(result) - except StopIteration: - return result - finally: - gen.close() - elif advice is Return: - return - elif isinstance(advice, Return): - return advice.value - else: - raise UnacceptableAdvice("Unknown advice %s" % advice) - finally: - advisor.close() - - return mimic(advising_asyncgenerator_wrapper_py35, cutpoint_function) - - -def decorate_advising_generator_py35(advising_function, cutpoint_function, bind): - assert isgeneratorfunction(cutpoint_function) - - def advising_generator_wrapper_py35(*args, **kwargs): - if bind: - advisor = advising_function(cutpoint_function, *args, **kwargs) - else: - advisor = advising_function(*args, **kwargs) - if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % advising_function) - try: - advice = next(advisor) - while True: - logdebug('Got advice %r from %s', advice, advising_function) - if advice is Proceed or advice is None or isinstance(advice, Proceed): - if isinstance(advice, Proceed): - args = advice.args - kwargs = advice.kwargs - gen = cutpoint_function(*args, **kwargs) - try: - result = yield from gen - except BaseException: - advice = advisor.throw(*sys.exc_info()) - else: - try: - advice = advisor.send(result) - except StopIteration: - return result - finally: - gen.close() - elif advice is Return: - return - elif isinstance(advice, Return): - return advice.value - else: - raise UnacceptableAdvice("Unknown advice %s" % advice) - finally: - advisor.close() - - return mimic(advising_generator_wrapper_py35, cutpoint_function) diff --git a/src/aspectlib/py3support.py b/src/aspectlib/py3support.py deleted file mode 100644 index beaee11..0000000 --- a/src/aspectlib/py3support.py +++ /dev/null @@ -1,56 +0,0 @@ -import sys -from inspect import isgenerator -from inspect import isgeneratorfunction -from logging import getLogger - -from aspectlib import ExpectedGenerator -from aspectlib import Proceed -from aspectlib import Return -from aspectlib import UnacceptableAdvice -from aspectlib import mimic -from aspectlib.utils import logf - -logger = getLogger(__name__) -logdebug = logf(logger.debug) - - -def decorate_advising_generator_py3(advising_function, cutpoint_function, bind): - assert isgeneratorfunction(cutpoint_function) - - def advising_generator_wrapper_py3(*args, **kwargs): - if bind: - advisor = advising_function(cutpoint_function, *args, **kwargs) - else: - advisor = advising_function(*args, **kwargs) - if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % advising_function) - try: - advice = next(advisor) - while True: - logdebug('Got advice %r from %s', advice, advising_function) - if advice is Proceed or advice is None or isinstance(advice, Proceed): - if isinstance(advice, Proceed): - args = advice.args - kwargs = advice.kwargs - gen = cutpoint_function(*args, **kwargs) - try: - result = yield from gen - except BaseException: - advice = advisor.throw(*sys.exc_info()) - else: - try: - advice = advisor.send(result) - except StopIteration: - return result - finally: - gen.close() - elif advice is Return: - return - elif isinstance(advice, Return): - raise StopIteration(advice.value) - else: - raise UnacceptableAdvice("Unknown advice %s" % advice) - finally: - advisor.close() - - return mimic(advising_generator_wrapper_py3, cutpoint_function) From 5e6bd6a61c51353295a94c87b94ac8a0f8413b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 19:06:48 +0300 Subject: [PATCH 10/20] Update changelog. --- CHANGELOG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c4de5e2..dc0fe74 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,13 @@ Changelog ========= +2.0.0 (2022-10-20) +------------------ + +* Drop support for legacy Pythons (2.7, 3.6 or older). +* Remove Travis/Appveyor CI and switch to GitHub Actions. +* Added support for Tornado 6 (in the test suite). + 1.5.2 (2020-11-15) ------------------ From 5ff96b4b152373a4ba47fe7635d1fb7d6415404b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 19:12:54 +0300 Subject: [PATCH 11/20] Move file in the right place. --- conftest.py => tests/conftest.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename conftest.py => tests/conftest.py (100%) diff --git a/conftest.py b/tests/conftest.py similarity index 100% rename from conftest.py rename to tests/conftest.py From 01c8d9c088988a3d920d47538c858816da339880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 19:13:01 +0300 Subject: [PATCH 12/20] Fix formatting. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc0fe74..cce57be 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -191,7 +191,7 @@ Changelog 0.3.1 (2014-03-05) ------------------ -* ??? +* `???` 0.3.0 (2014-03-05) ------------------ From d5122f86c20e95b27bee3a1a38e00d628043cc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Thu, 20 Oct 2022 19:22:58 +0300 Subject: [PATCH 13/20] =?UTF-8?q?Bump=20version:=201.5.2=20=E2=86=92=202.0?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- setup.py | 2 +- src/aspectlib/__init__.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8f991ed..ed83588 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.5.2 +current_version = 2.0.0 commit = True tag = True diff --git a/README.rst b/README.rst index d89249c..a1bbfa2 100644 --- a/README.rst +++ b/README.rst @@ -51,9 +51,9 @@ Overview :alt: Supported implementations :target: https://pypi.org/project/aspectlib -.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-aspectlib/v1.5.2.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-aspectlib/v2.0.0.svg :alt: Commits since latest release - :target: https://github.com/ionelmc/python-aspectlib/compare/v1.5.2...main + :target: https://github.com/ionelmc/python-aspectlib/compare/v2.0.0...main diff --git a/docs/conf.py b/docs/conf.py index cd0320c..4055513 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = '2014-2022' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) -version = release = '1.5.2' +version = release = '2.0.0' pygments_style = 'trac' templates_path = ['.'] diff --git a/setup.py b/setup.py index 46606a7..0fd4847 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def read(*names, **kwargs): setup( name='aspectlib', - version='1.5.2', + version='2.0.0', license='BSD-2-Clause', description='``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing', long_description='{}\n{}'.format( diff --git a/src/aspectlib/__init__.py b/src/aspectlib/__init__.py index 396ec91..f57cbbf 100644 --- a/src/aspectlib/__init__.py +++ b/src/aspectlib/__init__.py @@ -51,7 +51,7 @@ def isasyncfunction(obj): isasyncfunction = None __all__ = 'weave', 'Aspect', 'Proceed', 'Return', 'ALL_METHODS', 'NORMAL_METHODS', 'ABSOLUTELY_ALL_METHODS' -__version__ = '1.5.2' +__version__ = '2.0.0' logger = getLogger(__name__) logdebug = logf(logger.debug) From 14ea6dc1c7cc8da35c0baef5e03ae9d4324aacec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 17:53:03 +0300 Subject: [PATCH 14/20] Up skel, add py 3.11 and 3.12 and drop 3.7. --- .bumpversion.cfg | 4 + .cookiecutterrc | 26 +--- .github/workflows/github-actions.yml | 145 ++++++++++++------ .gitignore | 58 +++---- .pre-commit-config.yaml | 29 ++-- .readthedocs.yml | 4 + CONTRIBUTING.rst | 4 +- LICENSE | 2 +- README.rst | 18 +-- ci/bootstrap.py | 74 ++++----- ci/requirements.txt | 1 + .../.github/workflows/github-actions.yml | 31 ++-- docs/conf.py | 22 +-- docs/index.rst | 8 +- docs/readme.rst | 1 + docs/reference/aspectlib.rst | 4 +- docs/requirements.txt | 2 +- pyproject.toml | 52 ++++++- setup.cfg | 11 -- setup.py | 28 ++-- tox.ini | 37 ++--- 21 files changed, 312 insertions(+), 249 deletions(-) create mode 100644 docs/readme.rst delete mode 100644 setup.cfg diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ed83588..dc0a507 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -22,3 +22,7 @@ replace = version = release = '{new_version}' [bumpversion:file:src/aspectlib/__init__.py] search = __version__ = '{current_version}' replace = __version__ = '{new_version}' + +[bumpversion:file:.cookiecutterrc] +search = version: {current_version} +replace = version: {new_version} diff --git a/.cookiecutterrc b/.cookiecutterrc index 2351690..7eaddf9 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,14 +1,8 @@ # Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) default_context: - allow_tests_inside_package: 'no' - appveyor: 'no' - c_extension_function: '-' - c_extension_module: '-' c_extension_optional: 'no' c_extension_support: 'no' - c_extension_test_pypi: 'no' - c_extension_test_pypi_username: ionelmc codacy: 'no' codacy_projectid: '-' codeclimate: 'no' @@ -18,40 +12,36 @@ default_context: coveralls: 'yes' distribution_name: aspectlib email: contact@ionelmc.ro + formatter_quote_style: single full_name: Ionel Cristian Mărieș + function_name: compute github_actions: 'yes' github_actions_osx: 'no' github_actions_windows: 'no' - legacy_python: 'no' license: BSD 2-Clause License - linter: flake8 + module_name: core package_name: aspectlib pre_commit: 'yes' - pre_commit_formatter: black project_name: Aspectlib project_short_description: '``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing' pypi_badge: 'yes' pypi_disable_upload: 'no' - release_date: '2020-11-15' + release_date: '2022-10-20' repo_hosting: github.com repo_hosting_domain: github.com repo_main_branch: main repo_name: python-aspectlib repo_username: ionelmc - requiresio: 'yes' scrutinizer: 'no' - setup_py_uses_pytest_runner: 'no' setup_py_uses_setuptools_scm: 'no' sphinx_docs: 'yes' sphinx_docs_hosting: https://python-aspectlib.readthedocs.io/ sphinx_doctest: 'yes' - sphinx_theme: sphinx-py3doc-enhanced-theme - test_matrix_configurator: 'no' + sphinx_theme: furo test_matrix_separate_coverage: 'yes' - travis: 'no' - travis_osx: 'no' - version: 1.5.2 + tests_inside_package: 'no' + version: 2.0.0 version_manager: bump2version website: http://blog.ionelmc.ro year_from: '2014' - year_to: '2022' + year_to: '2024' diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 4adf1ad..97a257e 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,5 +1,5 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: name: ${{ matrix.name }} @@ -10,50 +10,26 @@ jobs: matrix: include: - name: 'check' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' - - name: 'py37-cover-release (ubuntu)' - python: '3.7' - toxpython: 'python3.7' - python_arch: 'x64' - tox_env: 'py37-cover-release,codecov' - os: 'ubuntu-latest' - - name: 'py37-cover-debug (ubuntu)' - python: '3.7' - toxpython: 'python3.7' - python_arch: 'x64' - tox_env: 'py37-cover-debug,codecov' - os: 'ubuntu-latest' - - name: 'py37-nocov-release (ubuntu)' - python: '3.7' - toxpython: 'python3.7' - python_arch: 'x64' - tox_env: 'py37-nocov-release' - os: 'ubuntu-latest' - - name: 'py37-nocov-debug (ubuntu)' - python: '3.7' - toxpython: 'python3.7' - python_arch: 'x64' - tox_env: 'py37-nocov-debug' - os: 'ubuntu-latest' - name: 'py38-cover-release (ubuntu)' python: '3.8' toxpython: 'python3.8' python_arch: 'x64' - tox_env: 'py38-cover-release,codecov' + tox_env: 'py38-cover-release' os: 'ubuntu-latest' - name: 'py38-cover-debug (ubuntu)' python: '3.8' toxpython: 'python3.8' python_arch: 'x64' - tox_env: 'py38-cover-debug,codecov' + tox_env: 'py38-cover-debug' os: 'ubuntu-latest' - name: 'py38-nocov-release (ubuntu)' python: '3.8' @@ -71,13 +47,13 @@ jobs: python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-cover-release,codecov' + tox_env: 'py39-cover-release' os: 'ubuntu-latest' - name: 'py39-cover-debug (ubuntu)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-cover-debug,codecov' + tox_env: 'py39-cover-debug' os: 'ubuntu-latest' - name: 'py39-nocov-release (ubuntu)' python: '3.9' @@ -95,13 +71,13 @@ jobs: python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-cover-release,codecov' + tox_env: 'py310-cover-release' os: 'ubuntu-latest' - name: 'py310-cover-debug (ubuntu)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-cover-debug,codecov' + tox_env: 'py310-cover-debug' os: 'ubuntu-latest' - name: 'py310-nocov-release (ubuntu)' python: '3.10' @@ -115,17 +91,65 @@ jobs: python_arch: 'x64' tox_env: 'py310-nocov-debug' os: 'ubuntu-latest' + - name: 'py311-cover-release (ubuntu)' + python: '3.11' + toxpython: 'python3.11' + python_arch: 'x64' + tox_env: 'py311-cover-release' + os: 'ubuntu-latest' + - name: 'py311-cover-debug (ubuntu)' + python: '3.11' + toxpython: 'python3.11' + python_arch: 'x64' + tox_env: 'py311-cover-debug' + os: 'ubuntu-latest' + - name: 'py311-nocov-release (ubuntu)' + python: '3.11' + toxpython: 'python3.11' + python_arch: 'x64' + tox_env: 'py311-nocov-release' + os: 'ubuntu-latest' + - name: 'py311-nocov-debug (ubuntu)' + python: '3.11' + toxpython: 'python3.11' + python_arch: 'x64' + tox_env: 'py311-nocov-debug' + os: 'ubuntu-latest' + - name: 'py312-cover-release (ubuntu)' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-cover-release' + os: 'ubuntu-latest' + - name: 'py312-cover-debug (ubuntu)' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-cover-debug' + os: 'ubuntu-latest' + - name: 'py312-nocov-release (ubuntu)' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-nocov-release' + os: 'ubuntu-latest' + - name: 'py312-nocov-debug (ubuntu)' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-nocov-debug' + os: 'ubuntu-latest' - name: 'pypy37-cover-release (ubuntu)' python: 'pypy-3.7' toxpython: 'pypy3.7' python_arch: 'x64' - tox_env: 'pypy37-cover-release,codecov' + tox_env: 'pypy37-cover-release' os: 'ubuntu-latest' - name: 'pypy37-cover-debug (ubuntu)' python: 'pypy-3.7' toxpython: 'pypy3.7' python_arch: 'x64' - tox_env: 'pypy37-cover-debug,codecov' + tox_env: 'pypy37-cover-debug' os: 'ubuntu-latest' - name: 'pypy37-nocov-release (ubuntu)' python: 'pypy-3.7' @@ -143,13 +167,13 @@ jobs: python: 'pypy-3.8' toxpython: 'pypy3.8' python_arch: 'x64' - tox_env: 'pypy38-cover-release,codecov' + tox_env: 'pypy38-cover-release' os: 'ubuntu-latest' - name: 'pypy38-cover-debug (ubuntu)' python: 'pypy-3.8' toxpython: 'pypy3.8' python_arch: 'x64' - tox_env: 'pypy38-cover-debug,codecov' + tox_env: 'pypy38-cover-debug' os: 'ubuntu-latest' - name: 'pypy38-nocov-release (ubuntu)' python: 'pypy-3.8' @@ -167,13 +191,13 @@ jobs: python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-cover-release,codecov' + tox_env: 'pypy39-cover-release' os: 'ubuntu-latest' - name: 'pypy39-cover-debug (ubuntu)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-cover-debug,codecov' + tox_env: 'pypy39-cover-debug' os: 'ubuntu-latest' - name: 'pypy39-nocov-release (ubuntu)' python: 'pypy-3.9' @@ -187,11 +211,35 @@ jobs: python_arch: 'x64' tox_env: 'pypy39-nocov-debug' os: 'ubuntu-latest' + - name: 'pypy310-cover-release (ubuntu)' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-cover-release' + os: 'ubuntu-latest' + - name: 'pypy310-cover-debug (ubuntu)' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-cover-debug' + os: 'ubuntu-latest' + - name: 'pypy310-nocov-release (ubuntu)' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-nocov-release' + os: 'ubuntu-latest' + - name: 'pypy310-nocov-debug (ubuntu)' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-nocov-debug' + os: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.python_arch }} @@ -207,3 +255,14 @@ jobs: TOXPYTHON: '${{ matrix.toxpython }}' run: > tox -e ${{ matrix.tox_env }} -v + finish: + needs: test + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - uses: coverallsapp/github-action@v2 + with: + parallel-finished: true + - uses: codecov/codecov-action@v3 + with: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 83a43fd..77973dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,53 @@ *.py[cod] __pycache__ +# Temp files +.*.sw[po] +*~ +*.bak +.DS_Store + # C extensions *.so -# Packages +# Build and package files *.egg *.egg-info -dist -build -eggs +.bootstrap +.build +.cache .eggs -parts +.env +.installed.cfg +.ve bin -var -sdist -wheelhouse +build develop-eggs -.installed.cfg +dist +eggs lib lib64 -venv*/ -pyvenv*/ +parts pip-wheel-metadata/ +pyvenv*/ +sdist +var +venv*/ +wheelhouse # Installer logs pip-log.txt # Unit test / coverage reports +.benchmarks .coverage -.tox .coverage.* +.pytest .pytest_cache/ -nosetests.xml +.tox coverage.xml htmlcov +nosetests.xml # Translations *.mo @@ -43,12 +56,12 @@ htmlcov .mr.developer.cfg # IDE project files +*.iml +*.komodoproject +.idea .project .pydevproject -.idea .vscode -*.iml -*.komodoproject # Complexity output/*.html @@ -57,18 +70,5 @@ output/*/index.html # Sphinx docs/_build -.DS_Store -*~ -.*.sw[po] -.build -.ve -.env -.cache -.pytest -.benchmarks -.bootstrap -.appveyor.token -*.bak - # Mypy Cache .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8be60d5..9e9b302 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,19 @@ -# To install the git pre-commit hook run: -# pre-commit install -# To update the pre-commit hooks run: -# pre-commit install-hooks +# To install the git pre-commit hooks run: +# pre-commit install --install-hooks +# To update the versions: +# pre-commit autoupdate exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' +# Note the order is intentional to avoid multiple passes of the hooks repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.7 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - - repo: https://github.com/timothycrosley/isort - rev: 5.10.1 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 diff --git a/.readthedocs.yml b/.readthedocs.yml index 59ff5c0..009a913 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,6 +3,10 @@ version: 2 sphinx: configuration: docs/conf.py formats: all +build: + os: ubuntu-22.04 + tools: + python: "3" python: install: - requirements: docs/requirements.txt diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 47c71ee..eaff88e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -49,7 +49,7 @@ To set up `python-aspectlib` for local development: Now you can make your changes locally. -4. When you're done making changes run all the checks and docs builder with `tox `_ one command:: +4. When you're done making changes run all the checks and docs builder with one command:: tox @@ -73,8 +73,6 @@ For merging, you should: 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. - - Tips ---- diff --git a/LICENSE b/LICENSE index 82c46d1..61b141c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2014-2022, Ionel Cristian Mărieș. All rights reserved. +Copyright (c) 2014-2024, Ionel Cristian Mărieș. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.rst b/README.rst index a1bbfa2..415ea7b 100644 --- a/README.rst +++ b/README.rst @@ -10,30 +10,24 @@ Overview * - docs - |docs| * - tests - - | |github-actions| |requires| - | |coveralls| |codecov| + - |github-actions| |coveralls| |codecov| * - package - - | |version| |wheel| |supported-versions| |supported-implementations| - | |commits-since| + - |version| |wheel| |supported-versions| |supported-implementations| |commits-since| .. |docs| image:: https://readthedocs.org/projects/python-aspectlib/badge/?style=flat - :target: https://python-aspectlib.readthedocs.io/ + :target: https://readthedocs.org/projects/python-aspectlib/ :alt: Documentation Status .. |github-actions| image:: https://github.com/ionelmc/python-aspectlib/actions/workflows/github-actions.yml/badge.svg :alt: GitHub Actions Build Status :target: https://github.com/ionelmc/python-aspectlib/actions -.. |requires| image:: https://requires.io/github/ionelmc/python-aspectlib/requirements.svg?branch=main - :alt: Requirements Status - :target: https://requires.io/github/ionelmc/python-aspectlib/requirements/?branch=main - -.. |coveralls| image:: https://coveralls.io/repos/ionelmc/python-aspectlib/badge.svg?branch=main&service=github +.. |coveralls| image:: https://coveralls.io/repos/github/ionelmc/python-aspectlib/badge.svg?branch=main :alt: Coverage Status - :target: https://coveralls.io/r/ionelmc/python-aspectlib + :target: https://coveralls.io/github/ionelmc/python-aspectlib?branch=main .. |codecov| image:: https://codecov.io/gh/ionelmc/python-aspectlib/branch/main/graphs/badge.svg?branch=main :alt: Coverage Status - :target: https://codecov.io/github/ionelmc/python-aspectlib + :target: https://app.codecov.io/github/ionelmc/python-aspectlib .. |version| image:: https://img.shields.io/pypi/v/aspectlib.svg :alt: PyPI Package latest release diff --git a/ci/bootstrap.py b/ci/bootstrap.py index bf9bbd6..f3c9a7e 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -1,63 +1,57 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os +import pathlib import subprocess import sys -from os.path import abspath -from os.path import dirname -from os.path import exists -from os.path import join -from os.path import relpath -base_path = dirname(dirname(abspath(__file__))) -templates_path = join(base_path, "ci", "templates") +base_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent +templates_path = base_path / 'ci' / 'templates' def check_call(args): - print("+", *args) + print('+', *args) subprocess.check_call(args) def exec_in_env(): - env_path = join(base_path, ".tox", "bootstrap") - if sys.platform == "win32": - bin_path = join(env_path, "Scripts") + env_path = base_path / '.tox' / 'bootstrap' + if sys.platform == 'win32': + bin_path = env_path / 'Scripts' else: - bin_path = join(env_path, "bin") - if not exists(env_path): + bin_path = env_path / 'bin' + if not env_path.exists(): import subprocess - print("Making bootstrap env in: {0} ...".format(env_path)) + print(f'Making bootstrap env in: {env_path} ...') try: - check_call([sys.executable, "-m", "venv", env_path]) + check_call([sys.executable, '-m', 'venv', env_path]) except subprocess.CalledProcessError: try: - check_call([sys.executable, "-m", "virtualenv", env_path]) + check_call([sys.executable, '-m', 'virtualenv', env_path]) except subprocess.CalledProcessError: - check_call(["virtualenv", env_path]) - print("Installing `jinja2` into bootstrap environment...") - check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) - python_executable = join(bin_path, "python") - if not os.path.exists(python_executable): - python_executable += '.exe' + check_call(['virtualenv', env_path]) + print('Installing `jinja2` into bootstrap environment...') + check_call([bin_path / 'pip', 'install', 'jinja2', 'tox']) + python_executable = bin_path / 'python' + if not python_executable.exists(): + python_executable = python_executable.with_suffix('.exe') - print("Re-executing with: {0}".format(python_executable)) - print("+ exec", python_executable, __file__, "--no-env") - os.execv(python_executable, [python_executable, __file__, "--no-env"]) + print(f'Re-executing with: {python_executable}') + print('+ exec', python_executable, __file__, '--no-env') + os.execv(python_executable, [python_executable, __file__, '--no-env']) def main(): import jinja2 - print("Project path: {0}".format(base_path)) + print(f'Project path: {base_path}') jinja = jinja2.Environment( - loader=jinja2.FileSystemLoader(templates_path), + loader=jinja2.FileSystemLoader(str(templates_path)), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True, ) - tox_environments = [ line.strip() # 'tox' need not be installed globally, but must be importable @@ -68,22 +62,22 @@ def main(): for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], universal_newlines=True).splitlines() ] tox_environments = [line for line in tox_environments if line.startswith('py')] - - for root, _, files in os.walk(templates_path): - for name in files: - relative = relpath(root, templates_path) - with open(join(base_path, relative, name), "w") as fh: - fh.write(jinja.get_template(join(relative, name)).render(tox_environments=tox_environments)) - print("Wrote {}".format(name)) - print("DONE.") + for template in templates_path.rglob('*'): + if template.is_file(): + template_path = template.relative_to(templates_path).as_posix() + destination = base_path / template_path + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(jinja.get_template(template_path).render(tox_environments=tox_environments)) + print(f'Wrote {template_path}') + print('DONE.') -if __name__ == "__main__": +if __name__ == '__main__': args = sys.argv[1:] - if args == ["--no-env"]: + if args == ['--no-env']: main() elif not args: exec_in_env() else: - print("Unexpected arguments {0}".format(args), file=sys.stderr) + print(f'Unexpected arguments: {args}', file=sys.stderr) sys.exit(1) diff --git a/ci/requirements.txt b/ci/requirements.txt index a0ef106..a1708f4 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -3,3 +3,4 @@ pip>=19.1.1 setuptools>=18.0.1 six>=1.14.0 tox +twine diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml index b4a7ae2..5274834 100644 --- a/ci/templates/.github/workflows/github-actions.yml +++ b/ci/templates/.github/workflows/github-actions.yml @@ -1,5 +1,5 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: name: {{ '${{ matrix.name }}' }} @@ -10,21 +10,21 @@ jobs: matrix: include: - name: 'check' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' {% for env in tox_environments %} {% set prefix = env.split('-')[0] -%} {% if prefix.startswith('pypy') %} -{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5:] }}{% endset %} {% set cpython %}pp{{ prefix[4:5] }}{% endset %} -{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5:] }}{% endset %} {% else %} {% set python %}{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} {% set cpython %}cp{{ prefix[2:] }}{% endset %} @@ -37,15 +37,15 @@ jobs: python: '{{ python }}' toxpython: '{{ toxpython }}' python_arch: '{{ python_arch }}' - tox_env: '{{ env }}{% if 'cover' in env %},codecov{% endif %}' + tox_env: '{{ env }}' os: '{{ os }}-latest' {% endfor %} {% endfor %} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: {{ '${{ matrix.python }}' }} architecture: {{ '${{ matrix.python_arch }}' }} @@ -61,3 +61,14 @@ jobs: TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' run: > tox -e {{ '${{ matrix.tox_env }}' }} -v + finish: + needs: test + if: {{ '${{ always() }}' }} + runs-on: ubuntu-latest + steps: + - uses: coverallsapp/github-action@v2 + with: + parallel-finished: true + - uses: codecov/codecov-action@v3 + with: + CODECOV_TOKEN: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} diff --git a/docs/conf.py b/docs/conf.py index 4055513..c243d2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- - - -import sphinx_py3doc_enhanced_theme - extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -17,19 +12,19 @@ source_suffix = '.rst' master_doc = 'index' project = 'Aspectlib' -year = '2014-2022' +year = '2014-2024' author = 'Ionel Cristian Mărieș' -copyright = '{0}, {1}'.format(year, author) +copyright = f'{year}, {author}' version = release = '2.0.0' pygments_style = 'trac' templates_path = ['.'] extlinks = { - 'issue': ('https://github.com/ionelmc/python-aspectlib/issues/%s', '#'), - 'pr': ('https://github.com/ionelmc/python-aspectlib/pull/%s', 'PR #'), + 'issue': ('https://github.com/ionelmc/python-aspectlib/issues/%s', '#%s'), + 'pr': ('https://github.com/ionelmc/python-aspectlib/pull/%s', 'PR #%s'), } -html_theme = 'sphinx_py3doc_enhanced_theme' -html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] + +html_theme = 'furo' html_theme_options = { 'githuburl': 'https://github.com/ionelmc/python-aspectlib/', } @@ -37,10 +32,7 @@ html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = False -html_sidebars = { - '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], -} -html_short_title = '%s-%s' % (project, version) +html_short_title = f'{project}-{version}' napoleon_use_ivar = True napoleon_use_rtype = False diff --git a/docs/index.rst b/docs/index.rst index 72af676..9267902 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,6 @@ -Welcome to python-aspectlib's documentation! -============================================ - -``aspectlib`` is an aspect-oriented programming, monkey-patch and decorators library. It is useful when changing -behavior in existing code is desired. +======== +Contents +======== .. toctree:: :maxdepth: 2 diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/reference/aspectlib.rst b/docs/reference/aspectlib.rst index 3a4d002..e1db807 100644 --- a/docs/reference/aspectlib.rst +++ b/docs/reference/aspectlib.rst @@ -1,5 +1,5 @@ -Reference: ``aspectlib`` -======================== +aspectlib +========= Overview -------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 62bc14e..c03e307 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx>=1.3 -sphinx-py3doc-enhanced-theme +furo diff --git a/pyproject.toml b/pyproject.toml index fc9d53d..988950a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,54 @@ [build-system] requires = [ "setuptools>=30.3.0", - "wheel", ] -[tool.black] +[tool.ruff] +extend-exclude = ["static", "ci/templates"] line-length = 140 -target-version = ['py37'] -skip-string-normalization = true +src = ["src", "tests"] +target-version = "py38" + +[tool.ruff.lint.per-file-ignores] +"ci/*" = ["S"] + +[tool.ruff.lint] +ignore = [ + "RUF001", # ruff-specific rules ambiguous-unicode-character-string + "S101", # flake8-bandit assert + "S308", # flake8-bandit suspicious-mark-safe-usage + "S603", # flake8-bandit subprocess-without-shell-equals-true + "S607", # flake8-bandit start-process-with-partial-path + "E501", # pycodestyle line-too-long +] +select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "EXE", # flake8-executable + "F", # pyflakes + "I", # isort + "INT", # flake8-gettext + "PIE", # flake8-pie + "PLC", # pylint convention + "PLE", # pylint errors + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RSE", # flake8-raise + "RUF", # ruff-specific rules + "S", # flake8-bandit + "UP", # pyupgrade + "W", # pycodestyle warnings +] + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.isort] +forced-separate = ["conftest"] +force-single-line = true + +[tool.ruff.format] +quote-style = "single" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e617fb4..0000000 --- a/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[flake8] -max-line-length = 140 -exclude = .tox,.eggs,ci/templates,build,dist - -[tool:isort] -force_single_line = True -line_length = 120 -known_first_party = aspectlib -default_section = THIRDPARTY -forced_separate = test_aspectlib -skip = .tox,.eggs,ci/templates,build,dist diff --git a/setup.py b/setup.py index 0fd4847..2cb466b 100755 --- a/setup.py +++ b/setup.py @@ -1,20 +1,13 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- - -import io import re -from glob import glob -from os.path import basename -from os.path import dirname -from os.path import join -from os.path import splitext +from pathlib import Path from setuptools import find_packages from setuptools import setup def read(*names, **kwargs): - with io.open(join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8')) as fh: + with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get('encoding', 'utf8')) as fh: return fh.read() @@ -32,7 +25,7 @@ def read(*names, **kwargs): url='https://github.com/ionelmc/python-aspectlib', packages=find_packages('src'), package_dir={'': 'src'}, - py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + py_modules=[path.stem for path in Path('src').glob('*.py')], include_package_data=True, zip_safe=False, classifiers=[ @@ -46,10 +39,11 @@ def read(*names, **kwargs): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - '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 :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', # uncomment if you test on these interpreters: @@ -86,9 +80,15 @@ def read(*names, **kwargs): 'mocking', 'logger', ], - python_requires='>=3.7', - install_requires=['fields'], - extras_require={}, + python_requires='>=3.8', + install_requires=[ + # eg: "aspectlib==1.1.1", "six>=1.7", + ], + extras_require={ + # eg: + # "rst": ["docutils>=0.11"], + # ":python_version=='3.8'": ["backports.zoneinfo"], + }, entry_points={ 'pytest11': ['aspectlib = aspectlib.pytestsupport'], }, diff --git a/tox.ini b/tox.ini index 0b23773..1900e6f 100644 --- a/tox.ini +++ b/tox.ini @@ -7,26 +7,27 @@ commands = python ci/bootstrap.py --no-env passenv = * -; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist +; a generative tox configuration, see: https://tox.wiki/en/latest/user_guide.html#generative-environments [tox] envlist = clean, check, docs, - {py37,py38,py39,py310,pypy37,pypy38,pypy39}-{cover,nocov}-{release,debug}, + {py38,py39,py310,py311,py312,pypy37,pypy38,pypy39,pypy310}-{cover,nocov}-{release,debug}, report ignore_basepython_conflict = true [testenv] basepython = - pypy37: {env:TOXPYTHON:pypy3.7} pypy38: {env:TOXPYTHON:pypy3.8} pypy39: {env:TOXPYTHON:pypy3.9} - py37: {env:TOXPYTHON:python3.7} + pypy310: {env:TOXPYTHON:pypy3.10} py38: {env:TOXPYTHON:python3.8} py39: {env:TOXPYTHON:python3.9} py310: {env:TOXPYTHON:python3.10} + py311: {env:TOXPYTHON:python3.11} + py312: {env:TOXPYTHON:python3.12} {bootstrap,clean,check,report,docs,codecov,coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests @@ -48,22 +49,21 @@ deps = cover: pytest-cov commands = nocov: {posargs:pytest -vv --ignore=src} - cover: {posargs:pytest --cov --cov-report=term-missing -vv} + cover: {posargs:pytest --cov --cov-report=term-missing --cov-report=xml -vv} [testenv:check] deps = docutils check-manifest - flake8 + pre-commit readme-renderer pygments isort skip_install = true commands = python setup.py check --strict --metadata --restructuredtext - check-manifest {toxinidir} - flake8 - isort --verbose --check-only --diff --filter-files . + check-manifest . + pre-commit run --all-files --show-diff-on-failure [testenv:docs] usedevelop = true @@ -74,20 +74,6 @@ commands = sphinx-build {posargs:-E} -b html docs dist/docs sphinx-build -b linkcheck docs dist/docs -[testenv:coveralls] -deps = - coveralls -skip_install = true -commands = - coveralls [] - -[testenv:codecov] -deps = - codecov -skip_install = true -commands = - codecov [] - [testenv:report] deps = coverage @@ -97,7 +83,10 @@ commands = coverage html [testenv:clean] -commands = coverage erase +commands = + python setup.py clean + coverage erase skip_install = true deps = + setuptools coverage From e85fc6cf3febb9f891f19df5eab6a71eea13a7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:08:57 +0300 Subject: [PATCH 15/20] Apply ruff cleanups/formatting. --- .pre-commit-config.yaml | 2 +- src/aspectlib/__init__.py | 98 +++++++------- src/aspectlib/contrib.py | 13 +- src/aspectlib/debug.py | 68 +++++----- src/aspectlib/test.py | 80 ++++++----- src/aspectlib/utils.py | 35 ++--- tests/mymod.py | 4 +- tests/test_aspectlib.py | 111 ++++++++-------- tests/test_aspectlib_debug.py | 16 +-- tests/test_aspectlib_py3.py | 3 - tests/test_aspectlib_py37.py | 14 +- tests/test_aspectlib_test.py | 182 +++++++++++++------------- tests/test_contrib.py | 18 +-- tests/test_integrations.py | 44 +++---- tests/test_pkg1/test_pkg2/test_mod.py | 2 +- tests/test_pytestsupport.py | 2 +- 16 files changed, 340 insertions(+), 352 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e9b302..0e3a2cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: rev: v0.5.7 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix, --show-fixes] + args: [--fix, --exit-non-zero-on-fix, --show-fixes, --unsafe-fixes] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 diff --git a/src/aspectlib/__init__.py b/src/aspectlib/__init__.py index f57cbbf..bd70924 100644 --- a/src/aspectlib/__init__.py +++ b/src/aspectlib/__init__.py @@ -85,7 +85,7 @@ class UnsupportedType(TypeError): pass -class Proceed(object): +class Proceed: """ Instruction for calling the decorated function. Can be used multiple times. @@ -99,7 +99,7 @@ def __init__(self, *args, **kwargs): self.kwargs = kwargs -class Return(object): +class Return: """ Instruction for returning a *optional* value. @@ -112,7 +112,7 @@ def __init__(self, value): self.value = value -class Aspect(object): +class Aspect: """ Container for the advice yielding generator. Can be used as a decorator on other function to change behavior according to the advices yielded from the generator. @@ -161,13 +161,13 @@ def __new__(cls, advising_function=UNSPECIFIED, bind=False): if advising_function is UNSPECIFIED: return partial(cls, bind=bind) else: - self = super(Aspect, cls).__new__(cls) + self = super().__new__(cls) self.__init__(advising_function, bind) return self def __init__(self, advising_function, bind=False): if not isgeneratorfunction(advising_function): - raise ExpectedGeneratorFunction("advising_function %s must be a generator function." % advising_function) + raise ExpectedGeneratorFunction(f'advising_function {advising_function} must be a generator function.') self.advising_function = advising_function self.bind = bind @@ -181,7 +181,7 @@ async def advising_asyncgenerator_wrapper_py35(*args, **kwargs): else: advisor = self.advising_function(*args, **kwargs) if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) + raise ExpectedGenerator(f'advising_function {self.advising_function} did not return a generator.') try: advice = next(advisor) while True: @@ -207,7 +207,7 @@ async def advising_asyncgenerator_wrapper_py35(*args, **kwargs): elif isinstance(advice, Return): return advice.value else: - raise UnacceptableAdvice("Unknown advice %s" % advice) + raise UnacceptableAdvice(f'Unknown advice {advice}') finally: advisor.close() @@ -221,7 +221,7 @@ def advising_generator_wrapper_py35(*args, **kwargs): else: advisor = self.advising_function(*args, **kwargs) if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) + raise ExpectedGenerator(f'advising_function {self.advising_function} did not return a generator.') try: advice = next(advisor) while True: @@ -247,7 +247,7 @@ def advising_generator_wrapper_py35(*args, **kwargs): elif isinstance(advice, Return): return advice.value else: - raise UnacceptableAdvice("Unknown advice %s" % advice) + raise UnacceptableAdvice(f'Unknown advice {advice}') finally: advisor.close() @@ -260,7 +260,7 @@ def advising_function_wrapper(*args, **kwargs): else: advisor = self.advising_function(*args, **kwargs) if not isgenerator(advisor): - raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function) + raise ExpectedGenerator(f'advising_function {self.advising_function} did not return a generator.') try: advice = next(advisor) while True: @@ -283,23 +283,23 @@ def advising_function_wrapper(*args, **kwargs): elif isinstance(advice, Return): return advice.value else: - raise UnacceptableAdvice("Unknown advice %s" % advice) + raise UnacceptableAdvice(f'Unknown advice {advice}') finally: advisor.close() return mimic(advising_function_wrapper, cutpoint_function) -class Fabric(object): +class Fabric: pass -class Rollback(object): +class Rollback: """ When called, rollbacks all the patches and changes the :func:`weave` has done. """ - __slots__ = '_rollbacks' + __slots__ = ('_rollbacks',) def __init__(self, rollback=None): if rollback is None: @@ -323,7 +323,7 @@ def __exit__(self, *_): rollback = __call__ = __exit__ -class ObjectBag(object): +class ObjectBag: def __init__(self): self._objects = {} @@ -336,10 +336,10 @@ def has(self, obj): return False -BrokenBag = type('BrokenBag', (), dict(has=lambda self, obj: False))() +BrokenBag = type('BrokenBag', (), {'has': lambda self, obj: False})() -class EmptyRollback(object): +class EmptyRollback: def __enter__(self): return self @@ -356,20 +356,20 @@ def _checked_apply(aspects, function, module=None): logdebug(' applying aspects %s to function %s.', aspects, function) if callable(aspects): wrapper = aspects(function) - assert callable(wrapper), 'Aspect %s did not return a callable (it return %s).' % (aspects, wrapper) + assert callable(wrapper), f'Aspect {aspects} did not return a callable (it return {wrapper}).' else: wrapper = function for aspect in aspects: wrapper = aspect(wrapper) - assert callable(wrapper), 'Aspect %s did not return a callable (it return %s).' % (aspect, wrapper) + assert callable(wrapper), f'Aspect {aspect} did not return a callable (it return {wrapper}).' return mimic(wrapper, function, module=module) def _check_name(name): if not VALID_IDENTIFIER.match(name): raise SyntaxError( - "Could not match %r to %r. It should be a string of " - "letters, numbers and underscore that starts with a letter or underscore." % (name, VALID_IDENTIFIER.pattern) + f'Could not match {name!r} to {VALID_IDENTIFIER.pattern!r}. It should be a string of ' + 'letters, numbers and underscore that starts with a letter or underscore.' ) @@ -408,12 +408,12 @@ def weave(target, aspects, **options): """ if not callable(aspects): if not hasattr(aspects, '__iter__'): - raise ExpectedAdvice('%s must be an `Aspect` instance, a callable or an iterable of.' % aspects) + raise ExpectedAdvice(f'{aspects} must be an `Aspect` instance, a callable or an iterable of.') for obj in aspects: if not callable(obj): - raise ExpectedAdvice('%s must be an `Aspect` instance or a callable.' % obj) - assert target, "Can't weave falsy value %r." % target - logdebug("weave (target=%s, aspects=%s, **options=%s)", target, aspects, options) + raise ExpectedAdvice(f'{obj} must be an `Aspect` instance or a callable.') + assert target, f"Can't weave falsy value {target!r}." + logdebug('weave (target=%s, aspects=%s, **options=%s)', target, aspects, options) bag = options.setdefault('bag', ObjectBag()) @@ -436,7 +436,7 @@ def weave(target, aspects, **options): else: break else: - raise ImportError("Could not import %r. Last try was for %s" % (target, owner)) + raise ImportError(f'Could not import {target!r}. Last try was for {owner}') if '.' in name: path, name = name.rsplit('.', 1) @@ -444,14 +444,14 @@ def weave(target, aspects, **options): while path: owner = getattr(owner, path.popleft()) - logdebug("@ patching %s from %s ...", name, owner) + logdebug('@ patching %s from %s ...', name, owner) obj = getattr(owner, name) if isinstance(obj, (type, ClassType)): - logdebug(" .. as a class %r.", obj) + logdebug(' .. as a class %r.', obj) return weave_class(obj, aspects, owner=owner, name=name, **options) elif callable(obj): # or isinstance(obj, FunctionType) ?? - logdebug(" .. as a callable %r.", obj) + logdebug(' .. as a callable %r.', obj) if bag.has(obj): return Nothing return patch_module_function(owner, obj, aspects, force_name=name, **options) @@ -468,7 +468,7 @@ def weave(target, aspects, **options): return Nothing inst = target.__self__ name = target.__name__ - logdebug("@ patching %r (%s) as instance method.", target, name) + logdebug('@ patching %r (%s) as instance method.', target, name) func = target.__func__ setattr(inst, name, _checked_apply(aspects, func).__get__(inst, type(inst))) return Rollback(lambda: delattr(inst, name)) @@ -480,7 +480,7 @@ def weave(target, aspects, **options): while path: owner = getattr(owner, path.popleft()) name = target.__name__ - logdebug("@ patching %r (%s) as a property.", target, name) + logdebug('@ patching %r (%s) as a property.', target, name) func = owner.__dict__[name] return patch_module(owner, name, _checked_apply(aspects, func), func, **options) elif isclass(target): @@ -490,7 +490,7 @@ def weave(target, aspects, **options): elif type(target).__module__ not in ('builtins', '__builtin__') or InstanceType and isinstance(target, InstanceType): return weave_instance(target, aspects, **options) else: - raise UnsupportedType("Can't weave object %s of type %s" % (target, type(target))) + raise UnsupportedType(f"Can't weave object {target} of type {type(target)}") def _rewrap_method(func, klass, aspect): @@ -521,12 +521,12 @@ def weave_instance(instance, aspect, methods=NORMAL_METHODS, lazy=False, bag=Bro entanglement = Rollback() method_matches = make_method_matcher(methods) - logdebug("weave_instance (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", instance, aspect, methods, lazy, options) + logdebug('weave_instance (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)', instance, aspect, methods, lazy, options) def fixup(func): return func.__get__(instance, type(instance)) - fixed_aspect = aspect + [fixup] if isinstance(aspect, (list, tuple)) else [aspect, fixup] + fixed_aspect = [*aspect, fixup] if isinstance(aspect, (list, tuple)) else [aspect, fixup] for attr in dir(instance): if method_matches(attr): @@ -553,7 +553,7 @@ def weave_module(module, aspect, methods=NORMAL_METHODS, lazy=False, bag=BrokenB entanglement = Rollback() method_matches = make_method_matcher(methods) - logdebug("weave_module (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)", module, aspect, methods, lazy, options) + logdebug('weave_module (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)', module, aspect, methods, lazy, options) for attr in dir(module): if method_matches(attr): @@ -578,7 +578,7 @@ def weave_class( .. warning:: You should not use this directly. """ - assert isclass(klass), "Can't weave %r. Must be a class." % klass + assert isclass(klass), f"Can't weave {klass!r}. Must be a class." if bag.has(klass): return Nothing @@ -586,7 +586,7 @@ def weave_class( entanglement = Rollback() method_matches = make_method_matcher(methods) logdebug( - "weave_class (klass=%r, methods=%s, subclasses=%s, lazy=%s, owner=%s, name=%s, aliases=%s, bases=%s)", + 'weave_class (klass=%r, methods=%s, subclasses=%s, lazy=%s, owner=%s, name=%s, aliases=%s, bases=%s)', klass, methods, subclasses, @@ -600,7 +600,7 @@ def weave_class( if subclasses and hasattr(klass, '__subclasses__'): sub_targets = klass.__subclasses__() if sub_targets: - logdebug("~ weaving subclasses: %s", sub_targets) + logdebug('~ weaving subclasses: %s', sub_targets) for sub_class in sub_targets: if not issubclass(sub_class, Fabric): entanglement.merge(weave_class(sub_class, aspect, methods=methods, subclasses=subclasses, lazy=lazy, bag=bag)) @@ -620,7 +620,7 @@ def __init__(self, *args, **kwargs): if ismethoddescriptor(func): wrappers[attr] = _rewrap_method(func, klass, aspect) - logdebug(" * creating subclass with attributes %r", wrappers) + logdebug(' * creating subclass with attributes %r', wrappers) name = name or klass.__name__ SubClass = type(name, (klass, Fabric), wrappers) SubClass.__module__ = klass.__module__ @@ -631,7 +631,7 @@ def __init__(self, *args, **kwargs): for attr, func in klass.__dict__.items(): if method_matches(attr): if isroutine(func): - logdebug("@ patching attribute %r (original: %r).", attr, func) + logdebug('@ patching attribute %r (original: %r).', attr, func) setattr(klass, attr, _rewrap_method(func, klass, aspect)) else: continue @@ -644,7 +644,7 @@ def __init__(self, *args, **kwargs): for attr, func in sklass.__dict__.items(): if method_matches(attr) and attr not in original and attr not in super_original: if isroutine(func): - logdebug("@ patching attribute %r (from superclass: %s, original: %r).", attr, sklass.__name__, func) + logdebug('@ patching attribute %r (from superclass: %s, original: %r).', attr, sklass.__name__, func) setattr(klass, attr, _rewrap_method(func, sklass, aspect)) else: continue @@ -692,31 +692,31 @@ def patch_module(module, name, replacement, original=UNSPECIFIED, aliases=True, except (TypeError, AttributeError): pass for alias in dir(module): - logdebug("alias:%s (%s)", alias, name) + logdebug('alias:%s (%s)', alias, name) if hasattr(module, alias): obj = getattr(module, alias) - logdebug("- %s:%s (%s)", obj, original, obj is original) + logdebug('- %s:%s (%s)', obj, original, obj is original) if obj is original: if aliases or alias == name: - logdebug("= saving %s on %s.%s ...", replacement, target, alias) + logdebug('= saving %s on %s.%s ...', replacement, target, alias) setattr(module, alias, replacement) rollback.merge(lambda alias=alias: setattr(module, alias, original)) if alias == name: seen = True elif alias == name: if ismethod(obj): - logdebug("= saving %s on %s.%s ...", replacement, target, alias) + logdebug('= saving %s on %s.%s ...', replacement, target, alias) setattr(module, alias, replacement) rollback.merge(lambda alias=alias: setattr(module, alias, original)) seen = True else: - raise AssertionError("%s.%s = %s is not %s." % (module, alias, obj, original)) + raise AssertionError(f'{module}.{alias} = {obj} is not {original}.') if not seen: warnings.warn( - 'Setting %s.%s to %s. There was no previous definition, probably patching the wrong module.' % (target, name, replacement) + f'Setting {target}.{name} to {replacement}. There was no previous definition, probably patching the wrong module.', stacklevel=2 ) - logdebug("= saving %s on %s.%s ...", replacement, target, name) + logdebug('= saving %s on %s.%s ...', replacement, target, name) setattr(module, name, replacement) rollback.merge(lambda: setattr(module, name, original)) return rollback @@ -731,7 +731,7 @@ def patch_module_function(module, target, aspect, force_name=None, bag=BrokenBag :returns: An :obj:`aspectlib.Rollback` object. """ logdebug( - "patch_module_function (module=%s, target=%s, aspect=%s, force_name=%s, **options=%s", module, target, aspect, force_name, options + 'patch_module_function (module=%s, target=%s, aspect=%s, force_name=%s, **options=%s', module, target, aspect, force_name, options ) name = force_name or target.__name__ return patch_module(module, name, _checked_apply(aspect, target, module=module), original=target, **options) diff --git a/src/aspectlib/contrib.py b/src/aspectlib/contrib.py index 727bf0c..298bf0d 100644 --- a/src/aspectlib/contrib.py +++ b/src/aspectlib/contrib.py @@ -53,8 +53,15 @@ def retry_aspect(cutpoint, *args, **kwargs): timeout = backoff else: timeout = backoff(count) - logger.exception("%s(%s, %s) raised exception %s. %s retries left. Sleeping %s secs.", - cutpoint.__name__, args, kwargs, exc, retries - count, timeout) + logger.exception( + '%s(%s, %s) raised exception %s. %s retries left. Sleeping %s secs.', + cutpoint.__name__, + args, + kwargs, + exc, + retries - count, + timeout, + ) sleep(timeout) return retry_aspect if func is None else retry_aspect(func) @@ -64,7 +71,7 @@ def exponential_backoff(count): """ Wait 2**N seconds. """ - return 2 ** count + return 2**count retry.exponential_backoff = exponential_backoff diff --git a/src/aspectlib/debug.py b/src/aspectlib/debug.py index 7e030a1..af589bc 100644 --- a/src/aspectlib/debug.py +++ b/src/aspectlib/debug.py @@ -28,11 +28,10 @@ def format_stack(skip=0, length=6, _sep=os.path.sep): """ Returns a one-line string with the current callstack. """ - return ' < '.join("%s:%s:%s" % ( - '/'.join(f.f_code.co_filename.split(_sep)[-2:]), - f.f_lineno, - f.f_code.co_name - ) for f in islice(frame_iterator(sys._getframe(1 + skip)), length)) + return ' < '.join( + '{}:{}:{}'.format('/'.join(f.f_code.co_filename.split(_sep)[-2:]), f.f_lineno, f.f_code.co_name) + for f in islice(frame_iterator(sys._getframe(1 + skip)), length) + ) PRINTABLE = string.digits + string.ascii_letters + string.punctuation + ' ' @@ -46,20 +45,22 @@ def strip_non_ascii(val): return str(val).translate(ASCII_ONLY) -def log(func=None, - stacktrace=10, - stacktrace_align=60, - attributes=(), - module=True, - call=True, - call_args=True, - call_args_repr=repr, - result=True, - exception=True, - exception_repr=repr, - result_repr=strip_non_ascii, - use_logging='CRITICAL', - print_to=None): +def log( + func=None, + stacktrace=10, + stacktrace_align=60, + attributes=(), + module=True, + call=True, + call_args=True, + call_args_repr=repr, + result=True, + exception=True, + exception_repr=repr, + result_repr=strip_non_ascii, + use_logging='CRITICAL', + print_to=None, +): """ Decorates `func` to have logging. @@ -145,9 +146,9 @@ def log(func=None, Added `call` option. """ - loglevel = use_logging and ( - logging._levelNames if hasattr(logging, '_levelNames') else logging._nameToLevel - ).get(use_logging, logging.CRITICAL) + loglevel = use_logging and (logging._levelNames if hasattr(logging, '_levelNames') else logging._nameToLevel).get( + use_logging, logging.CRITICAL + ) _missing = object() def dump(buf): @@ -168,7 +169,7 @@ class __logged__(Aspect): def __init__(self, cutpoint_function, binding=None): mimic(self, cutpoint_function) self.cutpoint_function = cutpoint_function - self.final_function = super(__logged__, self).__call__(cutpoint_function) + self.final_function = super().__call__(cutpoint_function) self.binding = binding def __get__(self, instance, owner): @@ -194,26 +195,21 @@ def advising_function(self, *args, **kwargs): callarg = False val = getattr(instance, key, _missing) if val is not _missing and key != name: - info.append(' %s=%s' % ( - key, call_args_repr(val() if callarg else val) - )) - sig = buf = '{%s%s%s}.%s' % ( - instance_type.__module__ + '.' if module else '', - instance_type.__name__, - ''.join(info), - name + info.append(f' {key}={call_args_repr(val() if callarg else val)}') + sig = buf = '{{{}{}{}}}.{}'.format( + instance_type.__module__ + '.' if module else '', instance_type.__name__, ''.join(info), name ) else: sig = buf = name if call_args: - buf += '(%s%s)' % ( + buf += '({}{})'.format( ', '.join(repr(i) for i in (args if call_args is True else args[:call_args])), - ((', ' if args else '') + ', '.join('%s=%r' % i for i in kwargs.items())) + ((', ' if args else '') + ', '.join('{}={!r}'.format(*i) for i in kwargs.items())) if kwargs and call_args is True else '', ) if stacktrace: - buf = ("%%-%ds <<< %%s" % stacktrace_align) % (buf, format_stack(skip=1, length=stacktrace)) + buf = ('%%-%ds <<< %%s' % stacktrace_align) % (buf, format_stack(skip=1, length=stacktrace)) if call: dump(buf) try: @@ -222,11 +218,11 @@ def advising_function(self, *args, **kwargs): if exception: if not call: dump(buf) - dump('%s ~ raised %s' % (sig, exception_repr(exc))) + dump(f'{sig} ~ raised {exception_repr(exc)}') raise if result: - dump('%s => %s' % (sig, result_repr(res))) + dump(f'{sig} => {result_repr(res)}') if func: return __logged__(func) diff --git a/src/aspectlib/test.py b/src/aspectlib/test.py index 8f27181..5544935 100644 --- a/src/aspectlib/test.py +++ b/src/aspectlib/test.py @@ -37,7 +37,7 @@ from collections import ChainMap from collections import OrderedDict -__all__ = 'mock', 'record', "Story" +__all__ = 'mock', 'record', 'Story' logger = getLogger(__name__) logexception = logf(logger.exception) @@ -46,7 +46,7 @@ CallEx = namedtuple('CallEx', ('self', 'name', 'args', 'kwargs')) Result = namedtuple('Result', ('self', 'args', 'kwargs', 'result', 'exception')) ResultEx = namedtuple('ResultEx', ('self', 'name', 'args', 'kwargs', 'result', 'exception')) -_INIT = Sentinel("INIT") +_INIT = Sentinel('INIT') def mock(return_value, call=False): @@ -73,7 +73,7 @@ def mock_wrapper(*args, **kwargs): return mock_decorator -class LogCapture(object): +class LogCapture: """ Records all log messages made on the given logger. Assumes the logger has a ``_log`` method. @@ -152,7 +152,7 @@ def messages(self): def has(self, message, *args, **kwargs): level = kwargs.pop('level', None) - assert not kwargs, "Unexpected arguments: %s" % kwargs + assert not kwargs, f'Unexpected arguments: {kwargs}' for call_final_message, call_message, call_args, call_level in self._calls: if level is None or level == call_level: if message == call_message and args == call_args if args else message == call_final_message or message == call_message: @@ -162,12 +162,11 @@ def has(self, message, *args, **kwargs): def assertLogged(self, message, *args, **kwargs): if not self.has(message, *args, **kwargs): raise AssertionError( - "There's no such message %r (with args %r) logged on %s. Logged messages where: %s" - % (message, args, self._logger, self.calls) + f"There's no such message {message!r} (with args {args!r}) logged on {self._logger}. Logged messages where: {self.calls}" ) -class _RecordingFunctionWrapper(object): +class _RecordingFunctionWrapper: """ Function wrapper that records calls and can be used as an weaver context manager. @@ -175,7 +174,7 @@ class _RecordingFunctionWrapper(object): """ def __init__(self, wrapped, iscalled=True, calls=None, callback=None, extended=False, results=False, recurse_lock=None, binding=None): - assert not results or iscalled, "`iscalled` must be True if `results` is True" + assert not results or iscalled, '`iscalled` must be True if `results` is True' mimic(self, wrapped) self.__wrapped = wrapped self.__entanglement = None @@ -295,8 +294,8 @@ def record(func=None, recurse_lock_factory=allocate_lock, **options): return partial(record, **options) -class StoryResultWrapper(object): - __slots__ = '__recorder__' +class StoryResultWrapper: + __slots__ = ('__recorder__',) # def __init__(self, recorder): self.__recorder__ = recorder @@ -306,11 +305,11 @@ def __eq__(self, result): def __pow__(self, exception): if not (isinstance(exception, BaseException) or isclass(exception) and issubclass(exception, BaseException)): - raise RuntimeError("Value %r must be an exception type or instance." % exception) + raise RuntimeError(f'Value {exception!r} must be an exception type or instance.') self.__recorder__(_Raises(exception)) def __unsupported__(self, *args): - raise TypeError("Unsupported operation. Only `==` (for results) and `**` (for exceptions) can be used.") + raise TypeError('Unsupported operation. Only `==` (for results) and `**` (for exceptions) can be used.') for mm in ( '__add__', @@ -382,10 +381,10 @@ def __unsupported__(self, *args): '__rcmp__', '__nonzero__', ): - exec("%s = __unsupported__" % mm) + exec(f'{mm} = __unsupported__') # noqa: S102 -class _StoryFunctionWrapper(object): +class _StoryFunctionWrapper: def __init__(self, wrapped, handle, binding=None, owner=None): self._wrapped = wrapped self._name = wrapped.__name__ @@ -429,7 +428,7 @@ def __call__(self, *args, **kwargs): return self._handle(self._binding, self._name, args, kwargs, self._wrapped) -class _RecordingBase(object): +class _RecordingBase: _target = None _options = None @@ -443,13 +442,13 @@ def __init__(self, target, **options): def _make_key(self, binding, name, args, kwargs): if binding is not None: binding, _ = self._ids[id(binding)] - return (binding, name, ', '.join(repr_ex(i) for i in args), ', '.join("%s=%s" % (k, repr_ex(v)) for k, v in kwargs.items())) + return (binding, name, ', '.join(repr_ex(i) for i in args), ', '.join(f'{k}={repr_ex(v)}' for k, v in kwargs.items())) def _tag_result(self, name, result): if isinstance(result, _Binds): instance_name = camelcase_to_underscores(name.rsplit('.', 1)[-1]) self._instances[instance_name] += 1 - instance_name = "%s_%s" % (instance_name, self._instances[instance_name]) + instance_name = f'{instance_name}_{self._instances[instance_name]}' self._ids[id(result.value)] = instance_name, result.value result.value = instance_name else: @@ -459,14 +458,9 @@ def _tag_result(self, name, result): def _handle(self, binding, name, args, kwargs, result): pk = self._make_key(binding, name, args, kwargs) result = self._tag_result(name, result) - assert ( - pk not in self._calls or self._calls[pk] == result - ), "Story creation inconsistency. There is already a result cached for " "binding:%r name:%r args:%r kwargs:%r and it's: %r." % ( - binding, - name, - args, - kwargs, - self._calls[pk], + assert pk not in self._calls or self._calls[pk] == result, ( + 'Story creation inconsistency. There is already a result cached for ' + f"binding:{binding!r} name:{name!r} args:{args!r} kwargs:{kwargs!r} and it's: {self._calls[pk]!r}." ) self._calls[pk] = result @@ -480,9 +474,9 @@ def __exit__(self, *args): del self._ids -_Raises = container("Raises") -_Returns = container("Returns") -_Binds = container("Binds") +_Raises = container('Raises') +_Returns = container('Returns') +_Binds = container('Binds') class Story(_RecordingBase): @@ -531,7 +525,7 @@ class Story(_RecordingBase): _FunctionWrapper = _StoryFunctionWrapper def __init__(self, *args, **kwargs): - super(Story, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) frame = _getframe(1) self._context = frame.f_globals, frame.f_locals @@ -577,14 +571,14 @@ def replay(self, **options): return Replay(self, **options) -ReplayPair = namedtuple("ReplayPair", ('expected', 'actual')) +ReplayPair = namedtuple('ReplayPair', ('expected', 'actual')) def logged_eval(value, context): try: - return eval(value, *context) - except: # noqa - logexception("Failed to evaluate %r.\nContext:\n%s", value, ''.join(format_stack(f=_getframe(1), limit=15))) + return eval(value, *context) # noqa: S307 + except: + logexception('Failed to evaluate %r.\nContext:\n%s', value, ''.join(format_stack(f=_getframe(1), limit=15))) raise @@ -599,7 +593,7 @@ class Replay(_RecordingBase): _FunctionWrapper = _ReplayFunctionWrapper def __init__(self, play, proxy=True, strict=True, dump=True, recurse_lock=False, **options): - super(Replay, self).__init__(play._target, **options) + super().__init__(play._target, **options) self._calls, self._expected, self._actual = ChainMap(self._calls, play._calls), play._calls, self._calls self._proxy = proxy @@ -619,7 +613,7 @@ def _handle(self, binding, name, args, kwargs, wrapped, bind=None): elif isinstance(result, _Raises): raise logged_eval(result.value, self._context) else: - raise RuntimeError('Internal failure - unknown result: %r' % result) # pragma: no cover + raise RuntimeError(f'Internal failure - unknown result: {result!r}') # pragma: no cover else: if self._proxy: shouldrecord = not self._recurse_lock or self._recurse_lock.acquire(False) @@ -640,7 +634,7 @@ def _handle(self, binding, name, args, kwargs, wrapped, bind=None): if shouldrecord and self._recurse_lock: self._recurse_lock.release() else: - raise AssertionError("Unexpected call to %s/%s with args:%s kwargs:%s" % pk) + raise AssertionError('Unexpected call to {}/{} with args:{} kwargs:{}'.format(*pk)) def _unexpected(self, _missing=False): if _missing: @@ -718,7 +712,7 @@ def expected(self): return ''.join(_format_calls(self._expected)) def __exit__(self, *exception): - super(Replay, self).__exit__() + super().__exit__() if self._strict or self._dump: diff = self.diff if diff: @@ -733,17 +727,17 @@ def __exit__(self, *exception): def _format_calls(calls): for (binding, name, args, kwargs), result in calls.items(): - sig = '%s(%s%s%s)' % (name, args, ', ' if kwargs and args else '', kwargs) + sig = '{}({}{}{})'.format(name, args, ', ' if kwargs and args else '', kwargs) if isinstance(result, _Binds): - yield '%s = %s\n' % (result.value, sig) + yield f'{result.value} = {sig}\n' elif isinstance(result, _Returns): if binding is None: - yield '%s == %s # returns\n' % (sig, result.value) + yield f'{sig} == {result.value} # returns\n' else: - yield '%s.%s == %s # returns\n' % (binding, sig, result.value) + yield f'{binding}.{sig} == {result.value} # returns\n' elif isinstance(result, _Raises): if binding is None: - yield '%s ** %s # raises\n' % (sig, result.value) + yield f'{sig} ** {result.value} # raises\n' else: - yield '%s.%s ** %s # raises\n' % (binding, sig, result.value) + yield f'{binding}.{sig} ** {result.value} # raises\n' diff --git a/src/aspectlib/utils.py b/src/aspectlib/utils.py index c936911..fea87e6 100644 --- a/src/aspectlib/utils.py +++ b/src/aspectlib/utils.py @@ -7,7 +7,7 @@ from functools import wraps from inspect import isclass -RegexType = type(re.compile("")) +RegexType = type(re.compile('')) PY3 = sys.version_info[0] == 3 PY310 = PY3 and sys.version_info[1] >= 10 @@ -50,7 +50,7 @@ def camelcase_to_underscores(name): def qualname(obj): if hasattr(obj, '__module__') and obj.__module__ not in ('builtins', 'exceptions'): - return '%s.%s' % (obj.__module__, obj.__name__) + return f'{obj.__module__}.{obj.__name__}' else: return obj.__name__ @@ -72,19 +72,19 @@ def make_method_matcher(regex_or_regexstr_or_namelist): elif isinstance(regex_or_regexstr_or_namelist, RegexType): return regex_or_regexstr_or_namelist.match else: - raise TypeError("Unacceptable methods spec %r." % regex_or_regexstr_or_namelist) + raise TypeError(f'Unacceptable methods spec {regex_or_regexstr_or_namelist!r}.') -class Sentinel(object): +class Sentinel: def __init__(self, name, doc=''): self.name = name self.__doc__ = doc def __repr__(self): if not self.__doc__: - return "%s" % self.name + return f'{self.name}' else: - return "%s: %s" % (self.name, self.__doc__) + return f'{self.name}: {self.__doc__}' __str__ = __repr__ @@ -99,8 +99,8 @@ def __init__(self, value): { '__slots__': 'value', '__init__': __init__, - '__str__': lambda self: "%s(%s)" % (name, self.value), - '__repr__': lambda self: "%s(%r)" % (name, self.value), + '__str__': lambda self: f'{name}({self.value})', + '__repr__': lambda self: f'{name}({self.value!r})', '__eq__': lambda self, other: type(self) is type(other) and self.value == other.value, }, ) @@ -123,13 +123,14 @@ def mimic(wrapper, func, module=None): representers = { - tuple: lambda obj, aliases: "(%s%s)" % (', '.join(repr_ex(i) for i in obj), ',' if len(obj) == 1 else ''), - list: lambda obj, aliases: "[%s]" % ', '.join(repr_ex(i) for i in obj), - set: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj), - frozenset: lambda obj, aliases: "set([%s])" % ', '.join(repr_ex(i) for i in obj), - deque: lambda obj, aliases: "collections.deque([%s])" % ', '.join(repr_ex(i) for i in obj), - dict: lambda obj, aliases: "{%s}" - % ', '.join("%s: %s" % (repr_ex(k), repr_ex(v)) for k, v in (obj.items() if PY3 else obj.iteritems())), + tuple: lambda obj, aliases: '({}{})'.format(', '.join(repr_ex(i) for i in obj), ',' if len(obj) == 1 else ''), + list: lambda obj, aliases: '[{}]'.format(', '.join(repr_ex(i) for i in obj)), + set: lambda obj, aliases: 'set([{}])'.format(', '.join(repr_ex(i) for i in obj)), + frozenset: lambda obj, aliases: 'set([{}])'.format(', '.join(repr_ex(i) for i in obj)), + deque: lambda obj, aliases: 'collections.deque([{}])'.format(', '.join(repr_ex(i) for i in obj)), + dict: lambda obj, aliases: '{{{}}}'.format( + ', '.join(f'{repr_ex(k)}: {repr_ex(v)}' for k, v in (obj.items() if PY3 else obj.iteritems())) + ), } @@ -137,7 +138,7 @@ def _make_fixups(): for obj in ('os.stat_result', 'grp.struct_group', 'pwd.struct_passwd'): mod, attr = obj.split('.') try: - yield getattr(__import__(mod), attr), lambda obj, aliases, prefix=obj: "%s(%r)" % (prefix, obj.__reduce__()[1][0]) + yield getattr(__import__(mod), attr), lambda obj, aliases, prefix=obj: f'{prefix}({obj.__reduce__()[1][0]!r})' except ImportError: continue @@ -148,7 +149,7 @@ def _make_fixups(): def repr_ex(obj, aliases=()): kind, ident = type(obj), id(obj) if isinstance(kind, BaseException): - return "%s(%s)" % (qualname(type(obj)), ', '.join(repr_ex(i, aliases) for i in obj.args)) + return '{}({})'.format(qualname(type(obj)), ', '.join(repr_ex(i, aliases) for i in obj.args)) elif isclass(obj): return qualname(obj) elif kind in representers: diff --git a/tests/mymod.py b/tests/mymod.py index 9855e10..eb019b8 100644 --- a/tests/mymod.py +++ b/tests/mymod.py @@ -1,6 +1,6 @@ def func(arg): - print("Got", arg, "in the real code!") + print('Got', arg, 'in the real code!') def badfunc(): - raise ValueError("boom!") + raise ValueError('boom!') diff --git a/tests/test_aspectlib.py b/tests/test_aspectlib.py index a50558f..ade88b4 100644 --- a/tests/test_aspectlib.py +++ b/tests/test_aspectlib.py @@ -1,12 +1,11 @@ -# encoding: utf8 -from pytest import raises +import pytest import aspectlib from aspectlib.test import mock from aspectlib.test import record -class Base(object): +class Base: def meth(*_): return 'base' @@ -44,7 +43,7 @@ def module_func2(): module_func3 = module_func2 -class NormalTestClass(object): +class NormalTestClass: some = 'attribute' def __init__(self, foo=None): @@ -156,7 +155,7 @@ def static_foobar(foo, bar=None): return 'subsub' + (bar or foo) -class SlotsTestClass(object): +class SlotsTestClass: __slots__ = 'inst', 'klass', 'static', 'other', 'foo', 'bar' some = 'attribute' @@ -220,7 +219,7 @@ def aspect(): yield def aspect_fail(): - return "crap" + return 'crap' aspect.advising_function = aspect_fail @@ -228,7 +227,7 @@ def aspect_fail(): def func(): pass - raises(aspectlib.ExpectedGenerator, func) + pytest.raises(aspectlib.ExpectedGenerator, func) def test_aspect_gen_bind(): @@ -271,7 +270,7 @@ def aspect(): yield def aspect_fail(): - return "crap" + return 'crap' aspect.advising_function = aspect_fail @@ -279,14 +278,14 @@ def aspect_fail(): def func(): yield - raises(aspectlib.ExpectedGenerator, list, func()) + pytest.raises(aspectlib.ExpectedGenerator, list, func()) def test_aspect_bad_decorate(): def aspect(): - return "crap" + return 'crap' - raises(aspectlib.ExpectedGeneratorFunction, aspectlib.Aspect, aspect) + pytest.raises(aspectlib.ExpectedGeneratorFunction, aspectlib.Aspect, aspect) def test_aspect_return(): @@ -327,7 +326,7 @@ def aspect(): @aspect def func(): - 1 / 0 + 1 / 0 # noqa: B018 assert func() == 'stuff' @@ -335,14 +334,14 @@ def func(): def test_aspect_raise_from_aspect(): @aspectlib.Aspect def aspect(): - 1 / 0 + 1 / 0 # noqa: B018 yield @aspect def func(): pass - raises(ZeroDivisionError, func) + pytest.raises(ZeroDivisionError, func) def test_aspect_return_but_call(): @@ -371,28 +370,28 @@ def test_weave_func(): def test_broken_aspect(): - raises(aspectlib.ExpectedAdvice, aspectlib.weave, None, None) + pytest.raises(aspectlib.ExpectedAdvice, aspectlib.weave, None, None) def test_weave_empty_target(): - raises(aspectlib.ExpectedAdvice, aspectlib.weave, (), None) + pytest.raises(aspectlib.ExpectedAdvice, aspectlib.weave, (), None) def test_weave_missing_global(cls=Global): global Global Global = 'crap' try: - raises(AssertionError, aspectlib.weave, cls, mock('stuff'), lazy=True) + pytest.raises(AssertionError, aspectlib.weave, cls, mock('stuff'), lazy=True) finally: Global = cls def test_weave_str_missing_target(): - raises(AttributeError, aspectlib.weave, 'test_pkg1.test_pkg2.target', mock('foobar')) + pytest.raises(AttributeError, aspectlib.weave, 'test_pkg1.test_pkg2.target', mock('foobar')) def test_weave_str_bad_target(): - raises(TypeError, aspectlib.weave, 'test_pkg1.test_pkg2.test_mod.a', mock('foobar')) + pytest.raises(TypeError, aspectlib.weave, 'test_pkg1.test_pkg2.test_mod.a', mock('foobar')) def test_weave_str_target(): @@ -446,7 +445,7 @@ def test_weave_wrong_module(): None, ( "Setting test_aspectlib.MissingGlobal to . " - "There was no previous definition, probably patching the wrong module.", + 'There was no previous definition, probably patching the wrong module.', ), {}, ) @@ -501,7 +500,7 @@ def test_weave_bad_args4(): def test_weave_bad_args5(): - raises(TypeError, aspectlib.weave, Sub, mock('stuff'), methods=False) + pytest.raises(TypeError, aspectlib.weave, Sub, mock('stuff'), methods=False) def test_weave_class_meth(): @@ -570,7 +569,7 @@ def test_weave_subclass_meth_from_baseclass(): @aspectlib.Aspect def aspect(*args): result = yield - history.append(args + (result,)) + history.append((*args, result)) yield aspectlib.Return('bar-' + result) with aspectlib.weave(NormalTestSubClass.only_in_base, aspect): @@ -590,7 +589,7 @@ def test_weave_subclass_meth_from_baseclass_2_level(): @aspectlib.Aspect def aspect(*args): result = yield - history.append(args + (result,)) + history.append((*args, result)) yield aspectlib.Return('bar-' + result) with aspectlib.weave(NormalTestSubSubClass.only_in_base, aspect): @@ -610,7 +609,7 @@ def test_weave_legacy_subclass_meth_from_baseclass(): @aspectlib.Aspect def aspect(*args): result = yield - history.append(args + (result,)) + history.append((*args, result)) yield aspectlib.Return('bar-' + result) with aspectlib.weave(LegacyTestSubClass.only_in_base, aspect): @@ -630,7 +629,7 @@ def test_weave_legacy_subclass_meth_from_baseclass_2_level(): @aspectlib.Aspect def aspect(*args): result = yield - history.append(args + (result,)) + history.append((*args, result)) yield aspectlib.Return('bar-' + result) with aspectlib.weave(LegacyTestSubSubClass.only_in_base, aspect): @@ -1147,9 +1146,9 @@ def aspect(): @aspect def func(): - 1 / 0 + 1 / 0 # noqa: B018 - raises(ZeroDivisionError, func) + pytest.raises(ZeroDivisionError, func) def test_weave_unknown(): @@ -1157,7 +1156,7 @@ def test_weave_unknown(): def aspect(): yield aspectlib.Proceed - raises(aspectlib.UnsupportedType, aspectlib.weave, 1, aspect) + pytest.raises(aspectlib.UnsupportedType, aspectlib.weave, 1, aspect) def test_weave_unimportable(): @@ -1165,7 +1164,7 @@ def test_weave_unimportable(): def aspect(): yield aspectlib.Proceed - raises(ImportError, aspectlib.weave, "asdf1.qwer2", aspect) + pytest.raises(ImportError, aspectlib.weave, 'asdf1.qwer2', aspect) def test_weave_subclass(Bub=Sub): @@ -1211,22 +1210,22 @@ def _internal(): pass -def ăbc(): +def ăbc(): # noqa: PLC2401 pass -def test_ăbc(): +def test_ăbc(): # noqa: PLC2401 with aspectlib.weave('test_aspectlib.ăbc', mock('stuff')): assert ăbc() == 'stuff' def test_invalid_string_target(): - raises(SyntaxError, aspectlib.weave, 'inva lid', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.inva lid', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.2invalid', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.some,junk', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.some?junk', mock(None)) - raises(SyntaxError, aspectlib.weave, 'os.some*junk', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'inva lid', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'os.inva lid', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'os.2invalid', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'os.some,junk', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'os.some?junk', mock(None)) + pytest.raises(SyntaxError, aspectlib.weave, 'os.some*junk', mock(None)) with aspectlib.weave('test_aspectlib._internal', mock('stuff')): assert _internal() == 'stuff' @@ -1238,15 +1237,15 @@ def test_list_of_aspects(): assert module_func.calls == [(None, (1, 2, 3), {})] with aspectlib.weave(module_func, [mock('foobar', call=True), record]): - raises(TypeError, module_func, 1, 2, 3) + pytest.raises(TypeError, module_func, 1, 2, 3) assert module_func.calls == [(None, (1, 2, 3), {})] def test_list_of_invalid_aspects(): - raises(AssertionError, aspectlib.weave, module_func, [lambda func: None]) - raises(TypeError, aspectlib.weave, module_func, [lambda: None]) - raises(aspectlib.ExpectedAdvice, aspectlib.weave, module_func, [None]) - raises(aspectlib.ExpectedAdvice, aspectlib.weave, module_func, ['foobar']) + pytest.raises(AssertionError, aspectlib.weave, module_func, [lambda func: None]) + pytest.raises(TypeError, aspectlib.weave, module_func, [lambda: None]) + pytest.raises(aspectlib.ExpectedAdvice, aspectlib.weave, module_func, [None]) + pytest.raises(aspectlib.ExpectedAdvice, aspectlib.weave, module_func, ['foobar']) def test_aspect_on_func(): @@ -1272,7 +1271,7 @@ def aspect(): @aspect def func(): - raise RuntimeError() + raise RuntimeError assert func() == 'squelched' assert hist == ['before', 'error', 'finally', 'closed'] @@ -1281,13 +1280,13 @@ def func(): def test_aspect_on_func_invalid_advice(): @aspectlib.Aspect def aspect(): - yield "stuff" + yield 'stuff' @aspect def func(): - raise RuntimeError() + raise RuntimeError - raises(aspectlib.UnacceptableAdvice, func) + pytest.raises(aspectlib.UnacceptableAdvice, func) def test_aspect_on_generator_func(): @@ -1314,9 +1313,8 @@ def aspect(): @aspect def func(): - for i in range(3): - yield i - raise RuntimeError() + yield from range(3) + raise RuntimeError assert list(func()) == [0, 1, 2] print(hist) @@ -1330,11 +1328,10 @@ def aspect(): @aspect def func(): - for i in range(3): - yield i - raise RuntimeError() + yield from range(3) + raise RuntimeError - raises(aspectlib.UnacceptableAdvice, list, func()) + pytest.raises(aspectlib.UnacceptableAdvice, list, func()) def test_aspect_on_generator_different_args(): @@ -1416,7 +1413,7 @@ def func(): gen = func() print(next(gen)) - raises(StopIteration, gen.throw, RuntimeError) + pytest.raises(StopIteration, gen.throw, RuntimeError) assert excs == [RuntimeError] @@ -1455,7 +1452,7 @@ def test_weave_module(strmod=None): def test_weave_module_as_str(): - test_weave_module("test_pkg1.test_pkg2.test_mod") + test_weave_module('test_pkg1.test_pkg2.test_mod') def test_weave_method(): @@ -1467,7 +1464,7 @@ def intercept(func, *args): intercepted.append(args) yield aspectlib.Proceed(*args) - class Foo(object): + class Foo: def foo(self, arg): calls.append((self, arg)) @@ -1488,7 +1485,7 @@ def intercept(func, *args): intercepted.append(args) yield - class Foo(object): + class Foo: def foo(self, arg): calls.append((self, arg)) diff --git a/tests/test_aspectlib_debug.py b/tests/test_aspectlib_debug.py index 4d91c46..976719c 100644 --- a/tests/test_aspectlib_debug.py +++ b/tests/test_aspectlib_debug.py @@ -14,10 +14,10 @@ from io import StringIO LOG_TEST_SIMPLE = ( - r'''^some_meth\(1, 2, 3, a=4\) +<<< .*tests/test_aspectlib_debug.py:\d+:test_simple.* -some_meth => \.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. !"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@''' - r'''ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~\.+ -$''' + r"""^some_meth\(1, 2, 3, a=4\) +<<< .*tests/test_aspectlib_debug.py:\d+:test_simple.* +some_meth => \.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\. !"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@""" + r"""ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~\.+ +$""" ) @@ -25,7 +25,7 @@ def some_meth(*_args, **_kwargs): return ''.join(chr(i) for i in range(255)) -class MyStuff(object): +class MyStuff: def __init__(self, foo): self.foo = foo @@ -58,7 +58,7 @@ def test_simple(): def test_fail_to_log(): - @aspectlib.debug.log(print_to="crap") + @aspectlib.debug.log(print_to='crap') def foo(): pass @@ -145,7 +145,7 @@ def test_no_stack_old_style(): ) -@pytest.mark.skipif(sys.version_info < (2, 7), reason="No weakref.WeakSet on Python<=2.6") +@pytest.mark.skipif(sys.version_info < (2, 7), reason='No weakref.WeakSet on Python<=2.6') def test_weakref(): with aspectlib.weave(MyStuff, aspectlib.debug.log): s = weakref.WeakSet() @@ -154,7 +154,7 @@ def test_weakref(): print(list(s)) -@pytest.mark.skipif(sys.version_info < (2, 7), reason="No weakref.WeakSet on Python<=2.6") +@pytest.mark.skipif(sys.version_info < (2, 7), reason='No weakref.WeakSet on Python<=2.6') def test_weakref_oldstyle(): with aspectlib.weave(OldStuff, aspectlib.debug.log): s = weakref.WeakSet() diff --git a/tests/test_aspectlib_py3.py b/tests/test_aspectlib_py3.py index 40c056b..dc0ed3b 100644 --- a/tests/test_aspectlib_py3.py +++ b/tests/test_aspectlib_py3.py @@ -1,6 +1,3 @@ -# encoding: utf8 - - import pytest import aspectlib diff --git a/tests/test_aspectlib_py37.py b/tests/test_aspectlib_py37.py index 96b112b..daf10e4 100644 --- a/tests/test_aspectlib_py37.py +++ b/tests/test_aspectlib_py37.py @@ -1,10 +1,6 @@ -# encoding: utf8 - - import pytest import aspectlib - from test_aspectlib_py3 import consume @@ -50,10 +46,10 @@ def aspect(): def func(): val = 99 for _ in range(3): - print("YIELD", val + 1) + print('YIELD', val + 1) val = yield val + 1 - print("GOT", val) - return "the-return-value" + print('GOT', val) + return 'the-return-value' gen = func() data = [] @@ -136,9 +132,9 @@ def func(): while 1: next(gen) except StopIteration as exc: - assert exc.args == ('result',) + assert exc.args == ('result',) # noqa: PT017 else: - raise AssertionError("did not raise StopIteration") + raise AssertionError('did not raise StopIteration') def test_aspect_chain_on_generator_no_return(): diff --git a/tests/test_aspectlib_test.py b/tests/test_aspectlib_test.py index 16c8e9c..02514ce 100644 --- a/tests/test_aspectlib_test.py +++ b/tests/test_aspectlib_test.py @@ -1,5 +1,4 @@ -from pytest import raises -from test_pkg1.test_pkg2 import test_mod +import pytest from aspectlib.test import OrderedDict from aspectlib.test import Story @@ -12,6 +11,7 @@ from aspectlib.test import record from aspectlib.utils import PY310 from aspectlib.utils import repr_ex +from test_pkg1.test_pkg2 import test_mod pytest_plugins = ('pytester',) @@ -64,7 +64,7 @@ def test_record_result(): def test_record_exception(): fun = record(results=True)(rfun) - raises(RuntimeError, fun) + pytest.raises(RuntimeError, fun) assert fun.calls == [ (None, (), {}, None, exc), ] @@ -88,7 +88,7 @@ def test_record_exception_callback(): fun = record(results=True, callback=lambda *args: calls.append(args))(rfun) - raises(RuntimeError, fun) + pytest.raises(RuntimeError, fun) assert calls == [ (None, 'test_aspectlib_test.rfun', (), {}, None, exc), ] @@ -152,23 +152,23 @@ def test_record_as_context(): def test_bad_mock(): - raises(TypeError, mock) - raises(TypeError, mock, call=False) + pytest.raises(TypeError, mock) + pytest.raises(TypeError, mock, call=False) def test_simple_mock(): - assert "foobar" == mock("foobar")(module_fun)(1) + assert 'foobar' == mock('foobar')(module_fun)(1) def test_mock_no_calls(): with record(module_fun) as history: - assert "foobar" == mock("foobar")(module_fun)(2) + assert 'foobar' == mock('foobar')(module_fun)(2) assert history.calls == [] def test_mock_with_calls(): with record(module_fun) as history: - assert "foobar" == mock("foobar", call=True)(module_fun)(3) + assert 'foobar' == mock('foobar', call=True)(module_fun)(3) assert history.calls == [(None, (3,), {})] @@ -193,7 +193,7 @@ def test_double_recording(): def test_record_not_iscalled_and_results(): - raises(AssertionError, record, module_fun, iscalled=False, results=True) + pytest.raises(AssertionError, record, module_fun, iscalled=False, results=True) record(module_fun, iscalled=False, results=False) record(module_fun, iscalled=True, results=True) record(module_fun, iscalled=True, results=False) @@ -201,23 +201,23 @@ def test_record_not_iscalled_and_results(): def test_story_empty_play_noproxy(): with Story(test_mod).replay(recurse_lock=True, proxy=False, strict=False) as replay: - raises(AssertionError, test_mod.target) + pytest.raises(AssertionError, test_mod.target) assert replay._actual == {} def test_story_empty_play_proxy(): assert test_mod.target() is None - raises(TypeError, test_mod.target, 123) + pytest.raises(TypeError, test_mod.target, 123) with Story(test_mod).replay(recurse_lock=True, proxy=True, strict=False) as replay: assert test_mod.target() is None - raises(TypeError, test_mod.target, 123) + pytest.raises(TypeError, test_mod.target, 123) assert format_calls(replay._actual) == format_calls( OrderedDict( [ - ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), + ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns('None')), ( (None, 'test_pkg1.test_pkg2.test_mod.target', '123', ''), _Raises( @@ -235,14 +235,14 @@ def test_story_empty_play_proxy(): def test_story_empty_play_noproxy_class(): with Story(test_mod).replay(recurse_lock=True, proxy=False, strict=False) as replay: - raises(AssertionError, test_mod.Stuff, 1, 2) + pytest.raises(AssertionError, test_mod.Stuff, 1, 2) assert replay._actual == {} def test_story_empty_play_error_on_init(): with Story(test_mod).replay(strict=False) as replay: - raises(ValueError, test_mod.Stuff, "error") + pytest.raises(ValueError, test_mod.Stuff, 'error') # noqa: PT011 print(replay._actual) assert replay._actual == OrderedDict([((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'error'", ''), _Raises('ValueError()'))]) @@ -253,21 +253,21 @@ def test_story_half_play_noproxy_class(): with story.replay(recurse_lock=True, proxy=False, strict=False): obj = test_mod.Stuff(1, 2) - raises(AssertionError, obj.mix, 3, 4) + pytest.raises(AssertionError, obj.mix, 3, 4) def test_xxx(): with Story(test_mod) as story: obj = test_mod.Stuff(1, 2) - test_mod.target(1) == 2 - test_mod.target(2) == 3 + test_mod.target(1) == 2 # noqa: B015 + test_mod.target(2) == 3 # noqa: B015 test_mod.target(3) ** ValueError other = test_mod.Stuff(2, 2) - obj.other('a') == other - obj.meth('a') == 'x' + obj.other('a') == other # noqa: B015 + obj.meth('a') == 'x' # noqa: B015 obj = test_mod.Stuff(2, 3) obj.meth() ** ValueError('crappo') - obj.meth('c') == 'x' + obj.meth('c') == 'x' # noqa: B015 with story.replay(recurse_lock=True, strict=False) as replay: obj = test_mod.Stuff(1, 2) @@ -280,10 +280,10 @@ def test_xxx(): obj.meth() for k, v in story._calls.items(): - print(k, "=>", v) - print("############## UNEXPECTED ##############") + print(k, '=>', v) + print('############## UNEXPECTED ##############') for k, v in replay._actual.items(): - print(k, "=>", v) + print(k, '=>', v) # TODO @@ -291,12 +291,12 @@ def test_xxx(): def test_story_text_helpers(): with Story(test_mod) as story: obj = test_mod.Stuff(1, 2) - obj.meth('a') == 'x' - obj.meth('b') == 'y' + obj.meth('a') == 'x' # noqa: B015 + obj.meth('b') == 'y' # noqa: B015 obj = test_mod.Stuff(2, 3) - obj.meth('c') == 'z' - test_mod.target(1) == 2 - test_mod.target(2) == 3 + obj.meth('c') == 'z' # noqa: B015 + test_mod.target(1) == 2 # noqa: B015 + test_mod.target(2) == 3 # noqa: B015 with story.replay(recurse_lock=True, strict=False) as replay: obj = test_mod.Stuff(1, 2) @@ -351,13 +351,13 @@ def test_story_empty_play_proxy_class_missing_report(LineMatcher): obj = test_mod.Stuff(1, 2) obj.mix(3, 4) obj.mix('a', 'b') - raises(ValueError, obj.raises, 123) + pytest.raises(ValueError, obj.raises, 123) # noqa: PT011 obj = test_mod.Stuff(0, 1) obj.mix('a', 'b') obj.mix(3, 4) test_mod.target() - raises(ValueError, test_mod.raises, 'badarg') - raises(ValueError, obj.raises, 123) + pytest.raises(ValueError, test_mod.raises, 'badarg') # noqa: PT011 + pytest.raises(ValueError, obj.raises, 123) # noqa: PT011 test_mod.ThatLONGStuf(1).mix(2) test_mod.ThatLONGStuf(3).mix(4) obj = test_mod.ThatLONGStuf(2) @@ -366,27 +366,27 @@ def test_story_empty_play_proxy_class_missing_report(LineMatcher): obj.mix(10) LineMatcher(replay.diff.splitlines()).fnmatch_lines( [ - "--- expected", - "+++ actual", - "@@ -0,0 +1,18 @@", - "+stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)", - "+stuff_1.mix(3, 4) == (1, 2, 3, 4) # returns", + '--- expected', + '+++ actual', + '@@ -0,0 +1,18 @@', + '+stuff_1 = test_pkg1.test_pkg2.test_mod.Stuff(1, 2)', + '+stuff_1.mix(3, 4) == (1, 2, 3, 4) # returns', "+stuff_1.mix('a', 'b') == (1, 2, 'a', 'b') # returns", - "+stuff_1.raises(123) ** ValueError((123,)*) # raises", - "+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)", + '+stuff_1.raises(123) ** ValueError((123,)*) # raises', + '+stuff_2 = test_pkg1.test_pkg2.test_mod.Stuff(0, 1)', "+stuff_2.mix('a', 'b') == (0, 1, 'a', 'b') # returns", - "+stuff_2.mix(3, 4) == (0, 1, 3, 4) # returns", - "+test_pkg1.test_pkg2.test_mod.target() == None # returns", + '+stuff_2.mix(3, 4) == (0, 1, 3, 4) # returns', + '+test_pkg1.test_pkg2.test_mod.target() == None # returns', "+test_pkg1.test_pkg2.test_mod.raises('badarg') ** ValueError(('badarg',)*) # raises", - "+stuff_2.raises(123) ** ValueError((123,)*) # raises", - "+that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)", - "+that_long_stuf_1.mix(2) == (1, 2) # returns", - "+that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)", - "+that_long_stuf_2.mix(4) == (3, 4) # returns", - "+that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)", - "+that_long_stuf_3.mix() == (2,) # returns", - "+that_long_stuf_3.meth() == None # returns", - "+that_long_stuf_3.mix(10) == (2, 10) # returns", + '+stuff_2.raises(123) ** ValueError((123,)*) # raises', + '+that_long_stuf_1 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(1)', + '+that_long_stuf_1.mix(2) == (1, 2) # returns', + '+that_long_stuf_2 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(3)', + '+that_long_stuf_2.mix(4) == (3, 4) # returns', + '+that_long_stuf_3 = test_pkg1.test_pkg2.test_mod.ThatLONGStuf(2)', + '+that_long_stuf_3.mix() == (2,) # returns', + '+that_long_stuf_3.meth() == None # returns', + '+that_long_stuf_3.mix(10) == (2, 10) # returns', ] ) @@ -399,22 +399,22 @@ def test_story_empty_play_proxy_class(): assert obj.mix(3, 4) == (1, 2, 3, 4) assert obj.mix('a', 'b') == (1, 2, 'a', 'b') - raises(TypeError, obj.meth, 123) + pytest.raises(TypeError, obj.meth, 123) obj = test_mod.Stuff(0, 1) assert obj.mix('a', 'b') == (0, 1, 'a', 'b') assert obj.mix(3, 4) == (0, 1, 3, 4) - raises(TypeError, obj.meth, 123) + pytest.raises(TypeError, obj.meth, 123) assert format_calls(replay._actual) == format_calls( OrderedDict( [ - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "1, 2", ''), _Binds('stuff_1')), - (('stuff_1', 'mix', "3, 4", ''), _Returns("(1, 2, 3, 4)")), + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '1, 2', ''), _Binds('stuff_1')), + (('stuff_1', 'mix', '3, 4', ''), _Returns('(1, 2, 3, 4)')), (('stuff_1', 'mix', "'a', 'b'", ''), _Returns("(1, 2, 'a', 'b')")), ( - ('stuff_1', 'meth', "123", ''), + ('stuff_1', 'meth', '123', ''), _Raises( repr_ex( TypeError( @@ -425,11 +425,11 @@ def test_story_empty_play_proxy_class(): ) ), ), - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', "0, 1", ''), _Binds('stuff_2')), + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '0, 1', ''), _Binds('stuff_2')), (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), - (('stuff_2', 'mix', "3, 4", ''), _Returns("(0, 1, 3, 4)")), + (('stuff_2', 'mix', '3, 4', ''), _Returns('(0, 1, 3, 4)')), ( - ('stuff_2', 'meth', "123", ''), + ('stuff_2', 'meth', '123', ''), _Raises( repr_ex( TypeError( @@ -450,20 +450,20 @@ def test_story_half_play_proxy_class(): with Story(test_mod) as story: obj = test_mod.Stuff(1, 2) - obj.mix(3, 4) == (1, 2, 3, 4) + obj.mix(3, 4) == (1, 2, 3, 4) # noqa: B015 with story.replay(recurse_lock=True, proxy=True, strict=False) as replay: obj = test_mod.Stuff(1, 2) assert obj.mix(3, 4) == (1, 2, 3, 4) assert obj.meth() is None - raises(TypeError, obj.meth, 123) + pytest.raises(TypeError, obj.meth, 123) obj = test_mod.Stuff(0, 1) assert obj.mix('a', 'b') == (0, 1, 'a', 'b') assert obj.mix(3, 4) == (0, 1, 3, 4) - raises(TypeError, obj.meth, 123) + pytest.raises(TypeError, obj.meth, 123) assert replay.unexpected == format_calls( OrderedDict( [ @@ -480,7 +480,7 @@ def test_story_half_play_proxy_class(): ) ), ), - ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '0, 1', ''), _Binds("stuff_2")), + ((None, 'test_pkg1.test_pkg2.test_mod.Stuff', '0, 1', ''), _Binds('stuff_2')), (('stuff_2', 'mix', "'a', 'b'", ''), _Returns("(0, 1, 'a', 'b')")), (('stuff_2', 'mix', '3, 4', ''), _Returns('(0, 1, 3, 4)')), ( @@ -502,45 +502,45 @@ def test_story_half_play_proxy_class(): def test_story_full_play_noproxy(): with Story(test_mod) as story: - test_mod.target(123) == 'foobar' + test_mod.target(123) == 'foobar' # noqa: B015 test_mod.target(1234) ** ValueError with story.replay(recurse_lock=True, proxy=False, strict=False, dump=False) as replay: - raises(AssertionError, test_mod.target) + pytest.raises(AssertionError, test_mod.target) assert test_mod.target(123) == 'foobar' - raises(ValueError, test_mod.target, 1234) + pytest.raises(ValueError, test_mod.target, 1234) # noqa: PT011 - assert replay.unexpected == "" + assert replay.unexpected == '' def test_story_full_play_noproxy_dump(): with Story(test_mod) as story: - test_mod.target(123) == 'foobar' + test_mod.target(123) == 'foobar' # noqa: B015 test_mod.target(1234) ** ValueError with story.replay(recurse_lock=True, proxy=False, strict=False, dump=True) as replay: - raises(AssertionError, test_mod.target) + pytest.raises(AssertionError, test_mod.target) assert test_mod.target(123) == 'foobar' - raises(ValueError, test_mod.target, 1234) + pytest.raises(ValueError, test_mod.target, 1234) # noqa: PT011 - assert replay.unexpected == "" + assert replay.unexpected == '' def test_story_full_play_proxy(): with Story(test_mod) as story: - test_mod.target(123) == 'foobar' + test_mod.target(123) == 'foobar' # noqa: B015 test_mod.target(1234) ** ValueError with story.replay(recurse_lock=True, proxy=True, strict=False) as replay: assert test_mod.target() is None assert test_mod.target(123) == 'foobar' - raises(ValueError, test_mod.target, 1234) - raises(TypeError, test_mod.target, 'asdf') + pytest.raises(ValueError, test_mod.target, 1234) # noqa: PT011 + pytest.raises(TypeError, test_mod.target, 'asdf') assert replay.unexpected == format_calls( OrderedDict( [ - ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns("None")), + ((None, 'test_pkg1.test_pkg2.test_mod.target', '', ''), _Returns('None')), ( (None, 'test_pkg1.test_pkg2.test_mod.target', "'asdf'", ''), _Raises( @@ -558,39 +558,39 @@ def test_story_full_play_proxy(): def test_story_result_wrapper(): x = StoryResultWrapper(lambda *a: None) - raises(AttributeError, setattr, x, 'stuff', 1) - raises(AttributeError, getattr, x, 'stuff') - raises(TypeError, lambda: x >> 2) - raises(TypeError, lambda: x << 1) - raises(TypeError, lambda: x > 1) - x == 1 + pytest.raises(AttributeError, setattr, x, 'stuff', 1) + pytest.raises(AttributeError, getattr, x, 'stuff') + pytest.raises(TypeError, lambda: x >> 2) + pytest.raises(TypeError, lambda: x << 1) + pytest.raises(TypeError, lambda: x > 1) + x == 1 # noqa: B015 x ** Exception() def test_story_result_wrapper_bad_exception(): x = StoryResultWrapper(lambda *a: None) - raises(RuntimeError, lambda: x**1) + pytest.raises(RuntimeError, lambda: x**1) x**Exception x ** Exception('boom!') def test_story_create(): with Story(test_mod) as story: - test_mod.target('a', 'b', 'c') == 'abc' + test_mod.target('a', 'b', 'c') == 'abc' # noqa: B015 test_mod.target() ** Exception - test_mod.target(1, 2, 3) == 'foobar' + test_mod.target(1, 2, 3) == 'foobar' # noqa: B015 obj = test_mod.Stuff('stuff') assert isinstance(obj, test_mod.Stuff) - obj.meth('other', 1, 2) == 123 - obj.mix('other') == 'mixymix' + obj.meth('other', 1, 2) == 123 # noqa: B015 + obj.mix('other') == 'mixymix' # noqa: B015 # from pprint import pprint as print # print (dict(story._calls)) assert dict(story._calls) == { (None, 'test_pkg1.test_pkg2.test_mod.Stuff', "'stuff'", ''): _Binds('stuff_1'), - ('stuff_1', 'meth', "'other', 1, 2", ''): _Returns("123"), + ('stuff_1', 'meth', "'other', 1, 2", ''): _Returns('123'), ('stuff_1', 'mix', "'other'", ''): _Returns("'mixymix'"), - (None, 'test_pkg1.test_pkg2.test_mod.target', '', ''): _Raises("Exception"), - (None, 'test_pkg1.test_pkg2.test_mod.target', "1, 2, 3", ''): _Returns("'foobar'"), + (None, 'test_pkg1.test_pkg2.test_mod.target', '', ''): _Raises('Exception'), + (None, 'test_pkg1.test_pkg2.test_mod.target', '1, 2, 3', ''): _Returns("'foobar'"), (None, 'test_pkg1.test_pkg2.test_mod.target', "'a', 'b', 'c'", ''): _Returns("'abc'"), } @@ -599,7 +599,7 @@ def xtest_story_empty_play_proxy_class_dependencies(): with Story(test_mod).replay(recurse_lock=True, proxy=True, strict=False) as replay: obj = test_mod.Stuff(1, 2) other = obj.other('x') - raises(ValueError, other.raises, 'badarg') + pytest.raises(ValueError, other.raises, 'badarg') # noqa: PT011 other.mix(3, 4) obj = test_mod.Stuff(0, 1) obj.mix(3, 4) @@ -608,4 +608,4 @@ def xtest_story_empty_play_proxy_class_dependencies(): print(repr(replay.diff)) - assert replay.diff == "" + assert replay.diff == '' diff --git a/tests/test_contrib.py b/tests/test_contrib.py index 5eb0373..3cc799b 100644 --- a/tests/test_contrib.py +++ b/tests/test_contrib.py @@ -32,11 +32,11 @@ def test_defaults(): def test_raises(): calls = [] - pytest.raises(OSError, retry(sleep=calls.append)(flaky_func), [None] * 6) + pytest.raises(OSError, retry(sleep=calls.append)(flaky_func), [None] * 6) # noqa: PT011 assert calls == [0, 0, 0, 0, 0] calls = [] - pytest.raises(OSError, retry(sleep=calls.append, retries=1)(flaky_func), [None, None]) + pytest.raises(OSError, retry(sleep=calls.append, retries=1)(flaky_func), [None, None]) # noqa: PT011 assert calls == [0] @@ -70,7 +70,7 @@ def test_backoff_flat(): def test_with_class(): logger = getLogger(__name__) - class Connection(object): + class Connection: count = 0 @retry @@ -81,24 +81,24 @@ def __init__(self, address): def __connect(self, *_, **__): self.count += 1 if self.count % 3: - raise OSError("Failed") + raise OSError('Failed') else: - logger.info("connected!") + logger.info('connected!') @retry(cleanup=__connect) def action(self, arg1, arg2): self.count += 1 if self.count % 3 == 0: - raise OSError("Failed") + raise OSError('Failed') else: - logger.info("action!") + logger.info('action!') def __repr__(self): - return "Connection@%s" % self.count + return f'Connection@{self.count}' with LogCapture([logger, contrib.logger]) as logcap: try: - conn = Connection("to-something") + conn = Connection('to-something') for i in range(5): conn.action(i, i) finally: diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 08f6e13..7a8fc0b 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -28,9 +28,9 @@ def test_mock_builtin(): with aspectlib.weave(open, mock('foobar')): - assert open('???') == 'foobar' + assert open('???') == 'foobar' # noqa: PTH123 - assert open(__file__) != 'foobar' + assert open(__file__) != 'foobar' # noqa: PTH123 def test_mock_builtin_os(): @@ -43,11 +43,11 @@ def test_mock_builtin_os(): def test_record_warning(): with aspectlib.weave('warnings.warn', record): - warnings.warn('crap') + warnings.warn('crap', stacklevel=1) assert warnings.warn.calls == [(None, ('crap',), {})] -@pytest.mark.skipif(not hasattr(os, 'fork'), reason="os.fork not available") +@pytest.mark.skipif(not hasattr(os, 'fork'), reason='os.fork not available') def test_fork(): with aspectlib.weave('os.fork', mock('foobar')): pid = os.fork() @@ -67,7 +67,7 @@ def test_socket(target=socket.socket): s = socket.socket() try: s.connect(('127.0.0.1', 1)) - except Exception: + except Exception: # noqa: S110 pass print(buf.getvalue()) @@ -76,7 +76,7 @@ def test_socket(target=socket.socket): s = socket.socket() try: s.connect(('127.0.0.1', 1)) - except Exception: + except Exception: # noqa: S110 pass assert re.match(LOG_TEST_SOCKET, buf.getvalue()) @@ -108,10 +108,10 @@ def test_socket_all_methods(): with aspectlib.weave(socket.socket, aspectlib.debug.log(print_to=buf, stacktrace=False), lazy=True, methods=aspectlib.ALL_METHODS): socket.socket() - assert "}.__init__ => None" in buf.getvalue() + assert '}.__init__ => None' in buf.getvalue() -@pytest.mark.skipif(not hasattr(os, 'fork') or PYPY, reason="os.fork not available or PYPY") +@pytest.mark.skipif(not hasattr(os, 'fork') or PYPY, reason='os.fork not available or PYPY') def test_realsocket_makefile(): buf = StringIO() p = socket.socket() @@ -131,21 +131,21 @@ def test_realsocket_makefile(): s.settimeout(1) s.connect(p.getsockname()) fh = s.makefile('rwb', buffering=0) - fh.write(b"STUFF\n") + fh.write(b'STUFF\n') fh.readline() with dump_on_error(buf.getvalue): wait_for_strings( buf.getvalue, 0, - "}.connect", - "}.makefile", - "}.write(", - "}.send", - "}.write =>", - "}.readline()", - "}.recv", - "}.readline => ", + '}.connect', + '}.makefile', + '}.write(', + '}.send', + '}.write =>', + '}.readline()', + '}.recv', + '}.readline => ', ) else: try: @@ -161,7 +161,7 @@ def test_realsocket_makefile(): def test_weave_os_module(): calls = [] - with aspectlib.weave('os', record(calls=calls, extended=True), methods="getenv|walk"): + with aspectlib.weave('os', record(calls=calls, extended=True), methods='getenv|walk'): os.getenv('BUBU', 'bubu') os.walk('.') @@ -174,13 +174,13 @@ def test_decorate_asyncio_coroutine(): @debug.log(print_to=buf, module=False, stacktrace=2, result_repr=repr) async def coro(): await asyncio.sleep(0.01) - return "result" + return 'result' loop = asyncio.new_event_loop() loop.run_until_complete(coro()) output = buf.getvalue() print(output) - assert 'coro => %r' % 'result' in output + assert 'coro => {!r}'.format('result') in output def test_decorate_tornado_coroutine(): @@ -193,7 +193,7 @@ def coro(): yield gen.Task(loop.add_timeout, timedelta(microseconds=10)) else: yield gen.sleep(0.01) - return "result" + return 'result' asyncio_loop = asyncio.new_event_loop() try: @@ -204,4 +204,4 @@ def coro(): finally: asyncio.get_event_loop = get_event_loop output = buf.getvalue() - assert 'coro => %r' % 'result' in output + assert 'coro => {!r}'.format('result') in output diff --git a/tests/test_pkg1/test_pkg2/test_mod.py b/tests/test_pkg1/test_pkg2/test_mod.py index 729259a..4586d92 100644 --- a/tests/test_pkg1/test_pkg2/test_mod.py +++ b/tests/test_pkg1/test_pkg2/test_mod.py @@ -13,7 +13,7 @@ def raises(*a): a = 1 -class Stuff(object): +class Stuff: def __init__(self, *args): if args == ('error',): raise ValueError diff --git a/tests/test_pytestsupport.py b/tests/test_pytestsupport.py index c1bb6ad..ed3344c 100644 --- a/tests/test_pytestsupport.py +++ b/tests/test_pytestsupport.py @@ -1,7 +1,7 @@ from aspectlib import test -class Foo(object): +class Foo: def bar(self): return 1 From b85abdb0565d1598ce56bd49d49dc709d4e16081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:36:13 +0300 Subject: [PATCH 16/20] Fix some cleanup regressions. --- tests/conftest.py | 7 ++++--- tests/test_aspectlib.py | 2 +- tests/test_integrations.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ee54d9d..29caa50 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ -def pytest_ignore_collect(path, config): - basename = path.basename +from pathlib import Path - if 'pytestsupport' in basename: + +def pytest_ignore_collect(collection_path: Path, config): + if 'pytestsupport' in collection_path.__fspath__(): return True diff --git a/tests/test_aspectlib.py b/tests/test_aspectlib.py index ade88b4..1c924f4 100644 --- a/tests/test_aspectlib.py +++ b/tests/test_aspectlib.py @@ -447,7 +447,7 @@ def test_weave_wrong_module(): "Setting test_aspectlib.MissingGlobal to . " 'There was no previous definition, probably patching the wrong module.', ), - {}, + {'stacklevel': 2}, ) ] diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 7a8fc0b..7a7e143 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -44,7 +44,7 @@ def test_mock_builtin_os(): def test_record_warning(): with aspectlib.weave('warnings.warn', record): warnings.warn('crap', stacklevel=1) - assert warnings.warn.calls == [(None, ('crap',), {})] + assert warnings.warn.calls == [(None, ('crap',), {'stacklevel': 1})] @pytest.mark.skipif(not hasattr(os, 'fork'), reason='os.fork not available') From 2516ffe0110d93ac228f2fe15be74f905984f071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:41:11 +0300 Subject: [PATCH 17/20] Ignore throw 3-arg deprecation. --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index de53382..118418c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -32,6 +32,7 @@ testpaths = filterwarnings = error ignore:Setting test_aspectlib.MissingGlobal to . There was no previous definition, probably patching the wrong module. + ignore:the \(type, exc, tb\) signature of throw\(\) is deprecated, use the single-arg signature instead.:DeprecationWarning # You can add exclusions, some examples: # ignore:'aspectlib' defines default_app_config:PendingDeprecationWarning:: # ignore:The {{% if::: From 8791387db7c310cd7a957bd7f86719c6485f37f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:42:49 +0300 Subject: [PATCH 18/20] Add a tidelift security policy. --- SECURITY.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 SECURITY.rst diff --git a/SECURITY.rst b/SECURITY.rst new file mode 100644 index 0000000..41127bf --- /dev/null +++ b/SECURITY.rst @@ -0,0 +1,5 @@ +Security contact information +============================ + +To report a security vulnerability, please use the `Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. From 671b0138d57a5c92ccbb56e22142278caf35d97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:45:15 +0300 Subject: [PATCH 19/20] Add a tidelift security policy. --- SECURITY.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..da9c516 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. From 52092d5e8ee86705c0cad9f9b510dcedabbdc46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 12 Aug 2024 18:47:32 +0300 Subject: [PATCH 20/20] Wrong extension/format apparently. --- SECURITY.rst | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 SECURITY.rst diff --git a/SECURITY.rst b/SECURITY.rst deleted file mode 100644 index 41127bf..0000000 --- a/SECURITY.rst +++ /dev/null @@ -1,5 +0,0 @@ -Security contact information -============================ - -To report a security vulnerability, please use the `Tidelift security contact `_. -Tidelift will coordinate the fix and disclosure.