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..0e3a2cd 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, --unsafe-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/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. 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/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::: 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/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/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/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..1c924f4 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,9 +445,9 @@ 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.', ), - {}, + {'stacklevel': 2}, ) ] @@ -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..7a7e143 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') - assert warnings.warn.calls == [(None, ('crap',), {})] + warnings.warn('crap', stacklevel=1) + assert warnings.warn.calls == [(None, ('crap',), {'stacklevel': 1})] -@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 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