From 178781d71c0d1098193a1f70dc0fc1be7f060b09 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 Jan 2025 10:01:17 -1000 Subject: [PATCH] Initial version --- .codecov.yml | 48 ++ .coveragerc | 36 ++ .github/FUNDING.yml | 7 + .github/ISSUE_TEMPLATE/bug_report.yml | 134 +++++ .github/ISSUE_TEMPLATE/config.yml | 37 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 62 +++ .github/dependabot.yml | 23 + .github/workflows/auto-merge.yml | 22 + .github/workflows/ci-cd.yml | 128 +++++ .github/workflows/codeql.yml | 41 ++ .gitignore | 106 +--- .mypy.ini | 46 ++ .pre-commit-config.yaml | 191 ++++++++ .readthedocs.yaml | 25 + .yamllint | 18 + CHANGES.rst | 138 ++++++ CHANGES/.TEMPLATE.rst | 90 ++++ CHANGES/.gitignore | 28 ++ CHANGES/README.rst | 109 +++++ LICENSE | 1 + MANIFEST.in | 21 + Makefile | 63 +++ NOTICE | 13 + README.rst | 98 ++++ docs/Makefile | 230 +++++++++ docs/_static/README.txt | 0 docs/api.rst | 56 +++ docs/changes.rst | 18 + docs/conf.py | 457 ++++++++++++++++++ docs/contributing/guidelines.rst | 28 ++ docs/contributing/release_guide.rst | 105 ++++ docs/index.rst | 83 ++++ docs/make.bat | 281 +++++++++++ docs/spelling_wordlist.txt | 52 ++ overlay.tar.gz | Bin 0 -> 141468 bytes packaging/README.md | 11 + packaging/pep517_backend/__init__.py | 1 + packaging/pep517_backend/__main__.py | 6 + packaging/pep517_backend/_backend.py | 391 +++++++++++++++ packaging/pep517_backend/_compat.py | 33 ++ .../pep517_backend/_cython_configuration.py | 107 ++++ packaging/pep517_backend/_transformers.py | 107 ++++ packaging/pep517_backend/cli.py | 53 ++ packaging/pep517_backend/hooks.py | 21 + pyproject.toml | 96 ++++ pytest.ini | 89 ++++ requirements/dev.txt | 2 + requirements/doc-spelling.txt | 2 + requirements/doc.txt | 4 + requirements/lint.txt | 1 + requirements/test.txt | 6 + requirements/towncrier.txt | 1 + setup.cfg | 96 ++++ src/aiohttp_asyncmdnsresolver/__init__.py | 32 ++ src/aiohttp_asyncmdnsresolver/_helpers.py | 39 ++ src/aiohttp_asyncmdnsresolver/_helpers_c.pyx | 84 ++++ src/aiohttp_asyncmdnsresolver/_helpers_py.py | 56 +++ src/aiohttp_asyncmdnsresolver/api.py | 8 + src/aiohttp_asyncmdnsresolver/py.typed | 1 + tests/conftest.py | 115 +++++ tests/test_api.py | 11 + tests/test_benchmarks.py | 87 ++++ tests/test_cached_property.py | 145 ++++++ tests/test_init.py | 43 ++ tests/test_under_cached_property.py | 129 +++++ towncrier.toml | 68 +++ 66 files changed, 4555 insertions(+), 85 deletions(-) create mode 100644 .codecov.yml create mode 100644 .coveragerc create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/auto-merge.yml create mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .mypy.ini create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 .yamllint create mode 100644 CHANGES.rst create mode 100644 CHANGES/.TEMPLATE.rst create mode 100644 CHANGES/.gitignore create mode 100644 CHANGES/README.rst create mode 100644 MANIFEST.in create mode 100644 Makefile create mode 100644 NOTICE create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/_static/README.txt create mode 100644 docs/api.rst create mode 100644 docs/changes.rst create mode 100644 docs/conf.py create mode 100644 docs/contributing/guidelines.rst create mode 100644 docs/contributing/release_guide.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/spelling_wordlist.txt create mode 100644 overlay.tar.gz create mode 100644 packaging/README.md create mode 100644 packaging/pep517_backend/__init__.py create mode 100644 packaging/pep517_backend/__main__.py create mode 100644 packaging/pep517_backend/_backend.py create mode 100644 packaging/pep517_backend/_compat.py create mode 100644 packaging/pep517_backend/_cython_configuration.py create mode 100644 packaging/pep517_backend/_transformers.py create mode 100644 packaging/pep517_backend/cli.py create mode 100644 packaging/pep517_backend/hooks.py create mode 100644 pyproject.toml create mode 100644 pytest.ini create mode 100644 requirements/dev.txt create mode 100644 requirements/doc-spelling.txt create mode 100644 requirements/doc.txt create mode 100644 requirements/lint.txt create mode 100644 requirements/test.txt create mode 100644 requirements/towncrier.txt create mode 100644 setup.cfg create mode 100644 src/aiohttp_asyncmdnsresolver/__init__.py create mode 100644 src/aiohttp_asyncmdnsresolver/_helpers.py create mode 100644 src/aiohttp_asyncmdnsresolver/_helpers_c.pyx create mode 100644 src/aiohttp_asyncmdnsresolver/_helpers_py.py create mode 100644 src/aiohttp_asyncmdnsresolver/api.py create mode 100644 src/aiohttp_asyncmdnsresolver/py.typed create mode 100644 tests/conftest.py create mode 100644 tests/test_api.py create mode 100644 tests/test_benchmarks.py create mode 100644 tests/test_cached_property.py create mode 100644 tests/test_init.py create mode 100644 tests/test_under_cached_property.py create mode 100644 towncrier.toml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..0e176b1 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,48 @@ +--- + +codecov: + notify: + after_n_builds: 23 # The number of test matrix+lint jobs uploading coverage + wait_for_ci: false + + require_ci_to_pass: false + + token: >- # notsecret # repo-scoped, upload-only, stability in fork PRs + 0b255cf3-ed51-43a8-b7df-8a9f23da39b1 + +comment: + require_changes: true + +coverage: + range: 99.34..100 + status: + patch: + default: + target: 100% + flags: + - pytest + project: + default: + target: 87.5% # 100% + lib: + flags: + - pytest + paths: + - aiohttp_asyncmdnsresolver/ + target: 100% + packaging: + paths: + - packaging/ + target: 75.24% + tests: + flags: + - pytest + paths: + - tests/ + target: 98.2% # 100% + typing: + flags: + - MyPy + target: 77.5% # 100% + +... diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..a0d0822 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,36 @@ +[html] +show_contexts = true +skip_covered = false + +[paths] +_site-packages-to-src-mapping = + src + */src + *\src + */lib/pypy*/site-packages + */lib/python*/site-packages + *\Lib\site-packages + +[report] +exclude_also = + ^\s*@pytest\.mark\.xfail +# small library, don't fail when running without C-extension +fail_under = 50.00 +skip_covered = true +skip_empty = true +show_missing = true + +[run] +branch = true +cover_pylib = false +# https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts +# dynamic_context = test_function # conflicts with `pytest-cov` if set here +parallel = true +plugins = + covdefaults + Cython.Coverage +relative_files = true +source = + . +source_pkgs = + aiohttp_asyncmdnsresolver diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..09c21c0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,7 @@ +--- +# These are supported funding model platforms + +github: +- asvetlov +- webknjaz +- Dreamsorcerer diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..59e3851 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,134 @@ +--- +name: 🐞 Bug Report +description: Create a report to help us improve. +labels: +- bug +body: +- type: markdown + attributes: + value: | + **Thanks for taking a minute to file a bug report!** + + ⚠ + Verify first that your issue is not [already reported on + GitHub][issue search]. + + _Please fill out the form below with as many precise + details as possible._ + + [issue search]: ../search?q=is%3Aissue&type=issues + +- type: checkboxes + id: terms + attributes: + label: Please confirm the following + description: | + Read the [aio-libs Code of Conduct][CoC] first. Check the existing issues + on the tracker. Take into account the possibility of your report + surfacing a security vulnerability. + + [CoC]: ../../.github/blob/main/CODE_OF_CONDUCT.md + options: + - label: | + I agree to follow the [aio-libs Code of Conduct][CoC] + + [CoC]: ../../.github/blob/main/CODE_OF_CONDUCT.md + required: true + - label: | + I have checked the [current issues][issue search] for duplicates. + + [issue search]: ../search?q=is%3Aissue&type=issues + required: true + - label: >- + I understand this is open source software provided for free and + that I might not receive a timely response. + required: true + - label: | + I am positive I am **NOT** reporting a (potential) security + vulnerability, to the best of my knowledge. *(These must be shared by + submitting [this report form][vulnerability report form] instead, if + any hesitation exists.)* + + [vulnerability report form]: ../security/advisories/new + required: true + - label: >- + I am willing to submit a pull request with reporoducers as xfailing test + cases or even entire fix. *(Assign this issue to me.)* + required: false + +- type: textarea + attributes: + label: Describe the bug + description: >- + A clear and concise description of what the bug is. + validations: + required: true + +- type: textarea + attributes: + label: To Reproduce + description: >- + Describe the steps to reproduce this bug. + placeholder: | + 1. Have certain environment + 2. Run given code snippet in a certain way + 3. See some behavior described + validations: + required: true + +- type: textarea + attributes: + label: Expected behavior + description: >- + A clear and concise description of what you expected to happen. + validations: + required: true + +- type: textarea + attributes: + label: Logs/tracebacks + description: | + If applicable, add logs/tracebacks to help explain your problem. + Paste the output of the steps above, including the commands + themselves and their output/traceback etc. + render: python-traceback + validations: + required: true + +- type: textarea + attributes: + label: Python Version + description: Attach your version of Python. + render: console + value: | + $ python --version + validations: + required: true +- type: textarea + attributes: + label: aiohttp_asyncmdnsresolver Version + description: Attach your version of aiohttp_asyncmdnsresolver. + render: console + value: | + $ python -m pip show aiohttp_asyncmdnsresolver + validations: + required: true + +- type: textarea + attributes: + label: OS + placeholder: >- + For example, Arch Linux, Windows, macOS, etc. + validations: + required: true + +- type: textarea + attributes: + label: Additional context + description: | + Add any other context about the problem here. + + Describe the environment you have that lead to your issue. + This includes proxy server and other bits that are related to your case. + +... diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..095db31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,37 @@ +--- + +# yamllint disable rule:line-length +# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser +# yamllint enable rule:line-length +blank_issues_enabled: false # default: true +contact_links: +- name: 🔐 Security bug report 🔥 + url: https://github.com/aio-libs/.github/security/policy + about: | + Please learn how to report security vulnerabilities here. + + For all security related bugs, send an email + instead of using this issue tracker and you + will receive a prompt response. + + For more information, see + https://github.com/aio-libs/.github/security/policy +- name: >- + [🎉 NEW 🎉] + 🤷💻🤦 GitHub Discussions + url: https://github.com/aio-libs/aiohttp_asyncmdnsresolver/discussions + about: >- + Please ask typical Q&A in the Discussions tab or on StackOverflow +- name: 🤷💻🤦 StackOverflow + url: https://stackoverflow.com/questions/tagged/aiohttp + about: >- + Please ask typical Q&A here or in the + Discussions tab @ https://github.com/aio-libs/aiohttp_asyncmdnsresolver/discussions +- name: 💬 Gitter Chat + url: https://gitter.im/aio-libs/Lobby + about: Chat with devs and community +- name: 📝 Code of Conduct + url: https://github.com/aio-libs/.github/blob/main/CODE_OF_CONDUCT.md + about: ❤ Be nice to other members of the community. ☮ Behave. + +... diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..45d3b73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,62 @@ +--- +name: 🚀 Feature request +description: Suggest an idea for this project. +labels: +- enhancement +body: +- type: markdown + attributes: + value: | + **Thanks for taking a minute to file a feature for aiohttp_asyncmdnsresolver!** + + ⚠ + Verify first that your feature request is not [already reported on + GitHub][issue search]. + + _Please fill out the form below with as many precise + details as possible._ + + [issue search]: ../search?q=is%3Aissue&type=issues + +- type: textarea + attributes: + label: Is your feature request related to a problem? + description: >- + Please add a clear and concise description of what + the problem is. _Ex. I'm always frustrated when [...]_ + +- type: textarea + attributes: + label: Describe the solution you'd like + description: >- + A clear and concise description of what you want to happen. + validations: + required: true + +- type: textarea + attributes: + label: Describe alternatives you've considered + description: >- + A clear and concise description of any alternative solutions + or features you've considered. + validations: + required: true + +- type: textarea + attributes: + label: Additional context + description: >- + Add any other context or screenshots about + the feature request here. + +- type: checkboxes + attributes: + label: Code of Conduct + description: | + Read the [aio-libs Code of Conduct][CoC] first. + + [CoC]: https://github.com/aio-libs/.github/blob/main/CODE_OF_CONDUCT.md + options: + - label: I agree to follow the aio-libs Code of Conduct + required: true +... diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..493e5f0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +--- + +version: 2 +updates: + +# Maintain dependencies for GitHub Actions +- package-ecosystem: "github-actions" + directory: "/" + labels: + - dependencies + schedule: + interval: "daily" + +# Maintain dependencies for Python +- package-ecosystem: "pip" + directory: "/" + labels: + - dependencies + schedule: + interval: "daily" + open-pull-requests-limit: 10 + +... diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000..8e5b142 --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,22 @@ +name: Dependabot auto-merge +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2.2.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..7dc8252 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,128 @@ +name: CI + +on: + push: + branches: + - main + - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 + tags: [ 'v*' ] + pull_request: + branches: + - main + - '[0-9].[0-9]+' + + +jobs: + lint: + name: Linter + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + - name: Install dependencies + uses: py-actions/py-dependency-install@v4 + with: + path: requirements-dev.txt + - name: Install itself + run: | + pip install . + - name: Run linter + run: mypy + - name: Prepare twine checker + run: | + pip install -U build twine wheel + python -m build + - name: Run twine checker + run: | + twine check dist/* + + test: + name: Test + strategy: + matrix: + pyver: ['3.9', '3.10', '3.11', '3.12', '3.13'] + os: [ubuntu, macos, windows] + include: + - pyver: pypy-3.10 + os: ubuntu + runs-on: ${{ matrix.os }}-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python ${{ matrix.pyver }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.pyver }} + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + - name: Install dependencies + uses: py-actions/py-dependency-install@v4 + with: + path: requirements.txt + - name: Run unittests + run: pytest + env: + COLOR: 'yes' + - run: python -m coverage xml + - name: Upload coverage + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: [lint, test] + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + + deploy: + name: Deploy + environment: pypi + runs-on: ubuntu-latest + needs: [check] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + permissions: + contents: write # GitHub Releases + id-token: write # Trusted publishing & sigstore + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install dependencies + run: + python -m pip install -U pip wheel setuptools build twine + - name: Build dists + run: | + python -m build + - name: Make Release + uses: aio-libs/create-release@v1.6.6 + with: + changes_file: CHANGES.rst + name: aiohttp-asyncmdnsresolver + version_file: aiohttp_asyncmdnsresolver/__init__.py + github_token: ${{ secrets.GITHUB_TOKEN }} + dist_dir: dist + fix_issue_regex: "`#(\\d+) `" + fix_issue_repl: "(#\\1)" + - name: >- + Publish 🐍📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..ca7176f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: "33 2 * * 2" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python ] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.gitignore b/.gitignore index 15201ac..9409d1a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python +env/ build/ develop-eggs/ dist/ @@ -19,12 +20,9 @@ lib64/ parts/ sdist/ var/ -wheels/ -share/python-wheels/ *.egg-info/ .installed.cfg *.egg -MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -39,17 +37,13 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ -.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml -*.cover -*.py,cover +*,cover .hypothesis/ -.pytest_cache/ -cover/ # Translations *.mo @@ -58,8 +52,6 @@ cover/ # Django stuff: *.log local_settings.py -db.sqlite3 -db.sqlite3-journal # Flask stuff: instance/ @@ -72,100 +64,44 @@ instance/ docs/_build/ # PyBuilder -.pybuilder/ target/ -# Jupyter Notebook +# IPython Notebook .ipynb_checkpoints -# IPython -profile_default/ -ipython_config.py - # pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid +.python-version -# SageMath parsed files -*.sage.py +# celery beat schedule file +celerybeat-schedule -# Environments +# dotenv .env -.venv -env/ + +# virtualenv +.venv/ venv/ ENV/ -env.bak/ -venv.bak/ # Spyder project settings .spyderproject -.spyproject # Rope project settings .ropeproject -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +coverage -# Pyre type checker -.pyre/ -# pytype static type analyzer -.pytype/ +aiohttp_asyncmdnsresolver/*.c +aiohttp_asyncmdnsresolver/*.html -# Cython debug symbols -cython_debug/ +.develop -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# Idea +.idea -# PyPI configuration file -.pypirc +.mypy_cache +.install-cython +.install-deps +.pytest_cache +pip-wheel-metadata diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..2ab5dab --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,46 @@ +[mypy] +python_version = 3.9 +color_output = true +error_summary = true +files = + packaging/, + tests/, + src/aiohttp_asyncmdnsresolver/ + +# check_untyped_defs = true + +# disallow_untyped_calls = true +# disallow_untyped_defs = true +# disallow_any_generics = true + +enable_error_code = + ignore-without-code + +follow_imports = normal + +ignore_missing_imports = false + +pretty = true + +show_column_numbers = true +show_error_codes = true +strict_optional = true + +warn_no_return = true +warn_redundant_casts = true +warn_unused_ignores = true + +[mypy-Cython.*] +ignore_missing_imports = true + +[mypy-distutils.*] +ignore_missing_imports = true + +[mypy-expandvars] +ignore_missing_imports = true + +[mypy-pytest] +ignore_missing_imports = true + +[mypy-tomllib] +ignore_missing_imports = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f9814ef --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,191 @@ +--- + +ci: + autoupdate_schedule: quarterly + skip: + - actionlint-docker + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: 'v5.0.0' + hooks: + - id: check-merge-conflict +- repo: https://github.com/asottile/yesqa + rev: v1.5.0 + hooks: + - id: yesqa + additional_dependencies: + - wemake-python-styleguide +- repo: https://github.com/PyCQA/isort + rev: '5.13.2' + hooks: + - id: isort +- repo: https://github.com/psf/black + rev: '24.8.0' + hooks: + - id: black + language_version: python3 # Should be a command that runs python + +- repo: https://github.com/python-jsonschema/check-jsonschema.git + rev: 0.29.3 + hooks: + - id: check-github-workflows + files: ^\.github/workflows/[^/]+$ + types: + - yaml + - id: check-jsonschema + alias: check-github-workflows-timeout + name: Check GitHub Workflows set timeout-minutes + args: + - --builtin-schema + - github-workflows-require-timeout + files: ^\.github/workflows/[^/]+$ + types: + - yaml + - id: check-readthedocs + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: 'v5.0.0' + hooks: + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: trailing-whitespace + - id: file-contents-sorter + files: | + docs/spelling_wordlist.txt| + .gitignore| + .gitattributes + - id: check-case-conflict + - id: check-json + - id: check-xml + - id: check-executables-have-shebangs + - id: check-toml + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: check-added-large-files + - id: check-symlinks + - id: debug-statements + - id: detect-aws-credentials + args: ['--allow-missing-credentials'] + - id: detect-private-key + exclude: ^examples/ +- repo: https://github.com/asottile/pyupgrade + rev: 'v3.17.0' + hooks: + - id: pyupgrade + args: ['--py39-plus'] +- repo: https://github.com/PyCQA/flake8 + rev: '7.1.1' + hooks: + - id: flake8 + exclude: "^docs/" + +- repo: https://github.com/codespell-project/codespell.git + rev: v2.3.0 + hooks: + - id: codespell + +- repo: https://github.com/adrienverge/yamllint.git + rev: v1.35.1 + hooks: + - id: yamllint + args: + - --strict + +- repo: https://github.com/MarcoGorelli/cython-lint.git + rev: v0.16.2 + hooks: + - id: cython-lint + +- repo: https://github.com/Lucas-C/pre-commit-hooks-markup + rev: v1.0.1 + hooks: + - id: rst-linter + exclude: ^CHANGES\.rst$ + files: >- + ^[^/]+[.]rst$ + +- repo: https://github.com/pre-commit/mirrors-mypy.git + rev: v1.11.2 + hooks: + - id: mypy + alias: mypy-py313 + name: MyPy, for Python 3.13 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest_codspeed==3.0.0 + - tomli # requirement of packaging/pep517_backend/ + - types-setuptools # requirement of packaging/pep517_backend/ + args: + - --python-version=3.13 + - --txt-report=.tox/.tmp/.mypy/python-3.13 + - --cobertura-xml-report=.tox/.tmp/.mypy/python-3.13 + - --html-report=.tox/.tmp/.mypy/python-3.13 + pass_filenames: false + - id: mypy + alias: mypy-py312 + name: MyPy, for Python 3.12 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest_codspeed==3.0.0 + - tomli # requirement of packaging/pep517_backend/ + - types-setuptools # requirement of packaging/pep517_backend/ + args: + - --python-version=3.12 + - --txt-report=.tox/.tmp/.mypy/python-3.12 + - --cobertura-xml-report=.tox/.tmp/.mypy/python-3.12 + - --html-report=.tox/.tmp/.mypy/python-3.12 + pass_filenames: false + - id: mypy + alias: mypy-py311 + name: MyPy, for Python 3.11 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest_codspeed==3.0.0 + - tomli # requirement of packaging/pep517_backend/ + - types-setuptools # requirement of packaging/pep517_backend/ + - types-Pygments + - types-colorama + args: + - --python-version=3.11 + - --txt-report=.tox/.tmp/.mypy/python-3.11 + - --cobertura-xml-report=.tox/.tmp/.mypy/python-3.11 + - --html-report=.tox/.tmp/.mypy/python-3.11 + pass_filenames: false + - id: mypy + alias: mypy-py39 + name: MyPy, for Python 3.9 + additional_dependencies: + - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` + - pytest + - pytest_codspeed==3.0.0 + - tomli # requirement of packaging/pep517_backend/ + - types-setuptools # requirement of packaging/pep517_backend/ + - types-Pygments + - types-colorama + args: + - --python-version=3.9 + - --txt-report=.tox/.tmp/.mypy/python-3.9 + - --cobertura-xml-report=.tox/.tmp/.mypy/python-3.9 + - --html-report=.tox/.tmp/.mypy/python-3.9 + pass_filenames: false + +- repo: https://github.com/rhysd/actionlint.git + rev: v1.7.3 + hooks: + - id: actionlint-docker + args: + - -ignore + - >- # https://github.com/rhysd/actionlint/issues/384 + ^type of expression at "float number value" must be number + but found type string$ + - -ignore + - >- # https://github.com/rhysd/actionlint/pull/380#issuecomment-2325391372 + ^input "attestations" is not defined in action + "pypa/gh-action-pypi-publish@release/v1". available inputs are ".*"$ + +... diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..d3c25a7 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,25 @@ +--- + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + + jobs: + post_create_environment: + - >- + pip install . + --config-settings=pure-python=true + +python: + install: + - requirements: requirements/doc.txt + +sphinx: + builder: dirhtml + configuration: docs/conf.py + fail_on_warning: true + +... diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..82cb77f --- /dev/null +++ b/.yamllint @@ -0,0 +1,18 @@ +--- + +extends: default + +rules: + indentation: + level: error + indent-sequences: false + truthy: + allowed-values: + - >- + false + - >- + true + - >- # Allow "on" key name in GHA CI/CD workflow definitions + on + +... diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..ca47f00 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,138 @@ +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/#adding-a-news-entry + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +0.2.1 +===== + +*(2024-12-01)* + + +Bug fixes +--------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by :user:`ajsanchezsanz` and + :user:`markgreene74`. + + *Related commits on GitHub:* + :commit:`64df0a6`. + +- Fixed ``wrapped`` and ``func`` not being accessible in the Cython versions of :func:`aiohttp_asyncmdnsresolver.api.cached_property` and :func:`aiohttp_asyncmdnsresolver.api.under_cached_property` decorators -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`72`. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support for Python 3.8 as it has reached end of life -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`57`. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by :user:`ajsanchezsanz` and + :user:`markgreene74`. + + *Related commits on GitHub:* + :commit:`64df0a6`. + + +---- + + +0.2.0 +===== + +*(2024-10-07)* + + +Bug fixes +--------- + +- Fixed loading the C-extensions on Python 3.8 -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`26`. + + +Features +-------- + +- Improved typing for the :func:`aiohttp_asyncmdnsresolver.api.under_cached_property` decorator -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`38`. + + +Improved documentation +---------------------- + +- Added API documentation for the :func:`aiohttp_asyncmdnsresolver.api.cached_property` and :func:`aiohttp_asyncmdnsresolver.api.under_cached_property` decorators -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`16`. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Moved :func:`aiohttp_asyncmdnsresolver.api.under_cached_property` and :func:`aiohttp_asyncmdnsresolver.api.cached_property` to `aiohttp_asyncmdnsresolver.api` -- by :user:`bdraco`. + + Both decorators remain importable from the top-level package, however importing from `aiohttp_asyncmdnsresolver.api` is now the recommended way to use them. + + *Related issues and pull requests on GitHub:* + :issue:`19`, :issue:`24`, :issue:`32`. + +- Converted project to use a src layout -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`22`, :issue:`29`, :issue:`37`. + + +---- + + +0.1.0 +===== + +*(2024-10-03)* + + +Features +-------- + +- Added ``armv7l`` wheels -- by :user:`bdraco`. + + *Related issues and pull requests on GitHub:* + :issue:`5`. + + +---- + + +0.0.0 +===== + +*(2024-10-02)* + + +- Initial release. diff --git a/CHANGES/.TEMPLATE.rst b/CHANGES/.TEMPLATE.rst new file mode 100644 index 0000000..2879ab2 --- /dev/null +++ b/CHANGES/.TEMPLATE.rst @@ -0,0 +1,90 @@ +{# TOWNCRIER TEMPLATE #} + +*({{ versiondata.date }})* + +{% for section, _ in sections.items() %} +{% set underline = underlines[0] %}{% if section %}{{section}} +{{ underline * section|length }}{% set underline = underlines[1] %} + +{% endif %} + +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section]%} +{{ definitions[category]['name'] }} +{{ underline * definitions[category]['name']|length }} + +{% if definitions[category]['showcontent'] %} +{% for text, change_note_refs in sections[section][category].items() %} +- {{ text }} + + {{- '\n' * 2 -}} + + {#- + NOTE: Replacing 'e' with 'f' is a hack that prevents Jinja's `int` + NOTE: filter internal implementation from treating the input as an + NOTE: infinite float when it looks like a scientific notation (with a + NOTE: single 'e' char in between digits), raising an `OverflowError`, + NOTE: subsequently. 'f' is still a hex letter so it won't affect the + NOTE: check for whether it's a (short or long) commit hash or not. + Ref: https://github.com/pallets/jinja/issues/1921 + -#} + {%- + set pr_issue_numbers = change_note_refs + | map('lower') + | map('replace', 'e', 'f') + | map('int', default=None) + | select('integer') + | map('string') + | list + -%} + {%- set arbitrary_refs = [] -%} + {%- set commit_refs = [] -%} + {%- with -%} + {%- set commit_ref_candidates = change_note_refs | reject('in', pr_issue_numbers) -%} + {%- for cf in commit_ref_candidates -%} + {%- if cf | length in (7, 8, 40) and cf | int(default=None, base=16) is not none -%} + {%- set _ = commit_refs.append(cf) -%} + {%- else -%} + {%- set _ = arbitrary_refs.append(cf) -%} + {%- endif -%} + {%- endfor -%} + {%- endwith -%} + + {% if pr_issue_numbers %} + *Related issues and pull requests on GitHub:* + :issue:`{{ pr_issue_numbers | join('`, :issue:`') }}`. + {{- '\n' * 2 -}} + {%- endif -%} + + {% if commit_refs %} + *Related commits on GitHub:* + :commit:`{{ commit_refs | join('`, :commit:`') }}`. + {{- '\n' * 2 -}} + {%- endif -%} + + {% if arbitrary_refs %} + *Unlinked references:* + {{ arbitrary_refs | join(', ') }}. + {{- '\n' * 2 -}} + {%- endif -%} + +{% endfor %} +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} +{% endfor %} +---- +{{ '\n' * 2 }} diff --git a/CHANGES/.gitignore b/CHANGES/.gitignore new file mode 100644 index 0000000..d6409a0 --- /dev/null +++ b/CHANGES/.gitignore @@ -0,0 +1,28 @@ +* +!.TEMPLATE.rst +!.gitignore +!README.rst +!*.bugfix +!*.bugfix.rst +!*.bugfix.*.rst +!*.breaking +!*.breaking.rst +!*.breaking.*.rst +!*.contrib +!*.contrib.rst +!*.contrib.*.rst +!*.deprecation +!*.deprecation.rst +!*.deprecation.*.rst +!*.doc +!*.doc.rst +!*.doc.*.rst +!*.feature +!*.feature.rst +!*.feature.*.rst +!*.misc +!*.misc.rst +!*.misc.*.rst +!*.packaging +!*.packaging.rst +!*.packaging.*.rst diff --git a/CHANGES/README.rst b/CHANGES/README.rst new file mode 100644 index 0000000..ea84227 --- /dev/null +++ b/CHANGES/README.rst @@ -0,0 +1,109 @@ +.. _Adding change notes with your PRs: + +Adding change notes with your PRs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It is very important to maintain a log for news of how +updating to the new version of the software will affect +end-users. This is why we enforce collection of the change +fragment files in pull requests as per `Towncrier philosophy`_. + +The idea is that when somebody makes a change, they must record +the bits that would affect end-users only including information +that would be useful to them. Then, when the maintainers publish +a new release, they'll automatically use these records to compose +a change log for the respective version. It is important to +understand that including unnecessary low-level implementation +related details generates noise that is not particularly useful +to the end-users most of the time. And so such details should be +recorded in the Git history rather than a changelog. + +Alright! So how to add a news fragment? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``aiohttp_asyncmdnsresolver`` uses `towncrier `_ +for changelog management. +To submit a change note about your PR, add a text file into the +``CHANGES/`` folder. It should contain an +explanation of what applying this PR will change in the way +end-users interact with the project. One sentence is usually +enough but feel free to add as many details as you feel necessary +for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, +combined with others, it will be a part of the "news digest" +telling the readers **what changed** in a specific version of +the library *since the previous version*. You should also use +reStructuredText syntax for highlighting code (inline or block), +linking parts of the docs or external sites. +If you wish to sign your change, feel free to add ``-- by +:user:`github-username``` at the end (replace ``github-username`` +with your own!). + +Finally, name your file following the convention that Towncrier +understands: it should start with the number of an issue or a +PR followed by a dot, then add a patch type, like ``feature``, +``doc``, ``contrib`` etc., and add ``.rst`` as a suffix. If you +need to add more than one fragment, you may add an optional +sequence number (delimited with another period) between the type +and the suffix. + +In general the name will follow ``..rst`` pattern, +where the categories are: + +- ``bugfix``: A bug fix for something we deemed an improper undesired + behavior that got corrected in the release to match pre-agreed + expectations. +- ``feature``: A new behavior, public APIs. That sort of stuff. +- ``deprecation``: A declaration of future API removals and breaking + changes in behavior. +- ``breaking``: When something public gets removed in a breaking way. + Could be deprecated in an earlier release. +- ``doc``: Notable updates to the documentation structure or build + process. +- ``packaging``: Notes for downstreams about unobvious side effects + and tooling. Changes in the test invocation considerations and + runtime assumptions. +- ``contrib``: Stuff that affects the contributor experience. e.g. + Running tests, building the docs, setting up the development + environment. +- ``misc``: Changes that are hard to assign to any of the above + categories. + +A pull request may have more than one of these components, for example +a code change may introduce a new feature that deprecates an old +feature, in which case two fragments should be added. It is not +necessary to make a separate documentation fragment for documentation +changes accompanying the relevant code changes. + +Examples for adding changelog entries to your Pull Requests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +File :file:`CHANGES/603.removal.1.rst`: + +.. code-block:: rst + + Dropped Python 3.5 support; Python 3.6 is the minimal supported Python + version -- by :user:`webknjaz`. + +File :file:`CHANGES/550.bugfix.rst`: + +.. code-block:: rst + + Started shipping Windows wheels for the x86 architecture + -- by :user:`Dreamsorcerer`. + +File :file:`CHANGES/553.feature.rst`: + +.. code-block:: rst + + Added support for ``GenericAliases`` (``MultiDict[str]``) under Python 3.9 + and higher -- by :user:`mjpieters`. + +.. tip:: + + See :file:`towncrier.toml` for all available categories + (``tool.towncrier.type``). + +.. _Towncrier philosophy: + https://towncrier.readthedocs.io/en/stable/#philosophy diff --git a/LICENSE b/LICENSE index 261eeb9..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..3060683 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,21 @@ +include .coveragerc +include pyproject.toml +include pytest.ini +include towncrier.toml +include LICENSE +include NOTICE +include CHANGES.rst +include README.rst +graft aiohttp_asyncmdnsresolver +graft packaging +graft docs +graft CHANGES +graft requirements +graft tests +global-exclude *.pyc +global-exclude *.cache +exclude src/aiohttp_asyncmdnsresolver/*.c +exclude src/aiohttp_asyncmdnsresolver/*.html +exclude src/aiohttp_asyncmdnsresolver/*.so +exclude src/aiohttp_asyncmdnsresolver/*.pyd +prune docs/_build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1cd737e --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +PYXS = $(wildcard src/aiohttp_asyncmdnsresolver/*.pyx) + +all: test + + +.install-deps: $(shell find requirements -type f) + pip install -U -r requirements/dev.txt + pre-commit install + @touch .install-deps + + +.install-cython: requirements/cython.txt + pip install -r requirements/cython.txt + touch .install-cython + + +src/aiohttp_asyncmdnsresolver/%.c: src/aiohttp_asyncmdnsresolver/%.pyx + python -m cython -3 -o $@ $< -I src/aiohttp_asyncmdnsresolver + + +.cythonize: .install-cython $(PYXS:.pyx=.c) + + +cythonize: .cythonize + + +.develop: .install-deps $(shell find src/aiohttp_asyncmdnsresolver -type f) + @pip install -e . + @touch .develop + +fmt: +ifdef CI + pre-commit run --all-files --show-diff-on-failure +else + pre-commit run --all-files +endif + +lint: fmt + +test: lint .develop + pytest + + +vtest: lint .develop + pytest -v + + +cov: lint .develop + pytest --cov-report html --cov-report term + @echo "python3 -Im webbrowser file://`pwd`/htmlcov/index.html" + + +doc: doctest doc-spelling + make -C docs html SPHINXOPTS="-W -E --keep-going -n" + @echo "python3 -Im webbrowser file://`pwd`/docs/_build/html/index.html" + + +doctest: .develop + make -C docs doctest SPHINXOPTS="-W -E --keep-going -n" + + +doc-spelling: + make -C docs spelling SPHINXOPTS="-W -E --keep-going -n" diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..fa53b2b --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ + Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..494b0ef --- /dev/null +++ b/README.rst @@ -0,0 +1,98 @@ +aiohttp_asyncmdnsresolver +========= + +The module provides a fast implementation of cached properties for Python 3.9+. + +.. image:: https://github.com/aio-libs/aiohttp_asyncmdnsresolver/actions/workflows/ci-cd.yml/badge.svg + :target: https://github.com/aio-libs/aiohttp_asyncmdnsresolver/actions?query=workflow%3ACI + :align: right + +.. image:: https://codecov.io/gh/aio-libs/aiohttp_asyncmdnsresolver/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/aiohttp_asyncmdnsresolver + +.. image:: https://badge.fury.io/py/aiohttp_asyncmdnsresolver.svg + :target: https://badge.fury.io/py/aiohttp_asyncmdnsresolver + + +.. image:: https://readthedocs.org/projects/aiohttp_asyncmdnsresolver/badge/?version=latest + :target: https://aiohttp_asyncmdnsresolver.readthedocs.io + + +.. image:: https://img.shields.io/pypi/pyversions/aiohttp_asyncmdnsresolver.svg + :target: https://pypi.python.org/pypi/aiohttp_asyncmdnsresolver + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + +Introduction +------------ + +The API is designed to be nearly identical to the built-in ``functools.cached_property`` class, +except for the additional ``under_cached_property`` class which uses ``self._cache`` +instead of ``self.__dict__`` to store the cached values and prevents ``__set__`` from being called. + +For full documentation please read https://aiohttp_asyncmdnsresolver.readthedocs.io. + +Installation +------------ + +:: + + $ pip install aiohttp_asyncmdnsresolver + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``aiohttp_asyncmdnsresolver`` on another operating system where wheels are not provided, +the the tarball will be used to compile the library from +the source code. It requires a C compiler and and Python headers installed. + +To skip the compilation you must explicitly opt-in by using a PEP 517 +configuration setting ``pure-python``, or setting the ``PROPCACHE_NO_EXTENSIONS`` +environment variable to a non-empty value, e.g.: + +.. code-block:: console + + $ pip install aiohttp_asyncmdnsresolver --config-settings=pure-python=false + +Please note that the pure-Python (uncompiled) version is much slower. However, +PyPy always uses a pure-Python implementation, and, as such, it is unaffected +by this variable. + + +API documentation +------------------ + +The documentation is located at https://aiohttp_asyncmdnsresolver.readthedocs.io. + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``aiohttp_asyncmdnsresolver`` package is derived from ``yarl`` which is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + +.. _GitHub: https://github.com/aio-libs/aiohttp_asyncmdnsresolver diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..e9f94ad --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,230 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aiohttp_asyncmdnsresolver.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aiohttp_asyncmdnsresolver.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/aiohttp_asyncmdnsresolver" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiohttp_asyncmdnsresolver" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." + +spelling: + $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling + @echo + @echo "Build finished." diff --git a/docs/_static/README.txt b/docs/_static/README.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..37c5ff7 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,56 @@ +.. _aiohttp_asyncmdnsresolver-api: + +========= +Reference +========= + +.. module:: aiohttp_asyncmdnsresolver.api + + + +cached_property +=============== + +.. decorator:: cached_property(func) + + This decorator functions exactly the same as the standard library + :func:`cached_property` decorator, but it's available in the + :mod:`aiohttp_asyncmdnsresolver.api` module along with an accelerated Cython version. + + As with the standard library version, the cached value is stored in + the instance's ``__dict__`` dictionary. To clear a cached value, you + can use the ``del`` operator on the instance's attribute or call + ``instance.__dict__.pop('attribute_name', None)``. + +under_cached_property +===================== + +.. decorator:: under_cached_property(func) + + Transform a method of a class into a property whose value is computed + only once and then cached as a private attribute. Similar to the + :func:`cached_property` decorator, but the cached value is stored + in the instance's ``_cache`` dictionary instead of ``__dict__``. + + Example:: + + from aiohttp_asyncmdnsresolver.api import under_cached_property + + class MyClass: + + def __init__(self, data: List[float]): + self._data = data + self._cache = {} + + @cached_property + def calculated_data(self): + return expensive_operation(self._data) + + def clear_cache(self): + self._cache.clear() + + instance = MyClass([1.0, 2.0, 3.0]) + print(instance.calculated_data) # expensive operation + + instance.clear_cache() + print(instance.calculated_data) # expensive operation diff --git a/docs/changes.rst b/docs/changes.rst new file mode 100644 index 0000000..691e54d --- /dev/null +++ b/docs/changes.rst @@ -0,0 +1,18 @@ +.. _aiohttp_asyncmdnsresolver_changes: + +========= +Changelog +========= + +.. only:: not is_release + + To be included in v\ |release| (if present) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT] + + Released versions + ^^^^^^^^^^^^^^^^^ + +.. include:: ../CHANGES.rst + :start-after: .. towncrier release notes start diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0059d78 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,457 @@ +#!/usr/bin/env python3 +# +# aiohttp_asyncmdnsresolver documentation build configuration file, created by +# sphinx-quickstart on Mon Aug 29 19:55:36 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +import os +import re +from pathlib import Path + +PROJECT_ROOT_DIR = Path(__file__).parents[1].resolve() +IS_RELEASE_ON_RTD = ( + os.getenv("READTHEDOCS", "False") == "True" + and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag" +) +if IS_RELEASE_ON_RTD: + tags.add("is_release") + + +_docs_path = Path(__file__).parent +_version_path = _docs_path / "../src/aiohttp_asyncmdnsresolver/__init__.py" + +with _version_path.open() as fp: + try: + _version_info = re.search( + r"^__version__ = \"" + r"(?P\d+)" + r"\.(?P\d+)" + r"\.(?P\d+)" + r"(?P.*)?\"$", + fp.read(), + re.M, + ).groupdict() + except IndexError: + raise RuntimeError("Unable to determine version.") + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + # stdlib-party extensions: + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.viewcode", + # Third-party extensions: + "alabaster", + "sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive + "myst_parser", # extended markdown; https://pypi.org/project/myst-parser/ +] + + +try: + import sphinxcontrib.spelling # noqa + + extensions.append("sphinxcontrib.spelling") +except ImportError: + pass + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} + + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# -- Project information ----------------------------------------------------- + +github_url = "https://github.com" +github_repo_org = "aio-libs" +github_repo_name = "aiohttp_asyncmdnsresolver" +github_repo_slug = f"{github_repo_org}/{github_repo_name}" +github_repo_url = f"{github_url}/{github_repo_slug}" +github_sponsors_url = f"{github_url}/sponsors" + +project = github_repo_name +copyright = f"2016, Andrew Svetlov, {project} contributors and aio-libs team" +author = "Andrew Svetlov and aio-libs team" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = "{major}.{minor}".format(**_version_info) +# The full version, including alpha/beta/rc tags. +release = "{major}.{minor}.{patch}-{tag}".format(**_version_info) + +rst_epilog = f""" +.. |project| replace:: {project} +""" # pylint: disable=invalid-name + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Extension configuration ------------------------------------------------- + +# -- Options for extlinks extension --------------------------------------- +extlinks = { + "issue": (f"{github_repo_url}/issues/%s", "#%s"), + "pr": (f"{github_repo_url}/pull/%s", "PR #%s"), + "commit": (f"{github_repo_url}/commit/%s", "%s"), + "gh": (f"{github_url}/%s", "GitHub: %s"), + "user": (f"{github_sponsors_url}/%s", "@%s"), +} + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "alabaster" + +html_theme_options = { + "description": "aiohttp_asyncmdnsresolver", + "github_user": "aio-libs", + "github_repo": "aiohttp_asyncmdnsresolver", + "github_button": True, + "github_type": "star", + "github_banner": True, + "codecov_button": True, + "pre_bg": "#FFF6E5", + "note_bg": "#E5ECD1", + "note_border": "#BFCF8C", + "body_text": "#482C0A", + "sidebar_text": "#49443E", + "sidebar_header": "#4B4032", + "sidebar_collapse": False, +} + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = 'aiohttp_asyncmdnsresolver v0.1.0' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +html_sidebars = { + "**": [ + "about.html", + "navigation.html", + "searchbox.html", + ] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "aiohttp_asyncmdnsresolverdoc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "aiohttp_asyncmdnsresolver.tex", + "aiohttp_asyncmdnsresolver Documentation", + "Andrew Svetlov", + "manual", + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "aiohttp_asyncmdnsresolver", "aiohttp_asyncmdnsresolver Documentation", [author], 1)] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "aiohttp_asyncmdnsresolver", + "aiohttp_asyncmdnsresolver Documentation", + author, + "aiohttp_asyncmdnsresolver", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + +default_role = "any" +nitpicky = True +nitpick_ignore = [ + ("envvar", "TMPDIR"), +] + +# -- Options for towncrier_draft extension ----------------------------------- + +towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-version', 'sphinx-release' +towncrier_draft_include_empty = True +towncrier_draft_working_directory = PROJECT_ROOT_DIR +# Not yet supported: towncrier_draft_config_path = 'pyproject.toml' # relative to cwd diff --git a/docs/contributing/guidelines.rst b/docs/contributing/guidelines.rst new file mode 100644 index 0000000..f0413b8 --- /dev/null +++ b/docs/contributing/guidelines.rst @@ -0,0 +1,28 @@ +----------------- +Contributing docs +----------------- + +We use Sphinx_ to generate our docs website. You can trigger +the process locally by executing: + + .. code-block:: shell-session + + $ make doc + +It is also integrated with `Read The Docs`_ that builds and +publishes each commit to the main branch and generates live +docs previews for each pull request. + +The sources of the Sphinx_ documents use reStructuredText as a +de-facto standard. But in order to make contributing docs more +beginner-friendly, we've integrated `MyST parser`_ allowing us +to also accept new documents written in an extended version of +Markdown that supports using Sphinx directives and roles. `Read +the docs `_ to learn more on how to use it. + +.. _MyST docs: https://myst-parser.readthedocs.io/en/latest/using/intro.html#writing-myst-in-sphinx +.. _MyST parser: https://pypi.org/project/myst-parser/ +.. _Read The Docs: https://readthedocs.org +.. _Sphinx: https://www.sphinx-doc.org + +.. include:: ../../CHANGES/README.rst diff --git a/docs/contributing/release_guide.rst b/docs/contributing/release_guide.rst new file mode 100644 index 0000000..9a44829 --- /dev/null +++ b/docs/contributing/release_guide.rst @@ -0,0 +1,105 @@ +************* +Release Guide +************* + +Welcome to the |project| Release Guide! + +This page contains information on how to release a new version +of |project| using the automated Continuous Delivery pipeline. + +.. tip:: + + The intended audience for this document is maintainers + and core contributors. + + +Pre-release activities +====================== + +1. Check if there are any open Pull Requests that could be + desired in the upcoming release. If there are any — merge + them. If some are incomplete, try to get them ready. + Don't forget to review the enclosed change notes per our + guidelines. +2. Visually inspect the draft section of the :ref:`Changelog` + page. Make sure the content looks consistent, uses the same + writing style, targets the end-users and adheres to our + documented guidelines. + Most of the changelog sections will typically use the past + tense or another way to relay the effect of the changes for + the users, since the previous release. + It should not target core contributors as the information + they are normally interested in is already present in the + Git history. + Update the changelog fragments if you see any problems with + this changelog section. +3. Optionally, test the previously published nightlies, that are + available through GitHub Actions CI/CD artifacts, locally. +4. If you are satisfied with the above, inspect the changelog + section categories in the draft. Presence of the breaking + changes or features will hint you what version number + segment to bump for the release. +5. Update the hardcoded version string in :file:`aiohttp_asyncmdnsresolver/__init__.py`. + Generate a new changelog from the fragments, and commit it + along with the fragments removal and the Python module changes. + Use the following commands, don't prepend a leading-``v`` before + the version number. Just use the raw version number as per + :pep:`440`. + + .. code-block:: shell-session + + [dir:aiohttp_asyncmdnsresolver] $ aiohttp_asyncmdnsresolver/__init__.py + [dir:aiohttp_asyncmdnsresolver] $ python -m towncrier build \ + -- --version 'VERSION_WITHOUT_LEADING_V' + [dir:aiohttp_asyncmdnsresolver] $ git commit -v CHANGES{.rst,/} aiohttp_asyncmdnsresolver/__init__.py + +.. seealso:: + + :ref:`Adding change notes with your PRs` + Writing beautiful changelogs for humans + + +The release stage +================= + +1. Tag the commit with version and changelog changes, created + during the preparation stage. If possible, make it GPG-signed. + Prepend a leading ``v`` before the version number for the tag + name. Add an extra sentence describing the release contents, + in a few words. + + .. code-block:: shell-session + + [dir:aiohttp_asyncmdnsresolver] $ git tag \ + -s 'VERSION_WITH_LEADING_V' \ + -m 'VERSION_WITH_LEADING_V' \ + -m 'This release does X and Y.' + + +2. Push that tag to the upstream repository, which ``origin`` is + considered to be in the example below. + + .. code-block:: shell-session + + [dir:aiohttp_asyncmdnsresolver] $ git push origin 'VERSION_WITH_LEADING_V' + +3. You can open the `GitHub Actions CI/CD workflow page `_ in your web browser to monitor the + progress. But generally, you don't need to babysit the CI. +4. Check that web page or your email inbox for the notification + with an approval request. GitHub will send it when it reaches + the final "publishing" job. +5. Approve the deployment and wait for the CD workflow to complete. +6. Verify that the following things got created: + - a PyPI release + - a Git tag + - a GitHub Releases page +7. Tell everyone you released a new version of |project| :) + Depending on your mental capacity and the burnout stage, you + are encouraged to post the updates in issues asking for the + next release, contributed PRs, Bluesky, Twitter etc. You can + also call out prominent contributors and thank them! + + +.. _GitHub Actions CI/CD workflow: + https://github.com/aio-libs/aiohttp_asyncmdnsresolver/actions/workflows/ci-cd.yml diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..7dfac4e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,83 @@ +.. aiohttp_asyncmdnsresolver documentation master file, created by + sphinx-quickstart on Mon Aug 29 19:55:36 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +aiohttp_asyncmdnsresolver +========= + +The module provides accelerated versions of ``cached_property`` + +Introduction +------------ + +Usage +----- + +The API is designed to be nearly identical to the built-in ``cached_property`` class, +except for the additional ``under_cached_property`` class which uses ``self._cache`` +instead of ``self.__dict__`` to store the cached values and prevents ``__set__`` from being called. + +API documentation +----------------- + +Open :ref:`aiohttp_asyncmdnsresolver-api` for reading full list of available methods. + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``aiohttp_asyncmdnsresolver`` package is derived from ``yarl`` which is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + + +Contents: + +.. toctree:: + :maxdepth: 2 + + api + +.. toctree:: + :caption: What's new + + changes + +.. toctree:: + :caption: Contributing + + contributing/guidelines + +.. toctree:: + :caption: Maintenance + + contributing/release_guide + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + + +.. _GitHub: https://github.com/aio-libs/aiohttp_asyncmdnsresolver diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..ca4e630 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\aiohttp_asyncmdnsresolver.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\aiohttp_asyncmdnsresolver.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 0000000..c386892 --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,52 @@ +Bluesky +Bugfixes +Changelog +Codecov +Cython +GPG +IPv +PRs +PYX +Towncrier +Twitter +UTF +aiohttp +backend +boolean +booleans +bools +changelog +changelogs +config +de +decodable +dev +dists +downstreams +facto +glibc +google +hardcoded +hostnames +macOS +mailto +manylinux +multi +nightlies +pre +aiohttp_asyncmdnsresolver +rc +reStructuredText +reencoding +requote +requoting +runtimes +sdist +src +subclass +subclasses +subcomponent +svetlov +uncompiled +unobvious +v1 diff --git a/overlay.tar.gz b/overlay.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ef31c4408980ebd19ae506dc10393b9b5ed3a3a9 GIT binary patch literal 141468 zcmZs@Wmp_d*e-~>y96Cvg1bAxB{;#|0|fU8&OmUt;O-LKArRbsut0(h?#z;R_d4I% z-ScCrpL(pMr@HUHtFNYtMS)RI_O*q9KM6!Nd#C3MiNDQRorpgb^k#6!GhO>sqh{2K z&zfWryOPBI(+h9iAi>MFq`}*ppQkB_f2zeXDdmYRMOtXhJ-t-5fWXkuFiBe3h)EBP zojn|ZDaDN{{PhMJc%G5FXgwNDQE-K)ynVNoeg0|tsrJq$Ma6f@-@(=O{PZmC=Q-tL zXLnKTtIY>B?;EdM&(itLKPErKa?k$Q-HmM?_Bv9k4>uFQcW`I&q*V8Kij}8LbI3%3 z247!TlaeYLlf(*D>`N-HMo_1%UgoR)p=KiPY~aL6i^CJB9!XFoc)T1NV%psu$} zy4>5{-6@iLrnvX$?K$w+mf!oHFRAm@5OqYCiLOz{(U2oIl7A}bMWlhDI#|zJFQN05 zn703sxaxp~Gt_HqO>pqBOvGQr`C)q&biBQ~x_(>>4Ofi(e3PJL-FU~tQ)%wt_hF01 zo1sm~pu2#awA&(;mB`9KUM}_HV2Egw78{Fvs_pNVFo$swIs+FHeCH5@O8ZO%Zjh7G z?6v&_i!DKu;W&{8{k(>1H;ig2oqcXju5J?}cWROjd({sWwaM%pVS?Yh_;e}aj_J0r zEv#%>X8~K9)hU&k%q_)ahOJU2?99oCd@>4Yiykbb(fm4dn0#DS{X-wC5q>LcnJ`*4 zTNnti6~laqQIblsJ5|x-Q6=drk~U$^fS9dnuutT1{LDdO;p|SeVAifAU^HLzC1rNF zE=hEiDyxYzX*CN?2~%~@xV`GW z31Uu?fgUb2`kJf>`oCGWa-W4Nv4SiKU+C?uG~fL zv^-|k(mzbi*Z2+<>OC}}sxpTveCSzNxH9a=s617`4p&P|hHI_DHHJYgRR=`O>l)pp z!XNmTJzUA21%Zu4og&%iD&;@UJik|IuO?-jOdjAHrPMscPvy-0_`(gE*JP`tubN(M zvoGN-ksdA2+bN>esV`RF>fl>lO;ssfU!UD*AgHpKGhk!Y^~wrs#PEvNV%C2be!)DF z;&Ag5vhl^srl&w96C{k9St+43HIqx6DPr~w;F#LJY*H&P%A^boxsrIYTIT+(X zJQ1c4dw{VjtJL94gC$MPAwEtGBUB%uMC1KOGi-cY^yi8k(Fz+vH*Tv*L)USJd({H2 zncO7RPg7M*o&xWSH{RoR?17Q{`M+a_#m_47_~}X=juvnJ)WL{3Q*2Tg`Ph7JU2pv% zxY~ZDpR<6%eITPVRot2h>o1Q|AzTl(16R2T%q1deAl^svA$T5bBF=DO)ZJ*lm@U!@ zJ1yZCPE7D9p(O1Dc=nq~o(5`nP98lVZB?!2J&dB{rrkh>O4I)`1;d zsand+3#N#C!zgrRH5Q@q88&nz6l=ghaPjvufb*p_{EKH?U1TK*;`bSRYX<86hMdH# zG}7aFSPR@IOsJBtvx7pCqX^)bt#Luf5jnZ7{;TlMW##Cu?}SV@*|14BNh*Ir1o>w$ zlV_nrsfAZC9!jD{ONLj;^aW*8u;{xUa>IhK@!Ww<$)e?9+L-TPbC9f>Dn;j$PZV1x zk_mYgEBk^9y8CIl%vu{YDqwUl9TKSd!oMXRe1K=bMvJ1K-JoBZ!SU+b2YJ$W7A9gU zxyZvKlbzr@p#<8KzAzCI!QKBMVf^d_*drH~=STQpq5N~h_q%Z)`{{O*?X|}5zA;GO z5%`;Ev+Xpb=VIqGM#xDxvTBFr5v7)yd8{?(XEWW@?`SeS{cKt)zF3FBKuzY;c2ehZ z5MD)ELzQ)b3DOhn>Evt+EfE5V;14(gBSX!_4_#&WtvV*GeLMlfQEt>CK=ye^jlcQT zAx3vxCuS?2nkGKJgB`|ix6~x3gx^n2Tlry$9~nY>V^q*Xv4xWzMDyTADZK0|J3E)- z$F2)sG8IYezV7oH&sgAkQ}gbu)?S$ z@){%I{aX6FUJ#^W&O9T8Gtq}tFF;_XEjI#^tnw`QPCM?X)uQ1g3ApU54=^LK!f^)F z5wyTX+%T!88O@Zu-$9p zY*=F6F&Ju4hcbkK6169*3vnMT#_66vEJw%Cew3#^yVn+>t3c_PzE4BGSov7Nt;HWi@rJ+$<%#5>YaSpVdjZ@^$z;iCTEA zfG5OGJR0SEplNn}7~KvysyF0VdJ`c!Y7`^8&UC)OoV&%=$IQ+u;7GK!q3i9Q0smWk z1u7=?Y4cC~mZ!i9ltS#&Y3)leP15PW`^?9Q_T+srLtXaFzwS5K+!wIA`q|HLB_I0% z-?7{vX#psATqrNAtE|+Mgw60jwzjG1Yu#=5thDGyDEvzO38JZR~P(0)}KQ6z(l082ptS1G* z@0H?`)k4~tAt`TwE#Bq|4k!%yn=;CgwX4zCH zv|OI5X-_Z1b%41h|H$-M;#^)k6AABU7(L)zu2g~SsOr&ECTE3&q49&B^FvfO>Slt;f_}%@7uZj%wsLM2-Z6++-pc1LOr@4bl*+`LSxO&C$QV@aNC>8UF${!6qcn ziDq8l58*F(kYRv2paG%%Vyqv>J{BPB&#}9q#$?F_@@h{THI11t*lUbLd1rUuq0q=j z=-TZ)Q9UEsD2xxaJ9!3r;_cO=ch;s<@?xjKKeL?RM8gp1V9?6cJs`o>GG#8fS!2k# zqhU-=#Hf@-esgrBq%Pue&8T-CCv#!S^^C_%&hf#Y@3!w#`-2Fu9-(TW{!QOH3sS=fWT|HO?0Mt{ zo8TYS(i0x_nSDW9D;eQZi!(N6M7AGE8Hb#5r(st{c0Ky!r_yN#hsymqxsQE}@kq^iKejfMn)^~IjVYQxGQOGdi&UAL z*GI8!{To<<1C>#Zj@_%fkZx;=(^dSjZuU~;(;(`GQa&2d9LqG6vf}w4H0n|hTDh(s zPH4M@I|qqy#OpTJcvi^xlpAN%8*+YX9iy#q_EBROh&G6(*5A+47Kzkk5KFl2DIWs0 z!hil6{K)3qm6O|=Zgk4k!fp6*d)g`<06w1;cdtmxqKRYg zy_;IcS_V@NBV_+{Qu_C{laVgrYU9Kz()0EazM^bR2sSb2*~;&a2Cd9ix~j_TS!%dy zyBg)vjj{@jWS$S2SDKE)tt~mpTfcDts>a|9=Ny6h&rO7Qg9YqF(Op(H`!uu{WR)Ls z_mXEsb*B{S^b8;PAbFU3K=mKezD4T%Ub&wJr-mpXAIzOh;^q$t6IX%aFz2q zTeLbJU>BY4X^oup29v5%9wK}XV4HIMn5WC>j_{1YP^3gBxNArD+`cqym;t2o{{Q$1jVe6r&)p+s4yaWL-lMbmbqVKPU{0Fj+pgh5dr7cP)N z_gw>epWs)`uy8$HF3Q1GY*|wpfvUn9DZs6)*DoAiSltA!@c3jC9W-6OM1hZeyqqTx zNo0fvIOnNn38!!VMi7LP2JXuXxDU%XhX>AvY;T3ED2FWz!R4GZ4xEI(ZTz6*h`A`D&kX$b*4D za5v1li@8B4yVz`a>y>4*tG?I}SWWD7d!r+9zRN6;(X5Q&5^%?RX6(byFV3c82p_^l zojGDkRA=8aX_MTvMt!r4e~O40Xmoaz5tu~C&6=Oj~vR6T5EAHFVz=y@AmnAuax#SPK6gteO=6mDCr6N$F(h*Qbc&2sGyAV4^9Awls2J1cF9OC zWTm{1n)_+OsDDYuq`~5L^!xaK#j@gmYYwflfq4JCaQKa>9r3p1d}($n_o-eOIkkyI zSMP#9ntkpUB5Ngg;*=%-A!6n@%0IH{%tYkDvZm^cWf@jqzPRGz0}$@!zGy+320nLS52x zl5YkMJ*Zty|h_5h-GE1Jja-}h%NV{G% zX&MM7$LQU@>G(Szp;Hz-_Sb6=x=AyZRuGS1F&(NlS$0QXNPe;x8ABGA9rTq$FZm(j zRAK6qUGhd`(&-;+ zBkOLsuKpC6f>qk>Ly*G zC@k9Mg7Wnx@q1>bpxmy0#rNfDE7qAGS_xX`Llf5!FARFJ?>o4h>!Z*Lcowr$kol6^ z)vj}+Wz2#Hx;~z&nt6X|RHD$|^(~5B#DTXWjJ}$S3TxpbiaQN#nq96+^Lmx_w=FxlVE$KviN6!k#^x}pyq8-mQViwgf1O{3~I zIHUTZ+GFiwh4YI7lm|0y?z!L;Ckt?=KSW99_ri%zW-(UNxu|6ls?i z*j7TtUMw$r;4PozMIMcs&h zle`}BTVFvMMF|5c68o#gQOIGyKo_1)>RtXmtv8A8YKa@I_woSX-N}~e@C|wka zk@*WSFk=_}PVIj-zoTcy9Ao)r#(0e@>a0*6o^NH~lRbsY%~YX{h*@1%-O(^9e(K_g zcCeP1&vW^Ns?EGP1VJMPfwAuO_Rk$2HNDT(z9Gygz*=741P9$hAIE>!S_cg=4GB@k=`N{4TJav zDL3v0{Y*4#2!!s0TC-?`KnFm{x4IOv5lqdn*bM|4R~^#T(uV@fDh5uhOB%AHEBMcG z5fmzi#C5AGA?%n>NosJt8+~K*6!m<}cea|0U^cg(vFvvO1TB`@@3Q3QyL~&SB-o5^ z+d)Q!tM;GDG&S)4I0-v3a_QBtU$P`JzM%wc!Y(mYB+XKD4ThJ>ujdOEsC8+)a2B41 z=w0WH;s$1#Ocrg$8Ycw@$_a*MqV1*`!6lzj9YjbvZw#)#mb2QclVqwR?~L4!fzq`4 zKa*LkZ=C3VMfhyMk2V)ULjOXarnW~=LNiOANQ+EfpTYU4Vwg8>y8-d+P5$w7-6L81 z3S2wG*vCD^jwh@aN1G_S(cWs?(2&a-F)PS!nP~a#CpfjJNeNi)jJ`LrZ^A}K4uaMB zj6~+)Kz`MP<>HwE@-k(D#|B}1H~Dlz_Kx{{yA8V%V|y)9S@Am}wPw}HW7ySS%CyAV zUu(7>zA;Q)hVpxXz6)jbO*&9`fiy?-*fIo`mmvFBS%zbo+)OAdCm)a0cbQAja#sEB z?W)-C6|KVTJ6UWQXmv7|4gO-5Pv>MTD#GA?I{_kNsWn zk!xN#le;EdRn*gmCqRweg?X%yoDgJm21J62S`P2c-76jbjEEy*)MlS@4P7 z7cFOF0~*HSE=C6N6|`JHsAEchO*w0py7=k=@d*QGo=yH}Q|8`bt(Q+J?!HFc32&ah zwvF982FhA#VHPTemx!P#5oc_4T7Edf5w?RF>^!SCo4>iNSlt|nt4xs zivtw5LL%^edj~YlnbDsOvbHxM4DDr&#Tz@#LG_LXoL$PBRM;d9T~foz6z z^~;mtdygf-4~wezFUa5o1T-#CFEG8DW%^GUby_bp|Lo|9Zaets$m#s_^wcSReO~O3 z*WJrDdeYGy6>oqtuIZnW^S{aB+WpT`>1}=uayf9B9ByUmLdUJ1Pp6FZUG_Scg+5Of|He<@IxBucv z-^M0v+m+>n?(rI0C!QH!klc+vsR9@d%1+wqtPc5DMp$CqDQmkYNYXtfC`T`+SC!ar z(nI^=tnTHc>h->5AjJueeM&SVV?mXMu{Q9MLSbdyh`Cen%YFR9?BsHi;r@4SQzioQ zD@B1089lOUG>-}!{1ZNXE&Ry>abFnL@lIYxZ|fw%z~b+5b(@Xj(cg6ks?bxqxU9Vy z68q9O`&bkO*VM<*$*gtdwfCkcS1u&+d3k()o^_pSuCSwushbRZrScfUDWRWgd&FDH z;j3HWJ_3ynoirm|A=_j+q~kg~_pyg|0!iDQEY5zbQDLwnE-AyJTAL-g`vh{Mf-cAe zL$42XZLuVxXeWN>G*(*(%T7w`W~KABnN8V;P@Ky0gyP+7B;2Gc4ZHO#>_&Oe0P8gkyH1^M ztg{65Hj&S^`$pjqpC!3GI!mxnOCdWiYVdal@Y8^Rwk`d_#G;aKOfD5?>4{3IKk=T$mWwv)^`*S3sl^$VS(Fa ziF+xzJQP2thOg5<`L4>RNRGSmnG*GWDsd4RA17DfdZM$f7slG zWmlYsIV1CGT?3k;5mnn5=f!@;%gHa5_c{UmS?W#k;n9nU9`>b6PDm`CEC9SJvCaKf z%0P|_6!Ybo`$W2h_9bR-7e!7QcVyhb$Yg~NpeBM^IZ%Kpom5ZvS{K5fYm2K8!d!>g zK*9!|%|9BN_p0CdcgHW%FOsXDF>F*}5wNo0G)BZtL#4BN1cR$FFRH~W$aIG_H25#@ z9{kt_^e%m|iVp8_w7*}>CcdjL&suLN!oNXgmE9?uo%UU23HvI4qAs1f=&%LJ>&r#m708v_E~)dCilw+y zH=0;%Ov=hqn=v-)ZA_!)FwdM9>4(?ouTYyTHcE~c==mVR@WXXJYh%Ihv~I|oC-@WI z!z<5A*S4>tLD6(HEdej2Xx=E_FM;vizjHvPb7LPm457cSC^;;c^$nY+&3HNF6$TN!=A(C*Gr|#z3A2m_MzP{u zW69OR{v;xLbHvVsR1ASO;xiXY4ZnIkW%FCEOY)5W8Yrua54IRsv6JsP`+ZgS-K$Xg z(Ao5T4YOIurV@nN1;T(61b4WZoi6EwA)i-FWqK1ql+Khs4%T?4uVz%a z1?J+7=qImQaOh9iE&P`ew~2c6X1dhLqaApST|tIinrNBF2OmChfJeXGmy+_Y-nG~s+Ej`dsq{^oEf>eG_UB`&*uyRKYsq->tp|ai9s((dUm*BN6@w*=pa7y{j=NK^7$Y0|0DKh zG0*LGb5GUz6z_%IUACOyNxC*N{y^MrvX2g)$A{WHk4}5*ZT|n_T-@iIlyT2f;S%7Q z6}Kkjh!Fa&pk~iD>d!_O>OJ*hWu^WEdt)1R z<$by@RO6A8FpVX2C#|j`BjWEgg!pMSBfaXNf2Ag#h5|1><=xlb`|s%nh`#>4Z`%ug z3YaeYKg~f3^GUa{*=w-IEf(s;12mMiTj0GWtkO%mPiOe>bMQ880^f7+Ip5Z&fyUS1 z2?tjtV=#2@0h%=v?7!O^)yo>hbQays-YWLkVkg!?f4MmJDe-29D4gi{rl>AOXYU88CaM{YEi=&vYceOs;B(;g|f9le7mr zF-@|LS-s8VBP78nSwgw(BYdQ;G<@#i0t|QpXQ~!vpe+aA{F&$I);M{tgiMGmQr<;0 zU4AO&Ih5p^dhXQ;hnyELn$>bH{AvlxSprP|%`~CZr8no*&Y=&y4gTL8#4jzrRbs!5+=o@0?k-=ax1gq_dOr02X;|?q$EiD^%R;Kh_*+@Izd&xQJsKlVo=F9bxzxZ8G%udTmkiHxr zMSc$^2(-PPoZXJ=;q_zj)4QHJ^5n%ABiSUUh;xbck`<6YI2xX{TOfZh*#9$@oX8<3 zPVtaEmajk=-NkzOd(08Q5wk&DA*&|K0BDnw2goEkCwf{1?vIaEMv+{64aR$|gDel2 zq6G^-3+yC(rNZnLc}|14MwsH&hT9GfyYUA&`ncYgy1BNq2dLify6FqEzi2#0TyxBo z6(@Y4O4(xVmh%H$fAiFuE2;KW?2zlw{F}0*oUZ)&C+q9S=hpl4UEAS=*kA^6w%5FS zzYF@^!0*gz(UHTNpCwNS1!DDLtA@;uhzrjndWigJuYb;%Mo?mQ^IVV2uu7LnHx=R+ zs@RD(vo^D*pw*z)V1@(BfD#x%fP0R9bwmlGXER85e;a}+egKmXcR6xXzf8YTm;jOp z@EDkmyeH)fE(>)aPcz_o}k>R}&l|p~^_4m1%8Byc53)gdlZ=fqs zOepb7{&nyWv>xjGx_nOs**;i)5qgtxp&`Ms5<9LprYGqE(Br4j^Wz?lhu7J={PVXWAI#Ow*pT}20NMkZTY3a$9Z z>(6k-X+B0;)puMz+S&Ts<6sUaykjGhXN10Pg7{CqApLW5NU~nkjSG?lq8l-=u3fF# z+x=dtVXGf}oR4>Gk7VlIt?z2Hf&yyZw2A2u@IBmIii=qId;Y{xuR(3$?V@Y z04xb``JB>Q1OkA10qWJRW{^AGsGA!Mlb67TAhAfj=$m7bE{7KV-n9OU7K=a%U@sN% zaZL`xLT|(}Y!){#80JeYgB$hS6MI7tAyD8h=7yCtXN7I44*Y=H5F`X#r-pLpVP6F^ z0|np$P$LDRZdx$5>NTrPe}Ru_f-xh_r}TJ84!sD0)?>X<{lxi1$E>7Xx^JbAc$~l# z*!2_Jk|jnD3YZC~C&s=65T*J}^9x+Yfp?4t+=Ge5z#7c?O2QUC?Dwr->O(a$$q9DM z7YSfsP{WyJ{W5W-KP(Y0)}j%o5r(W<xONlK)5y?pAWtu^N5r_w{lvP=cA!NlA~)}%i_JLu zz`737#ciCuvm#SgwPP5*T6no?pO-)HF;M{`p&-r`71d0o&U^Lj9ZP< z{7{x~nQ!`Gzgx{hIVwAW@9JhAa)}}AVQGn6$FxVUzKGO=X2+d)gI7+!w*>VPFH(M& zQMD>hyPsCID%ZW7PD9$^moq{~79f=%^P>C8yS}_9Q-WG+`}L%<-#;Xbv-ekC^0M@` zk7T|-AT@Z!^`|i}af@!_IeK%hggh5vyeGFfMFA$H53iXc5t}kDxg5GlRd0@fr8TQJ z$B$)RX^?u)+Vw@ozydG)Jw7NT_A0Iom#T-dOlxE3`=N?Sc&>Gth zNQi35CVd`@(kHiotQ1)|e;a%nUV{PNB}BJmlXTq`=pP??7EGxjNp$E{Z;l2?MsEX2mHR-@olIQG>;kS?=jhq(U zylsZ13=0Tw_n#Gc*q|t;a2PqJ>Fr-G^?9k*ij!$-jSvHA*q}3KP%oxsf&}Bu#tf8d zUw&`?&t`}P{}N03rj#d?i6thmb>{mQr^`h`V0%i7^p>^ezZ7Wy#NmH_M0*gH1|sXX zSi`UV1%i&||12Ng7AdhWMFFV=1bes8@EgiL5ze0$7(~tDC*~;HO$8}}KPG736~Mf40{^if4sVvvPXHHP>Vb(nkh*SKP#D8Q|}2Ky*u*(t;PhuBUZX$ zv5+NvNZ&4Anud#4nI@PCW!IDdF<7}=x~c2+n_MB0>x``8bv_nZaoRL_M{>%;bD4}a zn(vEL?!sj*R;ps!orBQF;u&kbNHhXNUxOora9UQGQD%aeue@l8GKKbN55PiZG}sKr z8ozF-)Ml_4#rQ1ugC~=6LE3~s1Ov0@Q%X7Y!W{RpulW(lSP3#CKGiGb-tViLmqJ+7DYj3kEC1J z^^X#IUbQB}fPSvIr?mP0mmns*@j9$CK!_$*JRVpLASfMshH*yUS2fWuhQO4IwIv<$ z%Rh{Rx{o3g%n2L6fxDp^a`z7PjJB^|3t|Je5yUAd>*0cpg$Hg0_QPg*7(p1|HvBkO z)ID4<&*=IJf&Jw6Q-xqmgq5d6Fcw_gDW(f!-)jSyAMRwG>IBet)C1Oqi;F{Qn<qb~<5?GN5NjO3E!Z!n()yKjJU?YgwPfhk?!C_z`%=6kZt*C|chnmIw#z0C&SP{oj z(J(Wr2f+w9VY`$H2_OjInvLFq<+GXq?V&L1t;oyx1-3>IKQt#S6NQl4$$m%F`X7w# z@h}w8W~{JD6#b4UGkF-}2#PJpb)|9@U>yWSPsBPpNh59_#FBZUt{fQh9)hko7}GVJ zt`wMMD3cE=Z0!&6_B1$mhy~o(GQu(rxUwv)3Kg=T)31ZjzIzFlBlrenMRj{1>?cHM zC&&?iGj_MP@Vc0ALCg>g$ z2J(4Efji)}ARWfUv_2e$eJcc}b~)y;m1q|lOia<>^W51mf8qRgZ)bZ|zbM;NAL%84 z`aA63V^oqm~Wqcsp zZQ;s#|5GrNm-?@`o6+VKSjZDkgONq_N(oo+^;5%_2A^wU@LjL?B|)-E`X}C)(AU7qa{q1!nxd=t zR3h}}D=(98iAb@@A0_+b`?_8AA@%4sV4Dd(`{%m*<=xI9>?l_PQ_9EC73MR7I%2?X zN#T59nKbm>BRXUQDtP@=xEyKnjs;nQ_&EX05vGnTK{+0AhDw3qjExC|Qlhm)EFlOW zkA}Ho1_|ExfPwuqkj<)om3o{mmUjAfZpfd$e1t8EdwsBKs2)NP*)?Jh;k|nSUO93p z{HM^t(APdivp_I66dQgvWE5%+HR{(6o`H%7FPcKOccAUi575JY#^8bc+k{8&N4*De zSd&id=Nqc0t)r)J=OyfSZC&^Qj!g&F>}<8@up>+!T}N2kl6XEE{)NBMB6Vi1yj8k=L_P?I*eap`$ZG5i$o3Dfa-wT6 zXm8X^!l|uKY@Hl9044G7aFegDFc_~%@}s1$j75M349R~40o^{s|4`Gn1T25zsBXo5 z5H?Q*83!1)kI65M4~)zIG&Bzf1sq$@xB!Shu?;y{EzVODo+Rl=56S2CD59(@KGRAy zCc3@*3a?OHkJFe~K!@NwM1`=N1_0kaIu9M=2kgK+_C%qWe3c>$yF<_7m&BlO3q}kb zG!p0V5qy4N&zpbObIY0sJ%DsLia`?wMX2tcu_JpMr>)KWKvq(RAesfQ*I(!6s+&8O)>N)o_ax+xFIoxIE`$ z0?M2`)HYqpBSokdj;`<+9waKppBQ1LnD6oOPA^3HN~6a!)*S` z@0Ew-Acs4@9v1+1&b4SbW(&#?Y*6Cihb{Eocm6#~lKmpL0jUxwXAOrEPD9*cruHfN zdBJVCgV)d5pcKsMbmx7a)vG#wt$^G^Q!>T=D|&Kf0x?r3QEkX`6R7K~{ScMhckuHx zu^?P+pOI=$_IkYh8==xnuXKE1s7%h zC9ICUa!rpqS)EHwn+lYl-aV&+L_3zRX60LOr%l(!p6>i!!vJ7ue{l;yg7e+;50Gfj z=9MjfV_^E>Am%>ObflBj-CmwVOXALE9yY&Hgjlczpwr7=+zbFr@ZH*$L=sI{8|y1L z#3f&S(43}PA5-x5do9=G2K#0Jz?%NAGxC0IK3mI@d64H&kmsG>mLq_|6w*}|S}bkb*x+#ed5Mkli8H>k4rGFn!Lav?ki?2rymQ_ss^0HvGKO zjSm5y`o@ApeZvnY`7aj;^R^ojS!=JlDngC__!fbTp(%%L&ift5Vy^XxON?T@i;}e$ zU0d?C_BUPe0I-99(7gOo!044B`NI_1w4;;NDSe*a`dG}luZcBa+i1tP1XTUnb4ZA3 zm3nx<-{{bCXw8pjdhA;P3XnMS{Q$gXnT{o2)b%m30!T#p2LYL3-MYlEKl@A^iebBx zr2K?`hh%*zjRoWu8h+c+{_-YNgf>dc5I?(4zu<+=!uKWs-+1nHAaPxHB+&7FPd2b> zIcML@>E_cr0!tmNh#?`YN0}xIN$OVb35@CMN7i>v7l!&+&e6Y)dJD+t`F_E& zsSRIX*_Slb_rB4<^iQAm!Q&HX@8YUO`};Na1(AU?AmA&Q7rFbDc?rHwV0!H`PuI60 zKzLLVGS&31E6^e77h^dIWSTIq`|#HC-9{_pVE@X2x0~BuYtdUb6a-0L|Coyi1c)F9 zvuus@SF9Qdk>FsPPq4#%Uc|n1G6lns%PCJFOgmkGnqXUUDDmO-(Ow^ekVp>#F*@c+ z4obY^Igb_(x-nRCw+0J-`Ma3UP-GHw9*t29V)#ubZej@QuMHQhV{iSsa-p)AaYjb= z-ExM<=(qu^ik>e8AyEjoCm&LDBue>9xWfZSZqLkSUK>s^*4l!}f%11ZTy|ETtifJZ z=)l{PQ|lRp+iOlcu$chRh4N_ESHPRw&aMD>h*?6)D>yTCg`|zKCjM=IrWFC`va&UD z<+AdtfMQ+XqKkr@wAuLy3F*ekXu5E6nWxI=r&h6g)P81I-Q<;2cf!~c>DsbtOC zB&#D!D!R6cWFf69Y2L9jOp3h6QwZ48yu*$330kB~>N$Cn?xtumLH&>wOX|6(?MQOH zpi@QaIebthNM|}!Fj1%fvp|`{w7)?4J&1>?LujN>F-59DnHD2vXaM6;Q+C%^Gf~Qd zZipK4Oo~t&%tW2!ykl!1-kTt0lCOs_8Ye=yfYNvO#(r|_-9dx#ph3^QyW3I0gc|<` zj(Fp`Dyd7Vj`1%h7+c~MvV+a>YS&M-{{-zx_uJZE-;G9_ojMZOxC*{|hUz^$Z{KO{ zwAUX!NTi(L=Ph3auh>DE+7F=0(ZQ==ADeCn#x%5q zqw($12|as&dIY?lU!VjIa7;f)=*ynx2ESta))D_Vm~Anr_cWB_*Mmg3VHdm9SJ?~R zAn5t%R=VG=L|m}g>+t>dD3tqE@%i*(8_pEEb)S1-{H*bectLS-d*5+C3_gacpL90V zjfhh{$(~GG1e5q1owR@tw+UHC;)h2p1x8y()-3c!-@|c0ZD(B-pzM$8v!mas;(8b# zMP~KF^d#^Xzbkw%ntipiy*7lLz=M~A@w=XSW1kK7v1xnpeSPDAOeF>yvOAkb?JPxP0B2^MQL3-*P}X_bF@I}H)d)AD1q`N!YiT{ zTTVJ1NgX*K?bQRq5tfhf`WbQuti$dR)W-^V51|S*MaaVn5Y52c!WSjkpnQi_8dhlL z=%#AsVa5ho>WhRwM;9d_LF;1Bz_fx3@6!oo4^4ele}WD{U7(}Su9v+7(D{&QC_6O$ zg)n$i;`IEI>h|tQ;raL_{UPWm;Zdvy`)TX1Kj>U)`zb)jwBI}Oe6NQwF#oP+^yw?d zDf!LmbSS9F0SC|l?dOjiB!KRpRPSP${H9dFYJ1PHwghpFpvNt*(c|{(jA`YFeN+@mpjJ#JypN zLJPp?M3^;Tcp23Oo*5+qL=ZEO3$jI%UX?fTHlA_b#~cmEQ+PlA4nO7%J^%=u_93`C zU;m*8w=T^jLSAy;Cv)>PdG~XiQYX4>{L6pPA#6>OTRI-V`bLP1t&I&04itk#MQND$ z-67xHe#y&I;&Ss}jzzgeg5tzj-Z@i0g#-8-gH;6IUJ}4YYkNZt#4b&V3U_F2{FNY- zNT>Z5|8U?tgU^F%J0T#^)=wGriKA`kd;c&4=aQrg&h7fx*=TqmKX;cAiE|1D`dc!` zw)$mxBr5z{-fyrwMC7C-1SVzx5eXKIe|Q0{lbjxY;C=j1+clcNy5c#Jd3mTP4aSJgh84n3`RCFITHm0;S8?)( z0S5D`H>B`kWgu}Sbs43b|8k#DIsqj5C49yipl8hJ$@h&lT#fw4D|6wXd~K(R;=1B- zva~<^4|&DaF}@n3V539F3QQ!pOxBu|_E81cO&#IE}+taJ6=UU~0tY9y+c51eonQpMDzAHd=lxs5b{ZvzCiJI9_#bi?5=_L9uf!<*O ziEHum;V$3A``4GZiU%IYBe>a5>?;TDy+}ggqeZ=DxoOb?X9+eN6 zko}u&a%3dzv`vuX>gQZ1q;(#5CJ=s_oA_AjeV>NjEV5PO7~jEx|JNJoIz8xon)I(L z*17=VVYbD+D$RWX z9zkV$i37PGr)PT;f_NWak2=4*#`JJM4jdUbcATY6YOec{rF%6P1V2Tap@m&6YZSFiu^KsKtIYE72tq4sEG6MF(=*pt&FoIkrgLA;Nyx4U>TTrv$8l@<`GA#e|R@Y zeaW2opS986Vv@QnT54ZAR9@AtfBK)>xB_s@$6Q17prj#l@otMT&%qqBi5*pj@QDc0{z=;x@6x$*bS^&fr|!w3Qwf9%ahO1$`P)*h79s(OYRaqRsW12}fW5@?tD zM$(FpnoSTKH@%Ux$2=JP)V%}XMw%sdBr1;BFUkGG%h6wegNO}5j6ig`29w`6G^VzY zEb{Tr zUzLg!_rL0iW<$`|H@a3By|HdXf?>(vTj0SK(Bg$(4f$_9_0zb*+XjG47uJlG)NSf{ zWB`$s>D!g+zYLV?@BeuZ@4+-tKgFDO4m}28ZBzfJIvf2jAITQ`l7i1`y}qr~Enz2h zKH{n#hB(8L>kcI3KNURCBKJ*q@?8F3l`6dFboW8y&27)f?_YvZ|F)BzvvC=zy4fI7Z3f5N&YWEc1LnW_c$tF9pS8Bgxn(OJ^^d@f`H@ZaYPwEhp@Mu?Yd4Ti=P z2exerzd!$0x5@c`arWI&O*LJ=2?-(e-dljsdoNN#4^?^x0cp}iq$3b|hY*T%DWOW2 zB2AGdUAl;XigXZ_4x-*X&-cFTu66Ia|J=3Cn%T4WZ|~Ws&YYPu`#h)hw;mZAug)^y zFI4ui-cED2(EH%r6NAG6F)xmGwG~<&4uQJ-p7G7$H>*#uN=a;B)WSLI7vCm4> zEUBr^cvTg9*9KcisSDS0Pv%ftbTmv{r(>*^CnUmk4O!6CTBajrh9x>SkLeaA7d0$I z>7-LFpAMJgIgA@l8Z;_|XEoX^gw-_{a8A2ul%zB3y)SxCCLEKO_pJO7^Wl}G{$`+D z@+`TUks-~tP^`jsoXEXi(^o;g#S9Lm(2RWL>h-FN>F?Y^cun5yGVNRi@%Y)rxjqba z11`_;``6wpR!&d8^tNOnH|0gGW*9Jr#z=e?ejTsI#twUGH^=z0m10y zH#WBJz<^J~R~gI09P;8|dxi4)?cp4!@n*9*5)ZM8yilSM;VV^U(A$BHALOLYZ=^ADZ8G2anCZi%j>Uw zQVtRh*C}~{aE;lAC|K9gAh?-mXDcHxLY1|m)+trdn)tOs^hTqXT`aZ(-Xz2hwn~^8Re!!Vvlb4n370%dUTlS%d71A{ z=<>@}GPrp$Z&rJnPvkYx>t83Z`ddEqVxC_mwimQWS^GYjb<74*f&7AiEQKcb?j*eX znlRfrzNj_xgX!b;dC7P{m{R*AGiO$04y_jxZ{yOj^P#c#RO#&fJw{bgh1JaQ6*gAD zPCIWn=G$9%55*IrAk>2mso|~mre|WdSvw8osHmU@@g8`njH#LMc0QbSv=2VxRQ_d; zHFx=qfF}=QNwK-EzXd@z+58JA-aSEjv(WmoyM=pem`BB$3}xqgRO#GH^CU8IW`$Xg zq?z={Uz9v&7y|^k?vaF?A3}UL7G4vpQ#K~QiT=nFl$qheGkQai5QN?Kx;sA@Zc`N) z4*3@LzSIS}B|4Y+M72QTTX_*|%MVym*;OddoK&kzc($Mrn%$72t5BdvcOwF~L-i7kD{W3tBc83T+ecGUYB&^gSrU@{^i(N+FW= z5d?>Rt7w+y>imJ+4-anbmkmsdglc_qDh`e^rtt8pU78ok+;nCOzA`*pU|qzPnZBV= zh;+y%&-j`_cGAFY^=-Jpxj6cL3!WH@{%pHM<-{7zV6xF}TfDB}dTVo(v$KbHT%nMV z=}bK&l+Bv>YP!v=(q$r4U6ksgO{+iM>U1QL-9>XR{y0+C|u< z>sc9z#4i&?8F#B|JCg-dPyYDd(v36|^(l2srnJH})q7e~==6MG1p1La1))vD*K}^C zbREw}CesB2E-Cmz@s%Zj3y=6G(|APkwBxg4)%FmFY){6V7anveyH1@1nbC_whDQUK zsx66U+{;zqV>FohnR}s7XGuO@FC17c<<%pR9O3HqCdwe@ z0J$BuSX$g8EsZ#ZtiWru_{AMd0>i^PgSaowf;#mM#VYQX$N5d|DzrfvS56rlb6UOR z1O}{TpNttXZa$%slqjnbtfJH7$7yoDW?He?4IfAh4pifB zYkK5#91(V#?5wMQ*@WE}u*r}HDflsU^0!;4+2XxT!8*P)+wr7h&5gM~M&B+($IIGI zCLu{AgqYxO_v6Vy#3tt1aqx_iGHh}lPr&0PU4*1(%jRm z`+m^-#T|99%nK^9yXO9wP`t2IgB)4mi5L%^2Lq8SBbD<5d!=*Y4G!kz<3-wUAqf&e z4n5|p;U8aplpualr7eNJa$U|0O+*VN|gbwF3mov_|JYggank>iUSf3m(5C64mq zzPncJcvOA$@L}-xzN@?L?wR3#qEx9u{Dyl7#z`aD^Fdk`o1xQLs@gKfxsIA2tm&-m ztkq4^cM8K32okhL6TMkT0AsDjK_@v<`ja!v%m?gicQo1%Gdsee`yf9X-?z3585Ue1 z#)t&@K($3g7pn%MWt-Rp&N1Pd$K1t}qdyeoCtUc;L%!HO;SxxUY^Z<<|G&sdx#s z84FWWbJ#vq8n8W>$t7C|r_;V39Ycr)&fl_XPZ`{)Yf-=m35+IljmxJoL^t}<6=b>b zX?3I*99ty>-6y=yGpwSft<{Wz>8yy$5qh~I9hs`Jy-FW8l%q1?fW+sxyAf3h1$`MN!C%539SEGGh3jwi9XHDs4RKfv;kk%%XWEXU zXF&ZEF2n@0ajeknqsaAF)Zq9pu1nE#h+|4hkQK+)R03C4p4MdeBx{D_3O_XuL%0Uq%;9!4ot4$nVSy1o(E zBsho9uHEA?cD9&z(+aWSEXNh?8(UG zx%INLEIJu$QaS7?g=)QjEu*FW4Exsm2cmo8Awx?_o3qXl4~2Nu0H&~{#6SSstFxYu zH<@iFWjf_$fOtC2MWDN7wfIUz%sThYSp2~RJm5vy)Mn@_3D;U9xBZnb*6Kq)+>5*q zLcJ`+$AozltEAheL7=IO%+OWkeJTm!a@TiycN z)joFSMLnp*m82q9>F3)WLL`k+=5MHD_}kZ$H0>-%od*vb4}f=J1u+(jd-&pyzr0-g zW$}88zrdODsq=B)Key_=@ZH;A_e#F?e`~sZP4R#=xQU9m`1x+Hmv0Y%zrZtNy);v1 zrsZ$1!oulST$iw<_3CsDdL=E3{{*z zRNQkcU06(r_1)x2>kgy7bFYpVrcEd9D$B@rT%ohE&OoeWb<0}JJ(sGRe=sb`@;3>- zf{AVmfQebtLAG#pc(u@++b#4}+027%8{iN4%$qNvdW9~Q5gBaU7oxqjtE)X8_TCxh zcD$K~nzTocNa8l(^_ux=ExKuR7ttWj6_HS+a|K)3kvT5HjRofmuW zVNQf@mg=Y1MBPsztg$*m2Lma?cu#pr&E-{(AK>j8&P|p-QA|CPsToXjC;YI(MlM67 z9}4oqOU@NDu`|m!gxu>--&L+xXK;}9dFA{D>6i!KbZTQc20N~Xj0Qc1Vh+}&8>fw@6M%<}yywiP0MhJC)YY(lO} z*ajXanbDOxD)~U)`*CAT*>)4eu$Yx?{=Rf=iG96}m&I*gWmY(;2W=jfW<+Z}r3&T# zRoo(BYbg`yxcp#Ec3`%}5f}Rd!F>l0bSn|tbt@dGOJI{K%w0dZQ!n_RtdGk@I|d+Rr+OSo6XzZo+DySJx*OL40w-w|V$$7Rihe+Bs) z8fNu=()5>A;3hx*Fy!Dd?lNKSkI$2>qZ87*zSDZ}CZT5)O(}9yvYfiU+6ujLc076R65| z6b{OVD)NCjUh`Xn6XBVk>QIFEAjaNxnB8oReqBQ@H2|HN2P+0iLYHaO_X%yMFeV`v z2%Sp$XO6;G_IL#wxo&72)`qp?cD?4Dm@y%D*N}#99rK(JtHs?bLmn(bc&+-i+l42hT z9WsRkJ*@T)8Wafapb0K9LFDxr?xaZ zWqK#6s-+mSajrLkeX`(8J6kDjt$jnwBRG z@(ImULJf(fCH9jJkvcYi+CDOhzSK#7_j&-nV}{Q|7NtAx++jC8%U$%hs$@s>7QSyz zV?lpiY`1Ip**3E!ipKKtnO?Yi1KoRH*h4c|3r_DAA+}TW(|a1J9`!&bniATRW^c zQ1$(GI@~mBzi^x+%3UQpO8_F%F-GimYcnMA%;r9kI7n(QUc(R7s2tjim+-AP)-%fm z^)1Kdsmo0V9eZ4r#&{}GrSpJyYv^&wfY2<@f!^obz75EUn|{8 zkSnS+yNH@PefnUIs69RM$3h_x1UHT+umsaJ6DwV(XCqUBZ5(BOOjHL@D!u`^ngl zLP-{H2>UnhiBG1X_w=-VgnF8EJad;t)dq##D#$}2E@k)N#+LEwHO=Xg>1h1!>EIl+4xi*=}*-@nG8WCNEDg6s1SDw`?WZZ+%>bsKe3mddV#xs&s`RA=e8G+*&0 zRyU&Re8KFm?eD2+kB-wUd>=3w#TNF)#TU#f{aE(Td|lD{eC|oI;yGPq^YSG#ebIQ@ ze6u;$zWvpSda7`fAgU22vRwUis&Q=i1_nQ{{-JFEt+SAE|1DAUoR_1c;I5g!LT$8M z%bWL-A6Z_3PPjEw<;Af5;ua5n(-Du|azu51?2)Ak(rhlW?QveSaSejrb4W6BfUZBI zA1->$yKyUef`1Zo)qkxz7^;f*rVsW;DBsquK!!e5aKs;dh?Vdu7iyBCcgWM^liViR z_@nxLR@g^uS-TT%+7zwoWl}B67;xuuelb_XK#4cuRE;5&Bj9c%`c_Ft_{SP7`WALg zfK5D&2|3LiJDeoPL}_f?t{C`OEB$2~>!4%y5`L^(YAz4`;dAfVb!&q&zPsvA_Rdj* zHHt`03FXc4bWXy6?vY2`n(sP}B_37aW6BYdWx@Wnts=)y6w=V2J7EAN!@0oOUT61HZ&?8Io1-f z5K*j06y*i~SwcsNGjMStfYmAr3+!D51~qGKG)eofa(?-_6nQUVv$Do_J$vw>Ae2LK z^&H;%reUc6%&po(*xx_X>$$f#xM}~UuITtzYv09Bg1C$90t(vx6bEIj!LBdsMgO{; z@3OyUFMD{s?Hu)|kz!26KKizu8$j{#c7=57tuIU^DE7LQ8JshQEAPB*%3|-;iacVv z)^-8(3H?n{yZ%Kr@#(42pI33HS2(zwO3=`CFZ0Z&b-zEy36$21j`M5?nhQWo6T#;` zw&98BF}5TsKpHFM=LK#e=}kP&A0VpNWJ$LQu+u{a#~naX5*dT$nRoRthuFXhu)6sd zsug5sL$K2Bx3e7&`~mtA&gyv8!#+Sha1w~xJp?Sg|2^>XcHkvo4nYo}5k}QgC54eU zqytbxxD6&&qY6+#^;LlIQpu1)VHEk7FtP6_yGl?@x-a0X1S*tZn4J|J6#!zm1B36Q za?U2 zsNQg(Z5+LrRtS*)%|8r;?hnZ60zwqV9RegAAVvcIg&ZGCqpl@T*LZSWve+lMMNb>V zKl~ngxXeJY?tqy=zziVcJO0%R`p@qi24*%va#iFlnOBO}?@P#ABCY@*6aUQ~rjWO| zT>-oj$o-UVP@hTJ2QDL&mjpj1uI&FFXaMAeJ!8+OMSZ*hi0p%2V##N#A^=a%fOhi) zemd*}>rVi|r@*yo0zcXRS7EP8dEm)+&{Zz^ENuh|{}Vmr2Mgo}iARt!;>9UQA&!Q8diXMX=4AG31Vtu|J)skkUR$k`KdU*z5rc#MagU^VQR$+p0!zQ*^jX@ z$$$@7DbDhv)B#$9#;G1GjgG$X3S)zpoX({{AsSQ#bT1fnQ(-3w1e+n)z#5DrN(6TK z$XTiZG=K?;9=JV?vW{2YW~ZdO$%)cXUApjsuL=CUT8gs;9vf(Wv8exT`HzicR3S7( zPW{?)4hkB{DTB6D))NYXrYcarso)x%cG(K&PcKB0Jk@JR$ZsW-*{L@3YX~#~siDYu z!Qua`ElJAJu9C4ML8Iu}d{RXLCQ_T4s5WE3r}7S{$#|XnnwIf-{Y!4ZrI9{Wi!RWu zK_t&3Zca4C*;hohL0=@0tfe+A_gU8k9?kmm=Xv=3sNAL0Vt?H(ErFCxpc_%yj022r zg?L4ze?lqIVc_wed6R88aGNRh0O$wVdtb?X4LTAZs7coR14!U9pHrQv2t(5l;_klR zABcZ^m=sljTD?OYeg8=3nnS%qHB7o4XFS6XAZ1Kt#%rOl5YBd<(?y;wLMwA=TzP;j z+$ge)5oBGSK(40!wV7V5^4^++<7e#|PA;BE_ypmPi5tNF5CM{18FBOSwxSBvkEdw{ z{0*EsJ*cRn(54o<_fY!4Q)6RiX4$EDYG=A@y^G}K_fs{%ZW1uN+Ed9Py_w0e!1E-U zqfKO>5V!#mtGI;?xM^NkMN**d!<;OyRn3^$w1q$5m$h-SeY%w-@*^4kN{szJroxG; zv@_(F+@NFk6`_FTZjJS|c@~f0!g3-PfLEsGf)+?mw2Ja~!GZIv;+~{SbMpks;nZ+O zcgQ$lY>0)nlGXsM9TLOvL#J0Ds9pnYM9?OPF_k4 z`Qgcb>J8d3N8}Ai!~wpV?C@PZ~ZF`G)H$uLGXekyScxkqs26Ukcl)sO9R5!s0T0z_*$_Vo$ zA>%kTVnP|?JaWjmVi_Mh3G8F-BtfjkC?J^;a4(r1&3%g^`ApyfP=SH2fE(Wko^VGF zSZ)%8bg}=)y#nG8EqYf#`8VWagpmUxUkJ9qDlpzFU~e2dn)()1{F&g0DiUG%1LUs; zXwd@$c|0psz*d)%s|8j6mX&iK+EEx`fxR; z17HuqfKV>L|Cb0l#;l4nho!*B+Hpq?4KkQa`~a&4+F5&AhaO~Y;_0#X8@rU|D}X@Ah1vd z)Rq%gk1a)F9q$l7iBuf$D+P!Ph=|A&2Uw2+jszjFNd}bAeZU1R2<`eW8wVJZ_=i0< z?y%({>fcF^ivjVOpe6NRTwB^dQpbD$8XD91zp3GfIDj=zj$srHYX+pv6SVx90@0|p z--|T(9<4-=cVP$BQORI=s6EpK3Ks#FSQAir3%)z0gSS<5T z*fp-mPe6`&G#ra%`3YNMQAEyDl4AmgK@5+fP)1fv$S^3RD1aOjHXOUf_7k8+3*@L& zpU(^d+{-!!7HfjROW~>t8C&!}VT3qS2{>J69FpNDOh-!*HIjfr&vE?Y!5b2C#|{n{ z_%6y@3o#~uXv+&hw{x?>A0`v{dF->nA14#&*lz+|r0`-UxHu|t5!BZ~V-LV_4R`}D zG!bKaXgfYOI3rPxHK}f6UY4zafv*K0EpuKfsorP;3AI2#Jkr+6S(fhwbIvO1_%{f5 zaBN$zYW$0`TVJCX+1j8yGP#)mzPCjgNLvNJO=RNy$bM3L6-Y#eb%KGQDs2JCoJYo; z9@{pm`rvC>-y@?!N1mK-HAUG^H#D>+tO7=bH#f$|GkeLRG_GPZ3_ zb$_qyNkET(a17aYb1c=0t4oXNl05h^a`X=0Hfzo)nQqwAtaC$35ejkY3Cz2s|A9#S2K(N;F z*K+Lx0^#gs8F27oRvJpd1Y)Fp0y#i#B3Q>+kO*igMLCTQTP+tYC=lyXma)aLn57Y? zfJrveHlaLlIA3~_wHObMbEKto$~uY%V@p5681u8*lX2b}Yo|~jIBduB1iJqt7bA>(gf;jy=T&erZC0IQB<{RZ{Hi9W)Xmb!0>`D1wLawR<_v^K za{)PM1t|C_$G;#EMn6eiY$#&6Op>!uWpf-0F=Ii>SPOg%jfr-f`~gxyn{|A*mP>~& z5I$Lvfwf-DQcP9Aco<)D&>`44DLA@uf)6ny zX$YaAtj_OM=b5P}&JFaH`Dm%;Vn1_~{3gx3=2tkiH7Tm-B2SCLg z2pV=D?#c$_kWBv(3Fj)!zz#2D6^DT10$3<1(Cs)vpO_N{W5hW-fQw=Z#`%oI3ZU% zzaM)*Pp(!T3ys&3$l6B=NU)A;Y9Wk_9b5vS>QGJ+jwCTKoQv_FdKmC9Yx>%4MfLzDB&W3jG#NYWISr?7awd`=EMWdMlL2*{u@ zsZAq;ya$4FXxOL?b32Lv29sEbv}VBJz}1-I7)`P_obHbOD6<26@9Z&Sn=QDoKblTn z`w+ifQWPPX_4Vc01G_CT5q~;*`IOY+VNJ44&RxHL6#E>ph8O4C(5!{NF(-#Dp=SD) z2rk~xtk^yWR8%PNPkb?fRtAWxCOT_qV9eTai?4PS;}(mUKM8|;?;-wGFTI~3 zm-Zt`!%cy#)vWuyV|DiDi9uPCy$%DXN76Z8N#r(Ci`%B4(1?II<ZhP?^xHQsg+z9U*O2g#(%SH}s6b$O%~Uo5j5_5pF0Z58n1NzO3TyyKamg$aB6FC5)3kD9r#t~&hHvkfU z3F>5MY(@}BY;4Y8Ypc3`PjbtM(!UyM9>?TQL#dDq9f0j((0hvTdNPHNIM?3VpiPC5 zIqWT?XvsxnJIwzjX$l|MFNx)u7Fj1pVolbIhhuZH2vLM*17B)2IDh~fKx866LcB-j zfCxqqwZP6TTaYGjU9&(1BB2E8f$9?>L94SZC>Ph#8A(bdi&Gl5qLhO&7uk+Icp;7K z&JDgVc=`tBBr#?@V4U_E4EM!CC-+7l;mNT)XmB@VfG5xnaKLgi<%pzsNvPfve)hw* zK9hGsL#+UbC^O~F+Sy_;WHB#lC~GukCr;JwGk^?Ow?>TIMvl{oI!45Otd<6;JAnlp44WuSZqlp@~2o4qGlsHNqE(Rt1rMel3 z9$-f>s|uXM;=|AiO3DKV-$7?ISPZ$z7(w0Fub==5_Oe*mHVW;DK#IfI%W7cfF=&5P zWCxCpy#lZf6YRm15d=Shr9kWerAZI8&MuBdW2j2d6s0 zbq!zz^9OJJ$Fg}gxEB?+Wl6k3TrQ_I>ih5M*3g1f1P0LSkDRBL96*iA{Kst*eF%X(qYoO zw@1qM@J%sH|JXB!W^u+LN!3)!TCafV+yD|>&UT6-1kkJln6*ONWFhO2SQEGjp9D^Z6xPA!z)f_h_~DEIsXRbg zE9{&v%RdLFLx||E2D0EeP-E@5O&W3Ff2&Jz;M^AaZ*>AZCN$J_xG{|X(X5irswe<% z0i^w7nB>*ci_0PHg@CdpFrb;xWPqB#lM!&11HfHR(s!~jCCHH(91R92;q%`n=bVuM z!gK+%rg*4Qs{f$Oky)HrYcF($RP?**Mo#FzEtPt+FH{K$UqPseRYC;^Wp$c(P`cH@qC_bQaSrp}n4 zOvF!<3Ut|&a6lm*ga_nTRX*>)8{Rfonapn=bWN1gFFy2AL03w zTsQJZwuY41Nmoo-kV+Sp0RMv<%QTsJRA%PqDMokJ5+v4@dbQJY`>`lnqWP(WVlapBH z2}-#fAm{twSH_eZk`?n$qddNp6bA;MSPsqQ_U0_T6jxUdS01Fb`Y0|}CiIuHy)^V= z7oXp(!y8W45~8N>Evt`pCkE8;cwvIu_aMlgx?5PKMTvP}!x!lKtfyfT=K~V6*VOW! zrR-Z0fjQ4rDgAjwRG|J;{3>G~zZ7db&zcA8ox|&!)oOSK0Z=UqrR}IV%^#@Rgh{C& zoAESG?o+ded~hjsgJ`9tAQEg)d*N.WW#h*hpuH|RoB5WxG?yKVN34ID&uzA0-e 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
  • ^wp>dPe?OSvMbrwICHZZ9p?Z03gN$Ui8AMaR= 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 jWd-(HGSORM1!Alyuml4LAb bool: + truthy_values = {'', None, 'true', '1', 'on'} + return setting_value.lower() in truthy_values + + +def _get_setting_value( + config_settings: _ConfigDict | None = None, + config_setting_name: str | None = None, + env_var_name: str | None = None, + *, + default: bool = False, +) -> bool: + user_provided_setting_sources = ( + (config_settings, config_setting_name, (KeyError, TypeError)), + (os.environ, env_var_name, KeyError), + ) + for src_mapping, src_key, lookup_errors in user_provided_setting_sources: + if src_key is None: + continue + + with suppress(lookup_errors): # type: ignore[arg-type] + return _is_truthy_setting_value(src_mapping[src_key]) # type: ignore[index] + + return default + + +def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool: + return _get_setting_value( + config_settings, + PURE_PYTHON_CONFIG_SETTING, + PURE_PYTHON_ENV_VAR, + default=PURE_PYTHON_MODE_CLI_FALLBACK, + ) + + +def _include_cython_line_tracing( + config_settings: _ConfigDict | None = None, + *, + default=False, +) -> bool: + return _get_setting_value( + config_settings, + CYTHON_TRACING_CONFIG_SETTING, + CYTHON_TRACING_ENV_VAR, + default=default, + ) + + +@contextmanager +def patched_distutils_cmd_install(): + """Make `install_lib` of `install` cmd always use `platlib`. + + :yields: None + """ + # Without this, build_lib puts stuff under `*.data/purelib/` folder + orig_finalize = _distutils_install_cmd.finalize_options + + def new_finalize_options(self): # noqa: WPS430 + self.install_lib = self.install_platlib + orig_finalize(self) + + _distutils_install_cmd.finalize_options = new_finalize_options + try: + yield + finally: + _distutils_install_cmd.finalize_options = orig_finalize + + +@contextmanager +def patched_dist_has_ext_modules(): + """Make `has_ext_modules` of `Distribution` always return `True`. + + :yields: None + """ + # Without this, build_lib puts stuff under `*.data/platlib/` folder + orig_func = _DistutilsDistribution.has_ext_modules + + _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True + try: + yield + finally: + _DistutilsDistribution.has_ext_modules = orig_func + + +@contextmanager +def patched_dist_get_long_description(): + """Make `has_ext_modules` of `Distribution` always return `True`. + + :yields: None + """ + # Without this, build_lib puts stuff under `*.data/platlib/` folder + _orig_func = _DistutilsDistributionMetadata.get_long_description + + def _get_sanitized_long_description(self): + return sanitize_rst_roles(self.long_description) + + _DistutilsDistributionMetadata.get_long_description = ( + _get_sanitized_long_description + ) + try: + yield + finally: + _DistutilsDistributionMetadata.get_long_description = _orig_func + + +def _exclude_dir_path( + excluded_dir_path: Path, + visited_directory: str, + _visited_dir_contents: list[str], +) -> list[str]: + """Prevent recursive directory traversal.""" + # This stops the temporary directory from being copied + # into self recursively forever. + # Ref: https://github.com/aio-libs/yarl/issues/992 + visited_directory_subdirs_to_ignore = [ + subdir + for subdir in _visited_dir_contents + if excluded_dir_path == Path(visited_directory) / subdir + ] + if visited_directory_subdirs_to_ignore: + print( + f'Preventing `{excluded_dir_path !s}` from being ' + 'copied into itself recursively...', + file=_standard_error_stream, + ) + return visited_directory_subdirs_to_ignore + + +@contextmanager +def _in_temporary_directory(src_dir: Path) -> t.Iterator[None]: + with TemporaryDirectory(prefix='.tmp-aiohttp_asyncmdnsresolver-pep517-') as tmp_dir: + tmp_dir_path = Path(tmp_dir) + root_tmp_dir_path = tmp_dir_path.parent + _exclude_tmpdir_parent = partial(_exclude_dir_path, root_tmp_dir_path) + + with chdir_cm(tmp_dir): + tmp_src_dir = tmp_dir_path / 'src' + copytree( + src_dir, + tmp_src_dir, + ignore=_exclude_tmpdir_parent, + symlinks=True, + ) + os.chdir(tmp_src_dir) + yield + + +@contextmanager +def maybe_prebuild_c_extensions( + line_trace_cython_when_unset: bool = False, + build_inplace: bool = False, + config_settings: _ConfigDict | None = None, +) -> t.Generator[None, t.Any, t.Any]: + """Pre-build C-extensions in a temporary directory, when needed. + + This context manager also patches metadata, setuptools and distutils. + + :param build_inplace: Whether to copy and chdir to a temporary location. + :param config_settings: :pep:`517` config settings mapping. + + """ + cython_line_tracing_requested = _include_cython_line_tracing( + config_settings, + default=line_trace_cython_when_unset, + ) + is_pure_python_build = _make_pure_python(config_settings) + + if is_pure_python_build: + print("*********************", file=_standard_error_stream) + print("* Pure Python build *", file=_standard_error_stream) + print("*********************", file=_standard_error_stream) + + if cython_line_tracing_requested: + _warn_that( + f'The `{CYTHON_TRACING_CONFIG_SETTING !s}` setting requesting ' + 'Cython line tracing is set, but building C-extensions is not. ' + 'This option will not have any effect for in the pure-python ' + 'build mode.', + RuntimeWarning, + stacklevel=999, + ) + + yield + return + + print("**********************", file=_standard_error_stream) + print("* Accelerated build *", file=_standard_error_stream) + print("**********************", file=_standard_error_stream) + if not IS_CPYTHON: + _warn_that( + 'Building C-extensions under the runtimes other than CPython is ' + 'unsupported and will likely fail. Consider passing the ' + f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.', + RuntimeWarning, + stacklevel=999, + ) + + build_dir_ctx = ( + nullcontext() if build_inplace + else _in_temporary_directory(src_dir=Path.cwd().resolve()) + ) + with build_dir_ctx: + config = _get_local_cython_config() + + cythonize_args = _make_cythonize_cli_args_from_config(config) + with _patched_cython_env(config['env'], cython_line_tracing_requested): + _cythonize_cli_cmd(cythonize_args) + with patched_distutils_cmd_install(): + with patched_dist_has_ext_modules(): + yield + + +@patched_dist_get_long_description() +def build_wheel( + wheel_directory: str, + config_settings: _ConfigDict | None = None, + metadata_directory: str | None = None, +) -> str: + """Produce a built wheel. + + This wraps the corresponding ``setuptools``' build backend hook. + + :param wheel_directory: Directory to put the resulting wheel in. + :param config_settings: :pep:`517` config settings mapping. + :param metadata_directory: :file:`.dist-info` directory path. + + """ + with maybe_prebuild_c_extensions( + line_trace_cython_when_unset=False, + build_inplace=False, + config_settings=config_settings, + ): + return _setuptools_build_wheel( + wheel_directory=wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + + +@patched_dist_get_long_description() +def build_editable( + wheel_directory: str, + config_settings: _ConfigDict | None = None, + metadata_directory: str | None = None, +) -> str: + """Produce a built wheel for editable installs. + + This wraps the corresponding ``setuptools``' build backend hook. + + :param wheel_directory: Directory to put the resulting wheel in. + :param config_settings: :pep:`517` config settings mapping. + :param metadata_directory: :file:`.dist-info` directory path. + + """ + with maybe_prebuild_c_extensions( + line_trace_cython_when_unset=True, + build_inplace=True, + config_settings=config_settings, + ): + return _setuptools_build_editable( + wheel_directory=wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + + +def get_requires_for_build_wheel( + config_settings: _ConfigDict | None = None, +) -> list[str]: + """Determine additional requirements for building wheels. + + :param config_settings: :pep:`517` config settings mapping. + + """ + is_pure_python_build = _make_pure_python(config_settings) + + if not is_pure_python_build and not IS_CPYTHON: + _warn_that( + 'Building C-extensions under the runtimes other than CPython is ' + 'unsupported and will likely fail. Consider passing the ' + f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.', + RuntimeWarning, + stacklevel=999, + ) + + c_ext_build_deps = [] if is_pure_python_build else [ + 'Cython ~= 3.0.0; python_version >= "3.12"', + 'Cython; python_version < "3.12"', + ] + + return _setuptools_get_requires_for_build_wheel( + config_settings=config_settings, + ) + c_ext_build_deps + + +build_sdist = patched_dist_get_long_description()(_setuptools_build_sdist) +get_requires_for_build_editable = get_requires_for_build_wheel +prepare_metadata_for_build_wheel = patched_dist_get_long_description()( + _setuptools_prepare_metadata_for_build_wheel, +) +prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel diff --git a/packaging/pep517_backend/_compat.py b/packaging/pep517_backend/_compat.py new file mode 100644 index 0000000..dccada6 --- /dev/null +++ b/packaging/pep517_backend/_compat.py @@ -0,0 +1,33 @@ +"""Cross-python stdlib shims.""" + +import os +import typing as t +from contextlib import contextmanager +from pathlib import Path + +# isort: off +try: + from contextlib import chdir as chdir_cm # type: ignore[attr-defined, unused-ignore] # noqa: E501 +except ImportError: + + @contextmanager # type: ignore[no-redef, unused-ignore] + def chdir_cm(path: os.PathLike) -> t.Iterator[None]: + """Temporarily change the current directory, recovering on exit.""" + original_wd = Path.cwd() + os.chdir(path) + try: + yield + finally: + os.chdir(original_wd) + + +# isort: on + + +try: + from tomllib import loads as load_toml_from_string +except ImportError: + from tomli import loads as load_toml_from_string + + +__all__ = ("chdir_cm", "load_toml_from_string") # noqa: WPS410 diff --git a/packaging/pep517_backend/_cython_configuration.py b/packaging/pep517_backend/_cython_configuration.py new file mode 100644 index 0000000..316b85f --- /dev/null +++ b/packaging/pep517_backend/_cython_configuration.py @@ -0,0 +1,107 @@ +# fmt: off + +from __future__ import annotations + +import os +from contextlib import contextmanager +from pathlib import Path +from sys import version_info as _python_version_tuple + +from expandvars import expandvars + +from ._compat import load_toml_from_string # noqa: WPS436 +from ._transformers import ( # noqa: WPS436 + get_cli_kwargs_from_config, + get_enabled_cli_flags_from_config, +) + + +def get_local_cython_config() -> dict: + """Grab optional build dependencies from pyproject.toml config. + + :returns: config section from ``pyproject.toml`` + :rtype: dict + + This basically reads entries from:: + + [tool.local.cythonize] + # Env vars provisioned during cythonize call + src = ["src/**/*.pyx"] + + [tool.local.cythonize.env] + # Env vars provisioned during cythonize call + LDFLAGS = "-lssh" + + [tool.local.cythonize.flags] + # This section can contain the following booleans: + # * annotate — generate annotated HTML page for source files + # * build — build extension modules using distutils + # * inplace — build extension modules in place using distutils (implies -b) + # * force — force recompilation + # * quiet — be less verbose during compilation + # * lenient — increase Python compat by ignoring some compile time errors + # * keep-going — compile as much as possible, ignore compilation failures + annotate = false + build = false + inplace = true + force = true + quiet = false + lenient = false + keep-going = false + + [tool.local.cythonize.kwargs] + # This section can contain args that have values: + # * exclude=PATTERN exclude certain file patterns from the compilation + # * parallel=N run builds in N parallel jobs (default: calculated per system) + exclude = "**.py" + parallel = 12 + + [tool.local.cythonize.kwargs.directives] + # This section can contain compiler directives + # NAME = "VALUE" + + [tool.local.cythonize.kwargs.compile-time-env] + # This section can contain compile time env vars + # NAME = "VALUE" + + [tool.local.cythonize.kwargs.options] + # This section can contain cythonize options + # NAME = "VALUE" + """ + config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text() + config_mapping = load_toml_from_string(config_toml_txt) + return config_mapping['tool']['local']['cythonize'] + + +def make_cythonize_cli_args_from_config(config) -> list[str]: + py_ver_arg = f'-{_python_version_tuple.major!s}' + + cli_flags = get_enabled_cli_flags_from_config(config['flags']) + cli_kwargs = get_cli_kwargs_from_config(config['kwargs']) + + return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src'] + + +@contextmanager +def patched_env(env: dict[str, str], cython_line_tracing_requested: bool): + """Temporary set given env vars. + + :param env: tmp env vars to set + :type env: dict + + :yields: None + """ + orig_env = os.environ.copy() + expanded_env = {name: expandvars(var_val) for name, var_val in env.items()} + os.environ.update(expanded_env) + + if cython_line_tracing_requested: + os.environ['CFLAGS'] = ' '.join(( + os.getenv('CFLAGS', ''), + '-DCYTHON_TRACE_NOGIL=1', # Implies CYTHON_TRACE=1 + )).strip() + try: + yield + finally: + os.environ.clear() + os.environ.update(orig_env) diff --git a/packaging/pep517_backend/_transformers.py b/packaging/pep517_backend/_transformers.py new file mode 100644 index 0000000..463dcec --- /dev/null +++ b/packaging/pep517_backend/_transformers.py @@ -0,0 +1,107 @@ +"""Data conversion helpers for the in-tree PEP 517 build backend.""" + +from itertools import chain +from re import sub as _substitute_with_regexp + + +def _emit_opt_pairs(opt_pair): + flag, flag_value = opt_pair + flag_opt = f"--{flag!s}" + if isinstance(flag_value, dict): + sub_pairs = flag_value.items() + else: + sub_pairs = ((flag_value,),) + + yield from ("=".join(map(str, (flag_opt,) + pair)) for pair in sub_pairs) + + +def get_cli_kwargs_from_config(kwargs_map): + """Make a list of options with values from config.""" + return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items()))) + + +def get_enabled_cli_flags_from_config(flags_map): + """Make a list of enabled boolean flags from config.""" + return [f"--{flag}" for flag, is_enabled in flags_map.items() if is_enabled] + + +def sanitize_rst_roles(rst_source_text: str) -> str: + """Replace RST roles with inline highlighting.""" + pep_role_regex = r"""(?x) + :pep:`(?P\d+)` + """ + pep_substitution_pattern = ( + r"`PEP \g >`__" + ) + + user_role_regex = r"""(?x) + :user:`(?P[^`]+)(?:\s+(.*))?` + """ + user_substitution_pattern = ( + r"`@\g " + r">`__" + ) + + issue_role_regex = r"""(?x) + :issue:`(?P[^`]+)(?:\s+(.*))?` + """ + issue_substitution_pattern = ( + r"`#\g " + r">`__" + ) + + pr_role_regex = r"""(?x) + :pr:`(?P[^`]+)(?:\s+(.*))?` + """ + pr_substitution_pattern = ( + r"`PR #\g " + r">`__" + ) + + commit_role_regex = r"""(?x) + :commit:`(?P[^`]+)(?:\s+(.*))?` + """ + commit_substitution_pattern = ( + r"`\g " + r">`__" + ) + + gh_role_regex = r"""(?x) + :gh:`(?P[^`<]+)(?:\s+([^`]*))?` + """ + gh_substitution_pattern = r"GitHub: ``\g``" + + meth_role_regex = r"""(?x) + (?::py)?:meth:`~?(?P[^`<]+)(?:\s+([^`]*))?` + """ + meth_substitution_pattern = r"``\g()``" + + role_regex = r"""(?x) + (?::\w+)?:\w+:`(?P[^`<]+)(?:\s+([^`]*))?` + """ + substitution_pattern = r"``\g``" + + project_substitution_regex = r"\|project\|" + project_substitution_pattern = "aiohttp_asyncmdnsresolver" + + substitutions = ( + (pep_role_regex, pep_substitution_pattern), + (user_role_regex, user_substitution_pattern), + (issue_role_regex, issue_substitution_pattern), + (pr_role_regex, pr_substitution_pattern), + (commit_role_regex, commit_substitution_pattern), + (gh_role_regex, gh_substitution_pattern), + (meth_role_regex, meth_substitution_pattern), + (role_regex, substitution_pattern), + (project_substitution_regex, project_substitution_pattern), + ) + + rst_source_normalized_text = rst_source_text + for regex, substitution in substitutions: + rst_source_normalized_text = _substitute_with_regexp( + regex, + substitution, + rst_source_normalized_text, + ) + + return rst_source_normalized_text diff --git a/packaging/pep517_backend/cli.py b/packaging/pep517_backend/cli.py new file mode 100644 index 0000000..f3a1c85 --- /dev/null +++ b/packaging/pep517_backend/cli.py @@ -0,0 +1,53 @@ +# fmt: off + +from __future__ import annotations + +import sys +from itertools import chain +from pathlib import Path + +from Cython.Compiler.Main import compile as _translate_cython_cli_cmd +from Cython.Compiler.Main import parse_command_line as _split_cython_cli_args + +from ._cython_configuration import get_local_cython_config as _get_local_cython_config +from ._cython_configuration import ( + make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config, +) +from ._cython_configuration import patched_env as _patched_cython_env + +_PROJECT_PATH = Path(__file__).parents[2] + + +def run_main_program(argv) -> int | str: + """Invoke ``translate-cython`` or fail.""" + if len(argv) != 2: + return 'This program only accepts one argument -- "translate-cython"' + + if argv[1] != 'translate-cython': + return 'This program only implements the "translate-cython" subcommand' + + config = _get_local_cython_config() + config['flags'] = {'keep-going': config['flags']['keep-going']} + config['src'] = list( + map( + str, + chain.from_iterable( + map(_PROJECT_PATH.glob, config['src']), + ), + ), + ) + translate_cython_cli_args = _make_cythonize_cli_args_from_config(config) + + cython_options, cython_sources = _split_cython_cli_args( + translate_cython_cli_args, + ) + + with _patched_cython_env(config['env'], cython_line_tracing_requested=True): + return _translate_cython_cli_cmd( + cython_sources, + cython_options, + ).num_errors + + +if __name__ == '__main__': + sys.exit(run_main_program(argv=sys.argv)) diff --git a/packaging/pep517_backend/hooks.py b/packaging/pep517_backend/hooks.py new file mode 100644 index 0000000..5fa77fe --- /dev/null +++ b/packaging/pep517_backend/hooks.py @@ -0,0 +1,21 @@ +"""PEP 517 build backend for optionally pre-building Cython.""" + +from contextlib import suppress as _suppress + +from setuptools.build_meta import * # Re-exporting PEP 517 hooks # pylint: disable=unused-wildcard-import,wildcard-import # noqa: E501, F401, F403 + +# Re-exporting PEP 517 hooks +from ._backend import ( # type: ignore[assignment] # noqa: WPS436 + build_sdist, + build_wheel, + get_requires_for_build_wheel, + prepare_metadata_for_build_wheel, +) + +with _suppress(ImportError): # Only succeeds w/ setuptools implementing PEP 660 + # Re-exporting PEP 660 hooks + from ._backend import ( # type: ignore[assignment] # noqa: WPS436 + build_editable, + get_requires_for_build_editable, + prepare_metadata_for_build_editable, + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4d7b413 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,96 @@ +[build-system] +requires = [ + # NOTE: The following build dependencies are necessary for initial + # NOTE: provisioning of the in-tree build backend located under + # NOTE: `packaging/pep517_backend/`. + "expandvars", + "setuptools >= 47", # Minimum required for `version = attr:` + "tomli; python_version < '3.11'", +] +backend-path = ["packaging"] # requires `pip >= 20` or `pep517 >= 0.6.0` +build-backend = "pep517_backend.hooks" # wraps `setuptools.build_meta` + +[tool.local.cythonize] +# This attr can contain multiple globs +src = ["src/aiohttp_asyncmdnsresolver/*.pyx"] + +[tool.local.cythonize.env] +# Env vars provisioned during cythonize call +#CFLAGS = "-DCYTHON_TRACE=1 ${CFLAGS}" +#LDFLAGS = "${LDFLAGS}" + +[tool.local.cythonize.flags] +# This section can contain the following booleans: +# * annotate — generate annotated HTML page for source files +# * build — build extension modules using distutils +# * inplace — build extension modules in place using distutils (implies -b) +# * force — force recompilation +# * quiet — be less verbose during compilation +# * lenient — increase Python compat by ignoring some compile time errors +# * keep-going — compile as much as possible, ignore compilation failures +annotate = false +build = false +inplace = true +force = true +quiet = false +lenient = false +keep-going = false + +[tool.local.cythonize.kwargs] +# This section can contain args that have values: +# * exclude=PATTERN exclude certain file patterns from the compilation +# * parallel=N run builds in N parallel jobs (default: calculated per system) +# exclude = "**.py" +# parallel = 12 + +[tool.local.cythonize.kwargs.directive] +# This section can contain compiler directives. Ref: +# https://cython.rtfd.io/en/latest/src/userguide/source_files_and_compilation.html#compiler-directives +embedsignature = "True" +emit_code_comments = "True" +linetrace = "True" # Implies `profile=True` + +[tool.local.cythonize.kwargs.compile-time-env] +# This section can contain compile time env vars + +[tool.local.cythonize.kwargs.option] +# This section can contain cythonize options +# Ref: https://github.com/cython/cython/blob/d6e6de9/Cython/Compiler/Options.py#L694-L730 +#docstrings = "True" +#embed_pos_in_docstring = "True" +#warning_errors = "True" +#error_on_unknown_names = "True" +#error_on_uninitialized = "True" + +[tool.cibuildwheel] +build-frontend = "build" +before-test = [ + # NOTE: Attempt to have pip pre-compile PyYAML wheel with our build + # NOTE: constraints unset. The hope is that pip will cache that wheel + # NOTE: and the test env provisioning stage will pick up PyYAML from + # NOTE: said cache rather than attempting to build it with a conflicting. + # NOTE: Version of Cython. + # Ref: https://github.com/pypa/cibuildwheel/issues/1666 + "PIP_CONSTRAINT= pip install PyYAML", +] +test-requires = "-r requirements/test.txt" +test-command = "pytest -v --no-cov {project}/tests" +# don't build PyPy wheels, install from source instead +skip = "pp*" + +[tool.cibuildwheel.environment] +COLOR = "yes" +FORCE_COLOR = "1" +MYPY_FORCE_COLOR = "1" +PIP_CONSTRAINT = "requirements/cython.txt" +PRE_COMMIT_COLOR = "always" +PY_COLORS = "1" + +[tool.cibuildwheel.config-settings] +pure-python = "false" + +[tool.cibuildwheel.windows] +before-test = [] # Windows cmd has different syntax and pip chooses wheels + +[tool.cibuildwheel.linux] +before-all = "yum install -y libffi-devel || apk add --upgrade libffi-dev || apt-get install libffi-dev" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..b58e30c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,89 @@ +[pytest] +addopts = + # `pytest-xdist`: + --numprocesses=auto + # NOTE: the plugin disabled because it's slower with so few tests + --numprocesses=0 + + # Show 10 slowest invocations: + --durations=10 + + # Report all the things == -rxXs: + -ra + + # Show values of the local vars in errors/tracebacks: + --showlocals + + # Autocollect and invoke the doctests from all modules: + # https://docs.pytest.org/en/stable/doctest.html + --doctest-modules + + # Pre-load the `pytest-cov` plugin early: + -p pytest_cov + + # `pytest-cov`: + --cov + --cov-config=.coveragerc + --cov-context=test + --no-cov-on-fail + + # Fail on config parsing warnings: + # --strict-config + + # Fail on non-existing markers: + # * Deprecated since v6.2.0 but may be reintroduced later covering a + # broader scope: + # --strict + # * Exists since v4.5.0 (advised to be used instead of `--strict`): + --strict-markers + +doctest_optionflags = ALLOW_UNICODE ELLIPSIS + +# Marks tests with an empty parameterset as xfail(run=False) +empty_parameter_set_mark = xfail + +faulthandler_timeout = 30 + +filterwarnings = + error + + # FIXME: drop this once `pytest-cov` is updated. + # Ref: https://github.com/pytest-dev/pytest-cov/issues/557 + ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning + + # https://github.com/pytest-dev/pytest/issues/10977 and https://github.com/pytest-dev/pytest/pull/10894 + ignore:ast\.(Num|NameConstant|Str) is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning:_pytest + ignore:Attribute s is deprecated and will be removed in Python 3\.14; use value instead:DeprecationWarning:_pytest + +# https://docs.pytest.org/en/stable/usage.html#creating-junitxml-format-files +junit_duration_report = call +# xunit1 contains more metadata than xunit2 so it's better for CI UIs: +junit_family = xunit1 +junit_logging = all +junit_log_passing_tests = true +junit_suite_name = aiohttp_asyncmdnsresolver_test_suite + +# A mapping of markers to their descriptions allowed in strict mode: +markers = + +minversion = 3.8.2 + +# Optimize pytest's lookup by restricting potentially deep dir tree scan: +norecursedirs = + build + dist + docs + requirements + venv + virtualenv + aiohttp_asyncmdnsresolver.egg-info + .cache + .eggs + .git + .github + .tox + *.egg + +testpaths = tests/ + +xfail_strict = true diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 0000000..2a4069d --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,2 @@ +-r test.txt +-r towncrier.txt diff --git a/requirements/doc-spelling.txt b/requirements/doc-spelling.txt new file mode 100644 index 0000000..3bb4a55 --- /dev/null +++ b/requirements/doc-spelling.txt @@ -0,0 +1,2 @@ +-r doc.txt +sphinxcontrib-spelling==8.0.1; platform_system!="Windows" # We only use it in Azure CI diff --git a/requirements/doc.txt b/requirements/doc.txt new file mode 100644 index 0000000..c72a429 --- /dev/null +++ b/requirements/doc.txt @@ -0,0 +1,4 @@ +-r towncrier.txt +myst-parser >= 0.10.0 +sphinx==8.1.3 +sphinxcontrib-towncrier diff --git a/requirements/lint.txt b/requirements/lint.txt new file mode 100644 index 0000000..e88d271 --- /dev/null +++ b/requirements/lint.txt @@ -0,0 +1 @@ +pre-commit==4.0.1 diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 0000000..abf4216 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,6 @@ +-r cython.txt +covdefaults +pytest==8.3.4 +pytest-codspeed==3.1.0 +pytest-cov>=2.3.1 +pytest-xdist diff --git a/requirements/towncrier.txt b/requirements/towncrier.txt new file mode 100644 index 0000000..409f3a3 --- /dev/null +++ b/requirements/towncrier.txt @@ -0,0 +1 @@ +towncrier==23.11.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3adec52 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,96 @@ +[bdist_wheel] +# wheels should be OS-specific: +# their names must contain macOS/manulinux1/2010/2014/Windows identifiers +universal = 0 + +[metadata] +name = aiohttp_asyncmdnsresolver +version = attr: aiohttp_asyncmdnsresolver.__version__ +url = https://github.com/aio-libs/aiohttp_asyncmdnsresolver +project_urls = + Chat: Matrix = https://matrix.to/#/#aio-libs:matrix.org + Chat: Matrix Space = https://matrix.to/#/#aio-libs-space:matrix.org + CI: GitHub Workflows = https://github.com/aio-libs/aiohttp_asyncmdnsresolver/actions?query=branch:main + Code of Conduct = https://github.com/aio-libs/.github/blob/main/CODE_OF_CONDUCT.md + Coverage: codecov = https://codecov.io/github/aio-libs/aiohttp_asyncmdnsresolver + Docs: Changelog = https://aiohttp_asyncmdnsresolver.readthedocs.io/en/latest/changes/ + Docs: RTD = https://aiohttp_asyncmdnsresolver.readthedocs.io + GitHub: issues = https://github.com/aio-libs/aiohttp_asyncmdnsresolver/issues + GitHub: repo = https://github.com/aio-libs/aiohttp_asyncmdnsresolver +description = Accelerated property cache +long_description = file: README.rst, CHANGES.rst +long_description_content_type = text/x-rst +author = Andrew Svetlov +author_email = andrew.svetlov@gmail.com +maintainer = aiohttp team +maintainer_email = team@aiohttp.org +license = Apache-2.0 +license_files = + LICENSE + NOTICE +classifiers = + Development Status :: 5 - Production/Stable + + Intended Audience :: Developers + + License :: OSI Approved :: Apache Software License + + Programming Language :: Cython + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 + + Topic :: Internet :: WWW/HTTP + Topic :: Software Development :: Libraries :: Python Modules +keywords = + cython + cext + aiohttp_asyncmdnsresolver + +[options] +python_requires = >=3.9 +# Ref: +# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#using-a-src-layout +# (`src/` layout) +package_dir = + =src +packages = + aiohttp_asyncmdnsresolver + +# https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html +zip_safe = False +include_package_data = True + +[options.package_data] +# Ref: +# https://setuptools.pypa.io/en/latest/userguide/datafiles.html#package-data +# (see notes for the asterisk/`*` meaning) +* = + *.so + *.pyd + *.pyx + +[options.exclude_package_data] +* = + *.c + *.h + +[pep8] +max-line-length=79 + +[flake8] +ignore = E203,E301,E302,E704,W503,W504,F811 +max-line-length = 88 + +# Allow certain violations in certain files: +per-file-ignores = + + # F401 imported but unused + packaging/pep517_backend/hooks.py: F401 + +[isort] +profile=black diff --git a/src/aiohttp_asyncmdnsresolver/__init__.py b/src/aiohttp_asyncmdnsresolver/__init__.py new file mode 100644 index 0000000..d35bcc6 --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/__init__.py @@ -0,0 +1,32 @@ +"""aiohttp_asyncmdnsresolver: An accelerated property cache for Python classes.""" + +from typing import TYPE_CHECKING + +_PUBLIC_API = ("cached_property", "under_cached_property") + +__version__ = "0.2.2.dev0" +__all__ = () + +# Imports have moved to `aiohttp_asyncmdnsresolver.api` in 0.2.0+. +# This module is now a facade for the API. +if TYPE_CHECKING: + from .api import cached_property as cached_property # noqa: F401 + from .api import under_cached_property as under_cached_property # noqa: F401 + + +def _import_facade(attr: str) -> object: + """Import the public API from the `api` module.""" + if attr in _PUBLIC_API: + from . import api # pylint: disable=import-outside-toplevel + + return getattr(api, attr) + raise AttributeError(f"module '{__package__}' has no attribute '{attr}'") + + +def _dir_facade() -> list[str]: + """Include the public API in the module's dir() output.""" + return [*_PUBLIC_API, *globals().keys()] + + +__getattr__ = _import_facade +__dir__ = _dir_facade diff --git a/src/aiohttp_asyncmdnsresolver/_helpers.py b/src/aiohttp_asyncmdnsresolver/_helpers.py new file mode 100644 index 0000000..99cadfd --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/_helpers.py @@ -0,0 +1,39 @@ +import os +import sys +from typing import TYPE_CHECKING + +__all__ = ("cached_property", "under_cached_property") + + +NO_EXTENSIONS = bool(os.environ.get("PROPCACHE_NO_EXTENSIONS")) # type: bool +if sys.implementation.name != "cpython": + NO_EXTENSIONS = True + + +# isort: off +if TYPE_CHECKING: + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py + under_cached_property = under_cached_property_py +elif not NO_EXTENSIONS: # pragma: no branch + try: + from ._helpers_c import cached_property as cached_property_c # type: ignore[attr-defined, unused-ignore] # noqa: E501 + from ._helpers_c import under_cached_property as under_cached_property_c # type: ignore[attr-defined, unused-ignore] # noqa: E501 + + cached_property = cached_property_c + under_cached_property = under_cached_property_c + except ImportError: # pragma: no cover + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py # type: ignore[assignment, misc] + under_cached_property = under_cached_property_py +else: + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py # type: ignore[assignment, misc] + under_cached_property = under_cached_property_py +# isort: on diff --git a/src/aiohttp_asyncmdnsresolver/_helpers_c.pyx b/src/aiohttp_asyncmdnsresolver/_helpers_c.pyx new file mode 100644 index 0000000..0c42ff3 --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/_helpers_c.pyx @@ -0,0 +1,84 @@ +# cython: language_level=3 +from types import GenericAlias + + +cdef _sentinel = object() + +cdef class under_cached_property: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef readonly object wrapped + cdef object name + + def __init__(self, wrapped): + self.wrapped = wrapped + self.name = wrapped.__name__ + + @property + def __doc__(self): + return self.wrapped.__doc__ + + def __get__(self, inst, owner): + if inst is None: + return self + cdef dict cache = inst._cache + val = cache.get(self.name, _sentinel) + if val is _sentinel: + val = self.wrapped(inst) + cache[self.name] = val + return val + + def __set__(self, inst, value): + raise AttributeError("cached property is read-only") + + +cdef class cached_property: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef readonly object func + cdef object name + + def __init__(self, func): + self.func = func + self.name = None + + @property + def __doc__(self): + return self.func.__doc__ + + def __set_name__(self, owner, name): + if self.name is None: + self.name = name + elif name != self.name: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.name!r} and {name!r})." + ) + + def __get__(self, inst, owner): + if inst is None: + return self + if self.name is None: + raise TypeError( + "Cannot use cached_property instance" + " without calling __set_name__ on it.") + cdef dict cache = inst.__dict__ + val = cache.get(self.name, _sentinel) + if val is _sentinel: + val = self.func(inst) + cache[self.name] = val + return val + + __class_getitem__ = classmethod(GenericAlias) diff --git a/src/aiohttp_asyncmdnsresolver/_helpers_py.py b/src/aiohttp_asyncmdnsresolver/_helpers_py.py new file mode 100644 index 0000000..2f3e688 --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/_helpers_py.py @@ -0,0 +1,56 @@ +"""Various helper functions.""" + +import sys +from functools import cached_property +from typing import Any, Callable, Generic, Optional, Protocol, TypeVar, Union, overload + +__all__ = ("under_cached_property", "cached_property") + + +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = Any + +_T = TypeVar("_T") + + +class _TSelf(Protocol, Generic[_T]): + _cache: dict[str, _T] + + +class under_cached_property(Generic[_T]): + """Use as a class method decorator. + + It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + """ + + def __init__(self, wrapped: Callable[..., _T]) -> None: + self.wrapped = wrapped + self.__doc__ = wrapped.__doc__ + self.name = wrapped.__name__ + + @overload + def __get__(self, inst: None, owner: Optional[type[object]] = None) -> Self: ... + + @overload + def __get__(self, inst: _TSelf[_T], owner: Optional[type[object]] = None) -> _T: ... + + def __get__( + self, inst: Optional[_TSelf[_T]], owner: Optional[type[object]] = None + ) -> Union[_T, Self]: + if inst is None: + return self + try: + return inst._cache[self.name] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + + def __set__(self, inst: _TSelf[_T], value: _T) -> None: + raise AttributeError("cached property is read-only") diff --git a/src/aiohttp_asyncmdnsresolver/api.py b/src/aiohttp_asyncmdnsresolver/api.py new file mode 100644 index 0000000..22389e6 --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/api.py @@ -0,0 +1,8 @@ +"""Public API of the property caching library.""" + +from ._helpers import cached_property, under_cached_property + +__all__ = ( + "cached_property", + "under_cached_property", +) diff --git a/src/aiohttp_asyncmdnsresolver/py.typed b/src/aiohttp_asyncmdnsresolver/py.typed new file mode 100644 index 0000000..dcf2c80 --- /dev/null +++ b/src/aiohttp_asyncmdnsresolver/py.typed @@ -0,0 +1 @@ +# Placeholder diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..3cb8e45 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,115 @@ +import argparse +from dataclasses import dataclass +from functools import cached_property +from importlib import import_module +from types import ModuleType + +import pytest + +C_EXT_MARK = pytest.mark.c_extension + + +@dataclass(frozen=True) +class PropcacheImplementation: + """A facade for accessing importable aiohttp_asyncmdnsresolver module variants. + + An instance essentially represents a c-extension or a pure-python module. + The actual underlying module is accessed dynamically through a property and + is cached. + + It also has a text tag depending on what variant it is, and a string + representation suitable for use in Pytest's test IDs via parametrization. + """ + + is_pure_python: bool + """A flag showing whether this is a pure-python module or a C-extension.""" + + @cached_property + def tag(self) -> str: + """Return a text representation of the pure-python attribute.""" + return "pure-python" if self.is_pure_python else "c-extension" + + @cached_property + def imported_module(self) -> ModuleType: + """Return a loaded importable containing a aiohttp_asyncmdnsresolver variant.""" + importable_module = "_helpers_py" if self.is_pure_python else "_helpers_c" + return import_module(f"aiohttp_asyncmdnsresolver.{importable_module}") + + def __str__(self) -> str: + """Render the implementation facade instance as a string.""" + return f"{self.tag}-module" + + +@pytest.fixture( + scope="session", + params=( + pytest.param( + PropcacheImplementation(is_pure_python=False), + marks=C_EXT_MARK, + ), + PropcacheImplementation(is_pure_python=True), + ), + ids=str, +) +def aiohttp_asyncmdnsresolver_implementation(request: pytest.FixtureRequest) -> PropcacheImplementation: + """Return a aiohttp_asyncmdnsresolver variant facade.""" + return request.param + + +@pytest.fixture(scope="session") +def aiohttp_asyncmdnsresolver_module( + aiohttp_asyncmdnsresolver_implementation: PropcacheImplementation, +) -> ModuleType: + """Return a pre-imported module containing a aiohttp_asyncmdnsresolver variant.""" + return aiohttp_asyncmdnsresolver_implementation.imported_module + + +def pytest_addoption( + parser: pytest.Parser, + pluginmanager: pytest.PytestPluginManager, +) -> None: + """Define a new ``--c-extensions`` flag. + + This lets the callers deselect tests executed against the C-extension + version of the ``aiohttp_asyncmdnsresolver`` implementation. + """ + del pluginmanager + parser.addoption( + "--c-extensions", # disabled with `--no-c-extensions` + action=argparse.BooleanOptionalAction, + default=True, + dest="c_extensions", + help="Test C-extensions (on by default)", + ) + + +def pytest_collection_modifyitems( + session: pytest.Session, + config: pytest.Config, + items: list[pytest.Item], +) -> None: + """Deselect tests against C-extensions when requested via CLI.""" + test_c_extensions = config.getoption("--c-extensions") is True + + if test_c_extensions: + return + + selected_tests: list[pytest.Item] = [] + deselected_tests: list[pytest.Item] = [] + + for item in items: + c_ext = item.get_closest_marker(C_EXT_MARK.name) is not None + + target_items_list = deselected_tests if c_ext else selected_tests + target_items_list.append(item) + + config.hook.pytest_deselected(items=deselected_tests) + items[:] = selected_tests + + +def pytest_configure(config: pytest.Config) -> None: + """Declare the C-extension marker in config.""" + config.addinivalue_line( + "markers", + f"{C_EXT_MARK.name}: tests running against the C-extension implementation.", + ) diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..f57276c --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,11 @@ +"""Test we do not break the public API.""" + +from aiohttp_asyncmdnsresolver import _helpers, api + + +def test_api() -> None: + """Verify the public API is accessible.""" + assert api.cached_property is not None + assert api.under_cached_property is not None + assert api.cached_property is _helpers.cached_property + assert api.under_cached_property is _helpers.under_cached_property diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py new file mode 100644 index 0000000..ff79e61 --- /dev/null +++ b/tests/test_benchmarks.py @@ -0,0 +1,87 @@ +"""codspeed benchmarks for aiohttp_asyncmdnsresolver.""" + +from pytest_codspeed import BenchmarkFixture + +from aiohttp_asyncmdnsresolver import cached_property, under_cached_property + + +def test_under_cached_property_cache_hit(benchmark: BenchmarkFixture) -> None: + """Benchmark for under_cached_property cache hit.""" + + class Test: + def __init__(self) -> None: + self._cache = {"prop": 42} + + @under_cached_property + def prop(self) -> int: + """Return the value of the property.""" + raise NotImplementedError + + t = Test() + + @benchmark + def _run() -> None: + for _ in range(100): + t.prop + + +def test_cached_property_cache_hit(benchmark: BenchmarkFixture) -> None: + """Benchmark for cached_property cache hit.""" + + class Test: + def __init__(self) -> None: + self.__dict__["prop"] = 42 + + @cached_property + def prop(self) -> int: + """Return the value of the property.""" + raise NotImplementedError + + t = Test() + + @benchmark + def _run() -> None: + for _ in range(100): + t.prop + + +def test_under_cached_property_cache_miss(benchmark: BenchmarkFixture) -> None: + """Benchmark for under_cached_property cache miss.""" + + class Test: + def __init__(self) -> None: + self._cache: dict[str, int] = {} + + @under_cached_property + def prop(self) -> int: + """Return the value of the property.""" + return 42 + + t = Test() + cache = t._cache + + @benchmark + def _run() -> None: + for _ in range(100): + cache.pop("prop", None) + t.prop + + +def test_cached_property_cache_miss(benchmark: BenchmarkFixture) -> None: + """Benchmark for cached_property cache miss.""" + + class Test: + + @cached_property + def prop(self) -> int: + """Return the value of the property.""" + return 42 + + t = Test() + cache = t.__dict__ + + @benchmark + def _run() -> None: + for _ in range(100): + cache.pop("prop", None) + t.prop diff --git a/tests/test_cached_property.py b/tests/test_cached_property.py new file mode 100644 index 0000000..6c9009f --- /dev/null +++ b/tests/test_cached_property.py @@ -0,0 +1,145 @@ +from operator import not_ +from typing import Protocol + +import pytest + +from aiohttp_asyncmdnsresolver.api import cached_property + + +class APIProtocol(Protocol): + + cached_property: type[cached_property] + + +def test_cached_property(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> int: + return 1 + + a = A() + assert a.prop == 1 + + +def test_cached_property_class(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + # self._cache not set because its never accessed in this test + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> None: + """Docstring.""" + + assert isinstance(A.prop, aiohttp_asyncmdnsresolver_module.cached_property) + assert A.prop.__doc__ == "Docstring." + + +def test_cached_property_without_cache(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + + __slots__ = () + + def __init__(self) -> None: + pass + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> None: + """Mock property.""" + + a = A() + + with pytest.raises(AttributeError): + a.prop = 123 + + +def test_cached_property_check_without_cache(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + + __slots__ = () + + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> None: + """Mock property.""" + + a = A() + with pytest.raises((TypeError, AttributeError)): + assert a.prop == 1 + + +def test_cached_property_caching(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> int: + """Docstring.""" + return 1 + + a = A() + assert a.prop == 1 + + +def test_cached_property_class_docstring(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> None: + """Docstring.""" + + assert isinstance(A.prop, aiohttp_asyncmdnsresolver_module.cached_property) + assert "Docstring." == A.prop.__doc__ + + +def test_set_name(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + """Test that the __set_name__ method is called and checked.""" + + class A: + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> None: + """Docstring.""" + + A.prop.__set_name__(A, "prop") + + match = r"Cannot assign the same cached_property to two " + with pytest.raises(TypeError, match=match): + A.prop.__set_name__(A, "something_else") + + +def test_get_without_set_name(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + """Test that get without __set_name__ fails.""" + cp = aiohttp_asyncmdnsresolver_module.cached_property(not_) + + class A: + """A class.""" + + A.cp = cp # type: ignore[attr-defined] + match = r"Cannot use cached_property instance " + with pytest.raises(TypeError, match=match): + _ = A().cp # type: ignore[attr-defined] + + +def test_ensured_wrapped_function_is_accessible(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + """Test that the wrapped function can be accessed from python.""" + + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.cached_property + def prop(self) -> int: + """Docstring.""" + return 1 + + a = A() + assert A.prop.func(a) == 1 diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 0000000..ef6a5b9 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,43 @@ +"""Test imports can happen from top-level.""" + +import pytest + +import aiohttp_asyncmdnsresolver +from aiohttp_asyncmdnsresolver import _helpers + + +def test_api_at_top_level() -> None: + """Verify the public API is accessible at top-level.""" + assert aiohttp_asyncmdnsresolver.cached_property is not None + assert aiohttp_asyncmdnsresolver.under_cached_property is not None + assert aiohttp_asyncmdnsresolver.cached_property is _helpers.cached_property + assert aiohttp_asyncmdnsresolver.under_cached_property is _helpers.under_cached_property + + +@pytest.mark.parametrize( + "prop_name", + ("cached_property", "under_cached_property"), +) +def test_public_api_is_discoverable_in_dir(prop_name: str) -> None: + """Verify the public API is discoverable programmatically.""" + assert prop_name in dir(aiohttp_asyncmdnsresolver) + + +def test_importing_invalid_attr_raises() -> None: + """Verify importing an invalid attribute raises an AttributeError.""" + match = r"^module 'aiohttp_asyncmdnsresolver' has no attribute 'invalid_attr'$" + with pytest.raises(AttributeError, match=match): + aiohttp_asyncmdnsresolver.invalid_attr + + +def test_import_error_invalid_attr() -> None: + """Verify importing an invalid attribute raises an ImportError.""" + # No match here because the error is raised by the import system + # and may vary between Python versions. + with pytest.raises(ImportError): + from aiohttp_asyncmdnsresolver import invalid_attr # noqa: F401 + + +def test_no_wildcard_imports() -> None: + """Verify wildcard imports are prohibited.""" + assert not aiohttp_asyncmdnsresolver.__all__ diff --git a/tests/test_under_cached_property.py b/tests/test_under_cached_property.py new file mode 100644 index 0000000..f0dd66f --- /dev/null +++ b/tests/test_under_cached_property.py @@ -0,0 +1,129 @@ +from typing import Any, Protocol + +import pytest + +from aiohttp_asyncmdnsresolver.api import under_cached_property + + +class APIProtocol(Protocol): + + under_cached_property: type[under_cached_property] + + +def test_under_cached_property(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + self._cache: dict[str, int] = {} + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> int: + return 1 + + a = A() + assert a.prop == 1 + + +def test_under_cached_property_class(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> None: + """Docstring.""" + + assert isinstance(A.prop, aiohttp_asyncmdnsresolver_module.under_cached_property) + assert A.prop.__doc__ == "Docstring." + + +def test_under_cached_property_assignment(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + self._cache: dict[str, Any] = {} + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> None: + """Mock property.""" + + a = A() + + with pytest.raises(AttributeError): + a.prop = 123 + + +def test_under_cached_property_without_cache(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + self._cache: dict[str, int] = {} + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> None: + """Mock property.""" + + a = A() + + with pytest.raises(AttributeError): + a.prop = 123 + + +def test_under_cached_property_check_without_cache( + aiohttp_asyncmdnsresolver_module: APIProtocol, +) -> None: + class A: + def __init__(self) -> None: + """Init.""" + # Note that self._cache is intentionally missing + # here to verify AttributeError + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> None: + """Mock property.""" + + a = A() + with pytest.raises(AttributeError): + _ = a.prop # type: ignore[call-overload] + + +def test_under_cached_property_caching(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + self._cache: dict[str, int] = {} + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> int: + """Docstring.""" + return 1 + + a = A() + assert a.prop == 1 + + +def test_under_cached_property_class_docstring(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + class A: + def __init__(self) -> None: + """Init.""" + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> Any: + """Docstring.""" + + assert isinstance(A.prop, aiohttp_asyncmdnsresolver_module.under_cached_property) + assert "Docstring." == A.prop.__doc__ + + +def test_ensured_wrapped_function_is_accessible(aiohttp_asyncmdnsresolver_module: APIProtocol) -> None: + """Test that the wrapped function can be accessed from python.""" + + class A: + def __init__(self) -> None: + """Init.""" + self._cache: dict[str, int] = {} + + @aiohttp_asyncmdnsresolver_module.under_cached_property + def prop(self) -> int: + """Docstring.""" + return 1 + + a = A() + assert A.prop.wrapped(a) == 1 diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 0000000..a371acb --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,68 @@ +[tool.towncrier] + package = "aiohttp_asyncmdnsresolver" + filename = "CHANGES.rst" + directory = "CHANGES/" + title_format = "v{version}" + template = "CHANGES/.TEMPLATE.rst" + issue_format = "{issue}" + + # NOTE: The types are declared because: + # NOTE: - there is no mechanism to override just the value of + # NOTE: `tool.towncrier.type.misc.showcontent`; + # NOTE: - and, we want to declare extra non-default types for + # NOTE: clarity and flexibility. + + [[tool.towncrier.section]] + path = "" + + [[tool.towncrier.type]] + # Something we deemed an improper undesired behavior that got corrected + # in the release to match pre-agreed expectations. + directory = "bugfix" + name = "Bug fixes" + showcontent = true + + [[tool.towncrier.type]] + # New behaviors, public APIs. That sort of stuff. + directory = "feature" + name = "Features" + showcontent = true + + [[tool.towncrier.type]] + # Declarations of future API removals and breaking changes in behavior. + directory = "deprecation" + name = "Deprecations (removal in next major release)" + showcontent = true + + [[tool.towncrier.type]] + # When something public gets removed in a breaking way. Could be + # deprecated in an earlier release. + directory = "breaking" + name = "Removals and backward incompatible breaking changes" + showcontent = true + + [[tool.towncrier.type]] + # Notable updates to the documentation structure or build process. + directory = "doc" + name = "Improved documentation" + showcontent = true + + [[tool.towncrier.type]] + # Notes for downstreams about unobvious side effects and tooling. Changes + # in the test invocation considerations and runtime assumptions. + directory = "packaging" + name = "Packaging updates and notes for downstreams" + showcontent = true + + [[tool.towncrier.type]] + # Stuff that affects the contributor experience. e.g. Running tests, + # building the docs, setting up the development environment. + directory = "contrib" + name = "Contributor-facing changes" + showcontent = true + + [[tool.towncrier.type]] + # Changes that are hard to assign to any of the above categories. + directory = "misc" + name = "Miscellaneous internal changes" + showcontent = true