pax_global_header00006660000000000000000000000064147645353070014530gustar00rootroot0000000000000052 comment=3dca08ce5bbe673d7df25f44f3dda92505d1043d python-attrs-attrs-bd2446d/000077500000000000000000000000001476453530700157525ustar00rootroot00000000000000python-attrs-attrs-bd2446d/.git_archival.txt000066400000000000000000000001521476453530700212230ustar00rootroot00000000000000node: 3dca08ce5bbe673d7df25f44f3dda92505d1043d node-date: 2025-03-13T12:00:23+01:00 describe-name: 25.3.0 python-attrs-attrs-bd2446d/.gitattributes000066400000000000000000000002031476453530700206400ustar00rootroot00000000000000# Force LF line endings for text files * text=auto eol=lf # Needed for setuptools-scm-git-archive .git_archival.txt export-subst python-attrs-attrs-bd2446d/.github/000077500000000000000000000000001476453530700173125ustar00rootroot00000000000000python-attrs-attrs-bd2446d/.github/CODE_OF_CONDUCT.md000066400000000000000000000125521476453530700221160ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations python-attrs-attrs-bd2446d/.github/CONTRIBUTING.md000066400000000000000000000247751476453530700215620ustar00rootroot00000000000000# How To Contribute > [!IMPORTANT] > This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone. > But don't be afraid to open half-finished PRs and ask questions if something is unclear! ## Support In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity: help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)! The official tag is `python-attrs` and helping out in support frees us up to improve *attrs* instead! ## Workflow First off, thank you for considering to contribute! It's people like *you* who make this project such a great tool for everyone. - No contribution is too small! Please submit as many fixes for typos and grammar bloopers as you can! - Try to limit each pull request to *one* change only. - Since we squash on merge, it's up to you how you handle updates to the `main` branch. Whether you prefer to rebase on `main` or merge `main` into your branch, do whatever is more comfortable for you. Just remember to [not use your own `main` branch for the pull request](https://hynek.me/articles/pull-requests-branch/). - *Always* add tests and docs for your code. This is a hard rule; patches with missing tests or documentation won't be merged. - Consider adding a news fragment to [`changelog.d`](../changelog.d/) to reflect the changes as observed by people *using* this library. - Make sure your changes pass our [CI](https://github.com/python-attrs/attrs/actions). You won't get any feedback until it's green unless you ask for it. For the CI to pass, the coverage must be 100%. If you have problems to test something, open anyway and ask for advice. In some situations, we may agree to add an `# pragma: no cover`. - Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. - Don't break [backwards-compatibility](SECURITY.md). ## Local Development Environment First, **fork** the repository on GitHub and **clone** it using one of the alternatives that you can copy-paste by pressing the big green button labeled `<> Code`. You can (and should) run our test suite using [*tox*](https://tox.wiki/). However, you'll probably want a more traditional environment as well. We recommend using the Python version from the `.python-version-default` file in the project's root directory, because that's the one that is used in the CI by default, too. If you're using [*direnv*](https://direnv.net), you can automate the creation of the project virtual environment with the correct Python version by adding the following `.envrc` to the project root: ```bash layout python python$(cat .python-version-default) ``` or, if you like [*uv*](https://github.com/astral-sh/uv): ```bash test -d .venv || uv venv --python python$(cat .python-version-default) . .venv/bin/activate ``` > [!WARNING] > - **Before** you start working on a new pull request, use the "*Sync fork*" button in GitHub's web UI to ensure your fork is up to date. > > - **Always create a new branch off `main` for each new pull request.** > Yes, you can work on `main` in your fork and submit pull requests. > But this will *inevitably* lead to you not being able to synchronize your fork with upstream and having to start over. Change into the newly created directory and after activating a virtual environment, install an editable version of this project along with its tests requirements: ```console $ pip install -e .[dev] # or `uv pip install -e .[dev]` ``` Now you can run the test suite: ```console $ python -Im pytest ``` You can *significantly* speed up the test suite by passing `-n auto` to *pytest* which activates [*pytest-xdist*](https://github.com/pytest-dev/pytest-xdist) and takes advantage of all your CPU cores. --- When working on the documentation, use: ```console $ tox run -e docs-watch ``` This will build the documentation, watch for changes, and rebuild it whenever you save a file. To just build the documentation and exit immediately use: ```console $ tox run -e docs-build ``` You will find the built documentation in `docs/_build/html`. To run doctests: ```console $ tox run -e docs-doctests ``` ## Code - We follow [PEP 8](https://peps.python.org/pep-0008/) as enforced by [Ruff](https://ruff.rs/) with a line length of 79 characters. - As long as you run our full *tox* suite before committing, or install our [*pre-commit*](https://pre-commit.com/) hooks, you won't have to spend any time on formatting your code at all. If you don't, CI will catch it for you -- but that seems like a waste of your time! - If you've changed or added public APIs, please update our type stubs (files ending in `.pyi`). ## Tests - Write your asserts as `expected == actual` to line them up nicely, and leave an empty line before them: ```python x = f() assert 42 == x.some_attribute assert "foo" == x._a_private_attribute ``` - You can run the test suite runs with all dependencies against all supported Python versions -- just as it will in our CI -- by running `tox`. - Write [good test docstrings](https://jml.io/test-docstrings/). - To ensure new features work well with the rest of the system, they should be also added to our [Hypothesis](https://hypothesis.readthedocs.io/) testing strategy, which can be found in `tests/strategies.py`. ## Documentation - Use [semantic newlines] in [reStructuredText](https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html) (`*.rst`) and [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (`*.md`) files: ```markdown This is a sentence. This is another sentence. This is a new paragraph. ``` - If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other: ```markdown # Main Header Last line of previous section. ## Header of New Top Section ### Header of New Section First line of new section. ``` - If you add a new feature, demonstrate its awesomeness on the [examples page](https://github.com/python-attrs/attrs/blob/main/docs/examples.md)! - For docstrings, we follow [PEP 257](https://peps.python.org/pep-0257/), use the `"""`-on-separate-lines style, and [Napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)-style API documentation: ```python def func(x: str, y: int) -> str: """ Do something. Args: x: A very important argument. y: Another very important argument, but its description is so long that it doesn't fit on one line. So, we start the whole block on a fresh new line to keep the block together. Returns: str: The result of doing something. Raises: ValueError: When an invalid value is passed. """ ``` Please note that the API docstrings are still reStructuredText. - If you add or change public APIs, tag the docstring using `.. versionadded:: 24.1.0 WHAT` or `.. versionchanged:: 24.1.0 WHAT`. We follow CalVer, so the next version will be the current with with the middle number incremented (for example, `24.1.0` -> `24.2.0`). ### Changelog If your change is interesting to end-users, there needs to be a changelog entry so they can learn about it! To avoid merge conflicts, we use the [Towncrier](https://pypi.org/project/towncrier) package to manage our changelog. *towncrier* uses independent Markdown files for each pull request -- so called *news fragments* -- instead of one monolithic changelog file. On release, those news fragments are compiled into our [`CHANGELOG.md`](../CHANGELOG.md). You don't need to install Towncrier yourself, you just have to abide by a few simple rules: - For each pull request, add a new file into `changelog.d` with a filename adhering to the `pr#.(change|deprecation|breaking).md` schema: For example, `changelog.d/42.change.md` for a non-breaking change that is proposed in pull request #42. - As with other docs, please use [semantic newlines] within news fragments. - Refer to all symbols by their fully-qualified names. For example, `attrs.Foo` -- not just `Foo`. - Wrap symbols like modules, functions, or classes into backticks, so they are rendered in a `monospace font`. - Wrap arguments into asterisks so they are *italicized* like in API documentation: `Added new argument *an_argument*.` - If you mention functions or methods, add parentheses at the end of their names: `attrs.func()` or `attrs.Class.method()`. This makes the changelog a lot more readable. - Prefer simple past tense or constructions with "now". Example entries: ```md Added `attrs.validators.func()`. The feature really *is* awesome. ``` or: ```md `attrs.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. The bug really *was* nasty. ``` --- If you want to reference multiple issues, copy the news fragment to another filename. Towncrier will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. `tox run -e changelog` will render the current changelog to the terminal if you have any doubts. ## Governance *attrs* is maintained by [team of volunteers](https://github.com/python-attrs) that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort. If you'd like to join, just get a pull request merged and ask to be added in the very same pull request! **The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.** [Hynek Schlawack](https://hynek.me/about/) acts reluctantly as the [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) and has the final say over design decisions. ## See You on GitHub! Again, this whole file is mainly to help you to get started by codifying tribal knowledge and expectations to save you time and turnarounds. It is **not** meant to be a barrier to entry, so don't be afraid to open half-finished PRs and ask questions if something is unclear! Please note that this project is released with a Contributor [Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. Please report any harm to Hynek Schlawack in any way you find appropriate. [semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ python-attrs-attrs-bd2446d/.github/FUNDING.yml000066400000000000000000000000511476453530700211230ustar00rootroot00000000000000--- github: hynek tidelift: "pypi/attrs" python-attrs-attrs-bd2446d/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000060631476453530700231200ustar00rootroot00000000000000# Summary # Pull Request Check List - [ ] Do **not** open pull requests from your `main` branch – **use a separate branch**! - There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch. - This is not a pre-requisite for your pull request to be accepted, but **you have been warned**. - [ ] Added **tests** for changed code. Our CI fails if coverage is not 100%. - [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py). - [ ] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``). - [ ] ...and used in the stub test file `tests/typing_example.py`. - [ ] If they've been added to `attr/__init__.pyi`, they've *also* been re-imported in `attrs/__init__.pyi`. - [ ] Updated **documentation** for changed code. - [ ] New functions/classes have to be added to `docs/api.rst` by hand. - [ ] Changes to the signatures of `@attr.s()` and `@attrs.define()` have to be added by hand too. - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded). The next version is the second number in the current release + 1. The first number represents the current year. So if the current version on PyPI is 22.2.0, the next version is gonna be 22.3.0. If the next version is the first in the new year, it'll be 23.1.0. - [ ] If something changed that affects both `attrs.define()` and `attr.s()`, you have to add version directives to both. - [ ] Documentation in `.rst` and `.md` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). - [ ] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d). - [ ] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you. python-attrs-attrs-bd2446d/.github/SECURITY.md000066400000000000000000000016631476453530700211110ustar00rootroot00000000000000# Security Policy ## Supported Versions We are following [Calendar Versioning](https://calver.org) with generous backwards-compatibility guarantees. Therefore we only support the latest version. Put simply, you shouldn't ever be afraid to upgrade as long as you're only using our public APIs. Whenever there is a need to break compatibility, it is announced in the changelog, and raises a `DeprecationWarning` for a year (if possible) before it's finally really broken. > [!WARNING] > The structure of the `attrs.Attribute` class is exempt from this rule. > It *will* change in the future, but since it should be considered read-only, that shouldn't matter. > > However if you intend to build extensions on top of *attrs* you have to anticipate that. ## Reporting a Vulnerability To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. python-attrs-attrs-bd2446d/.github/dependabot.yml000066400000000000000000000001651476453530700221440ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: monthly python-attrs-attrs-bd2446d/.github/workflows/000077500000000000000000000000001476453530700213475ustar00rootroot00000000000000python-attrs-attrs-bd2446d/.github/workflows/build-docset.yml000066400000000000000000000011461476453530700244520ustar00rootroot00000000000000--- name: Build docset on: push: tags: ["*"] workflow_dispatch: env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_PYTHON_VERSION_WARNING: "1" permissions: {} jobs: docset: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-python@v5 with: python-version: "3.x" - uses: hynek/setup-cached-uv@v2 - run: uvx --with=tox-uv tox run -e docset - uses: actions/upload-artifact@v4 with: name: docset path: attrs.tgz python-attrs-attrs-bd2446d/.github/workflows/ci.yml000066400000000000000000000131121476453530700224630ustar00rootroot00000000000000--- name: CI on: merge_group: push: branches: [main] tags: ["*"] pull_request: workflow_dispatch: env: FORCE_COLOR: "1" PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_PYTHON_VERSION_WARNING: "1" permissions: {} jobs: build-package: name: Build & verify package runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: hynek/build-and-inspect-python-package@v2 id: baipp outputs: # Used to define the matrix for tests below. The value is based on # packaging metadata (trove classifiers). supported-python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} tests: name: Tests & Mypy on ${{ matrix.python-version }} runs-on: ubuntu-latest needs: build-package strategy: fail-fast: false matrix: # Created by the build-and-inspect-python-package action above. python-version: ${{ fromJson(needs.build-package.outputs.supported-python-versions) }} steps: - name: Download pre-built packages uses: actions/download-artifact@v4 with: name: Packages path: dist - run: tar xf dist/*.tar.gz --strip-components=1 - uses: hynek/setup-cached-uv@v2 - name: Prepare tox env: V: ${{ matrix.python-version }} run: | DO_MYPY=1 if [[ "$V" == "3.8" || "$V" == "3.9" ]]; then DO_MYPY=0 fi echo DO_MYPY=$DO_MYPY >>$GITHUB_ENV echo TOX_PYTHON=py$(echo $V | tr -d .) >>$GITHUB_ENV - run: > uvx --with=tox-uv tox run -e $TOX_PYTHON-mypy if: env.DO_MYPY == '1' - name: Remove src to ensure tests run against wheel run: rm -rf src - run: > uvx --with=tox-uv tox run --installpkg dist/*.whl -e $TOX_PYTHON-tests - name: Upload coverage data uses: actions/upload-artifact@v4 with: name: coverage-data-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true if-no-files-found: ignore tests-pypy: name: Tests on ${{ matrix.python-version }} runs-on: ubuntu-latest needs: build-package strategy: fail-fast: false matrix: python-version: - pypy-3.10 steps: - name: Download pre-built packages uses: actions/download-artifact@v4 with: name: Packages path: dist - run: | tar xf dist/*.tar.gz --strip-components=1 rm -rf src # ensure tests run against wheel - uses: hynek/setup-cached-uv@v2 - run: > uvx --with=tox-uv tox run --installpkg dist/*.whl -e pypy3-tests coverage: name: Combine & check coverage runs-on: ubuntu-latest needs: tests steps: - name: Download pre-built packages uses: actions/download-artifact@v4 with: name: Packages path: dist - run: tar xf dist/*.tar.gz --strip-components=1 - uses: hynek/setup-cached-uv@v2 - name: Download coverage data uses: actions/download-artifact@v4 with: pattern: coverage-data-* merge-multiple: true - name: Combine coverage & fail if it's <100%. run: | uv tool install --python $(cat .python-version-default) coverage coverage combine coverage html --skip-covered --skip-empty # Report and write to summary. coverage report --format=markdown >> $GITHUB_STEP_SUMMARY # Report again and fail if under 100%. coverage report --fail-under=100 - name: Upload HTML report if check failed. uses: actions/upload-artifact@v4 with: name: html-report path: htmlcov if: ${{ failure() }} docs: name: Run doctests & render changelog runs-on: ubuntu-latest needs: build-package steps: - name: Download pre-built packages uses: actions/download-artifact@v4 with: name: Packages path: dist - run: tar xf dist/*.tar.gz --strip-components=1 - uses: hynek/setup-cached-uv@v2 - run: uvx --with=tox-uv tox run -e docs-doctests,changelog pyright: name: Check types using pyright runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: hynek/setup-cached-uv@v2 - run: > uvx --with=tox-uv --python $(cat .python-version-default) tox run -e pyright install-dev: name: Verify dev env runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: hynek/setup-cached-uv@v2 - run: uv venv --python $(cat .python-version-default) - run: uv pip install -e .[dev] - name: Ensure we can import attr and attrs packages run: | source .venv/bin/activate python -Ic 'import attr; print(attr.__version__)' python -Ic 'import attrs; print(attrs.__version__)' # Ensure everything required is passing for branch protection. required-checks-pass: if: always() needs: - coverage - tests-pypy - docs - install-dev - pyright 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) }} python-attrs-attrs-bd2446d/.github/workflows/codeql-analysis.yml000066400000000000000000000013471476453530700251670ustar00rootroot00000000000000--- name: CodeQL on: schedule: - cron: "30 22 * * 4" permissions: contents: read 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 repository uses: actions/checkout@v4 with: persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 python-attrs-attrs-bd2446d/.github/workflows/codspeed.yml000066400000000000000000000016241476453530700236630ustar00rootroot00000000000000--- name: CodSpeed Benchmarks on: push: branches: [main] tags: ["*"] paths: - src/**.py - bench/** - .github/workflows/codspeed.yml pull_request: paths: - src/**.py - bench/** - .github/workflows/codspeed.yml workflow_dispatch: env: FORCE_COLOR: "1" PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_PYTHON_VERSION_WARNING: "1" permissions: {} jobs: codspeed: name: Run CodSpeed benchmarks runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-python@v5 with: python-version-file: .python-version-default - uses: hynek/setup-cached-uv@v2 - name: Run CodSpeed benchmarks uses: CodSpeedHQ/action@v3 with: token: ${{ secrets.CODSPEED_TOKEN }} run: uvx --with tox-uv tox run -e codspeed python-attrs-attrs-bd2446d/.github/workflows/pypi-package.yml000066400000000000000000000036551476453530700244550ustar00rootroot00000000000000--- name: Build & upload PyPI package on: push: branches: [main] tags: ["*"] release: types: - published workflow_dispatch: jobs: # Always build & lint package. build-package: name: Build & verify package runs-on: ubuntu-latest permissions: attestations: write id-token: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: hynek/build-and-inspect-python-package@v2 with: attest-build-provenance-github: 'true' # Upload to Test PyPI on every commit on main. release-test-pypi: name: Publish in-dev package to test.pypi.org environment: release-test-pypi if: github.repository_owner == 'python-attrs' && github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: build-package permissions: id-token: write steps: - name: Download packages built by build-and-inspect-python-package uses: actions/download-artifact@v4 with: name: Packages path: dist - name: Upload package to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: attestations: true repository-url: https://test.pypi.org/legacy/ # Upload to real PyPI on GitHub Releases. release-pypi: name: Publish released package to pypi.org environment: release-pypi if: github.repository_owner == 'python-attrs' && github.event.action == 'published' runs-on: ubuntu-latest needs: build-package permissions: id-token: write steps: - name: Download packages built by build-and-inspect-python-package uses: actions/download-artifact@v4 with: name: Packages path: dist - name: Upload package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: attestations: true python-attrs-attrs-bd2446d/.github/workflows/zizmor.yml000066400000000000000000000016531476453530700234310ustar00rootroot00000000000000# https://github.com/woodruffw/zizmor name: Zizmor on: push: branches: ["main"] pull_request: branches: ["*"] permissions: contents: read jobs: zizmor: name: Zizmor latest via PyPI runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@v4 with: persist-credentials: false - uses: hynek/setup-cached-uv@v2 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file uses: github/codeql-action/upload-sarif@v3 with: # Path to SARIF file relative to the root of the repository sarif_file: results.sarif # Optional category for the results # Used to differentiate multiple results for one commit category: zizmor python-attrs-attrs-bd2446d/.gitignore000066400000000000000000000002771476453530700177500ustar00rootroot00000000000000*.egg-info *.pyc .DS_Store .cache .coverage* .direnv .envrc .hypothesis .mypy_cache .pytest_cache .tox .vscode .venv* build dist docs/_build htmlcov tmp* attrs.docset attrs.tgz Justfile t.py python-attrs-attrs-bd2446d/.pre-commit-config.yaml000066400000000000000000000012641476453530700222360ustar00rootroot00000000000000--- ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.10 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/econchick/interrogate rev: 1.7.0 hooks: - id: interrogate args: [tests] - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell args: [--exclude-file=tests/test_mypy.yml] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-toml - id: check-yaml python-attrs-attrs-bd2446d/.python-version-default000066400000000000000000000000051476453530700223740ustar00rootroot000000000000003.13 python-attrs-attrs-bd2446d/.readthedocs.yaml000066400000000000000000000007351476453530700212060ustar00rootroot00000000000000--- version: 2 build: os: ubuntu-lts-latest tools: # Keep version in sync with tox.ini/docs. python: "3.13" jobs: create_environment: # Need the tags to calculate the version (sometimes). - git fetch --tags - asdf plugin add uv - asdf install uv latest - asdf global uv latest build: html: - uvx --with tox-uv tox run -e docs-sponsors - uvx --with tox-uv tox run -e docs-build -- $READTHEDOCS_OUTPUT python-attrs-attrs-bd2446d/CHANGELOG.md000066400000000000000000002037301476453530700175700ustar00rootroot00000000000000# Changelog Versions follow [Calendar Versioning](https://calver.org) with a strict backwards-compatibility policy. The **first number** of the version is the year. The **second number** is incremented with each release, starting at 1 for each year. The **third number** is when we need to start branches for older releases (only for emergencies). You can find our backwards-compatibility policy [here](https://github.com/python-attrs/attrs/blob/main/.github/SECURITY.md). Changes for the upcoming release can be found in the [`changelog.d` directory](https://github.com/python-attrs/attrs/tree/main/changelog.d) in our repository. ## [25.3.0](https://github.com/python-attrs/attrs/tree/25.3.0) - 2025-03-13 ### Changes - Restore support for generator-based `field_transformer`s. [#1417](https://github.com/python-attrs/attrs/issues/1417) ## [25.2.0](https://github.com/python-attrs/attrs/tree/25.2.0) - 2025-03-12 ### Changes - Checking mandatory vs non-mandatory attribute order is now performed after the field transformer, since the field transformer may change attributes and/or their order. [#1147](https://github.com/python-attrs/attrs/issues/1147) - `attrs.make_class()` now allows for Unicode class names. [#1406](https://github.com/python-attrs/attrs/issues/1406) - Speed up class creation by 30%-50% by compiling methods only once and using a variety of other techniques. [#1407](https://github.com/python-attrs/attrs/issues/1407) - The error message if an attribute has both an annotation and a type argument will now disclose _what_ attribute seems to be the problem. [#1410](https://github.com/python-attrs/attrs/issues/1410) ## [25.1.0](https://github.com/python-attrs/attrs/tree/25.1.0) - 2025-01-25 ### Changes - This release only ensures correct PyPI licensing metadata. [#1386](https://github.com/python-attrs/attrs/issues/1386) ## [24.3.0](https://github.com/python-attrs/attrs/tree/24.3.0) - 2024-12-16 ### Backwards-incompatible Changes - Python 3.7 has been dropped. [#1340](https://github.com/python-attrs/attrs/issues/1340) ### Changes - Introduce `attrs.NothingType`, for annotating types consistent with `attrs.NOTHING`. [#1358](https://github.com/python-attrs/attrs/issues/1358) - Allow mutating `__suppress_context__` and `__notes__` on frozen exceptions. [#1365](https://github.com/python-attrs/attrs/issues/1365) - `attrs.converters.optional()` works again when taking `attrs.converters.pipe()` or another Converter as its argument. [#1372](https://github.com/python-attrs/attrs/issues/1372) - *attrs* instances now support [`copy.replace()`](https://docs.python.org/3/library/copy.html#copy.replace). [#1383](https://github.com/python-attrs/attrs/issues/1383) - `attrs.validators.instance_of()`'s type hints now allow for union types. For example: `instance_of(str | int)` [#1385](https://github.com/python-attrs/attrs/issues/1385) ## [24.2.0](https://github.com/python-attrs/attrs/tree/24.2.0) - 2024-08-06 ### Deprecations - Given the amount of warnings raised in the broader ecosystem, we've decided to only soft-deprecate the *hash* argument to `@define` / `@attr.s`. Please don't use it in new code, but we don't intend to remove it anymore. [#1330](https://github.com/python-attrs/attrs/issues/1330) ### Changes - `attrs.converters.pipe()` (and its syntactic sugar of passing a list for `attrs.field()`'s / `attr.ib()`'s *converter* argument) works again when passing `attrs.setters.convert` to *on_setattr* (which is default for `attrs.define`). [#1328](https://github.com/python-attrs/attrs/issues/1328) - Restored support for PEP [649](https://peps.python.org/pep-0649/) / [749](https://peps.python.org/pep-0749/)-implementing Pythons -- currently 3.14-dev. [#1329](https://github.com/python-attrs/attrs/issues/1329) ## [24.1.0](https://github.com/python-attrs/attrs/tree/24.1.0) - 2024-08-03 ### Backwards-incompatible Changes - `attrs.evolve()` doesn't accept the *inst* argument as a keyword argument anymore. Pass it as the first positional argument instead. [#1264](https://github.com/python-attrs/attrs/issues/1264) - `attrs.validators.provides()` has been removed. The removed code is available as a [gist](https://gist.github.com/hynek/9eaaaeb659808f3519870dfa16d2b6b2) for convenient copy and pasting. [#1265](https://github.com/python-attrs/attrs/issues/1265) - All packaging metadata except from `__version__` and `__version_info__` has been removed from the `attr` and `attrs` modules (for example, `attrs.__url__`). Please use [`importlib.metadata`](https://docs.python.org/3/library/importlib.metadata.html) or [*importlib-metadata*](https://pypi.org/project/importlib-metadata/) instead. [#1268](https://github.com/python-attrs/attrs/issues/1268) - The generated `__eq__` methods have been sped up significantly by generating a chain of attribute comparisons instead of constructing and comparing tuples. This change arguably makes the behavior more correct, but changes it if an attribute compares equal by identity but not value, like `float('nan')`. [#1310](https://github.com/python-attrs/attrs/issues/1310) ### Deprecations - The *repr_ns* argument to `attr.s` is now deprecated. It was a workaround for nested classes in Python 2 and is pointless in Python 3. [#1263](https://github.com/python-attrs/attrs/issues/1263) - The *hash* argument to `@attr.s`, `@attrs.define`, and `make_class()` is now deprecated in favor of *unsafe_hash*, as defined by PEP 681. [#1323](https://github.com/python-attrs/attrs/issues/1323) ### Changes - Allow original slotted `functools.cached_property` classes to be cleaned by garbage collection. Allow `super()` calls in slotted cached properties. [#1221](https://github.com/python-attrs/attrs/issues/1221) - Our type stubs now use modern type notation and are organized such that VS Code's quick-fix prefers the `attrs` namespace. [#1234](https://github.com/python-attrs/attrs/issues/1234) - Preserve `AttributeError` raised by properties of slotted classes with `functools.cached_properties`. [#1253](https://github.com/python-attrs/attrs/issues/1253) - It is now possible to wrap a converter into an `attrs.Converter` and get the current instance and/or the current field definition passed into the converter callable. Note that this is not supported by any type checker, yet. [#1267](https://github.com/python-attrs/attrs/issues/1267) - `attrs.make_class()` now populates the `__annotations__` dict of the generated class, so that `attrs.resolve_types()` can resolve them. [#1285](https://github.com/python-attrs/attrs/issues/1285) - Added the `attrs.validators.or_()` validator. [#1303](https://github.com/python-attrs/attrs/issues/1303) - The combination of a `__attrs_pre_init__` that takes arguments, a kw-only field, and a default on that field does not crash anymore. [#1319](https://github.com/python-attrs/attrs/issues/1319) - `attrs.validators.in_()` now transforms certain unhashable options to tuples to keep the field hashable. This allows fields that use this validator to be used with, for example, `attrs.filters.include()`. [#1320](https://github.com/python-attrs/attrs/issues/1320) - If a class has an *inherited* method called `__attrs_init_subclass__`, it is now called once the class is done assembling. This is a replacement for Python's `__init_subclass__` and useful for registering classes, and similar. [#1321](https://github.com/python-attrs/attrs/issues/1321) ## [23.2.0](https://github.com/python-attrs/attrs/tree/23.2.0) - 2023-12-31 ### Changes - The type annotation for `attrs.resolve_types()` is now correct. [#1141](https://github.com/python-attrs/attrs/issues/1141) - Type stubs now use `typing.dataclass_transform` to decorate dataclass-like decorators, instead of the non-standard `__dataclass_transform__` special form, which is only supported by Pyright. [#1158](https://github.com/python-attrs/attrs/issues/1158) - Fixed serialization of namedtuple fields using `attrs.asdict/astuple()` with `retain_collection_types=True`. [#1165](https://github.com/python-attrs/attrs/issues/1165) - `attrs.AttrsInstance` is now a `typing.Protocol` in both type hints and code. This allows you to subclass it along with another `Protocol`. [#1172](https://github.com/python-attrs/attrs/issues/1172) - If *attrs* detects that `__attrs_pre_init__` accepts more than just `self`, it will call it with the same arguments as `__init__` was called. This allows you to, for example, pass arguments to `super().__init__()`. [#1187](https://github.com/python-attrs/attrs/issues/1187) - Slotted classes now transform `functools.cached_property` decorated methods to support equivalent semantics. [#1200](https://github.com/python-attrs/attrs/issues/1200) - Added *class_body* argument to `attrs.make_class()` to provide additional attributes for newly created classes. It is, for example, now possible to attach methods. [#1203](https://github.com/python-attrs/attrs/issues/1203) ## [23.1.0](https://github.com/python-attrs/attrs/tree/23.1.0) - 2023-04-16 ### Backwards-incompatible Changes - Python 3.6 has been dropped and packaging switched to static package data using [Hatch](https://hatch.pypa.io/latest/). [#993](https://github.com/python-attrs/attrs/issues/993) ### Deprecations - The support for *zope-interface* via the `attrs.validators.provides` validator is now deprecated and will be removed in, or after, April 2024. The presence of a C-based package in our development dependencies has caused headaches and we're not under the impression it's used a lot. Let us know if you're using it and we might publish it as a separate package. [#1120](https://github.com/python-attrs/attrs/issues/1120) ### Changes - `attrs.filters.exclude()` and `attrs.filters.include()` now support the passing of attribute names as strings. [#1068](https://github.com/python-attrs/attrs/issues/1068) - `attrs.has()` and `attrs.fields()` now handle generic classes correctly. [#1079](https://github.com/python-attrs/attrs/issues/1079) - Fix frozen exception classes when raised within, for example, `contextlib.contextmanager`, which mutates their `__traceback__` attributes. [#1081](https://github.com/python-attrs/attrs/issues/1081) - `@frozen` now works with type checkers that implement [PEP-681](https://peps.python.org/pep-0681/) (ex. [pyright](https://github.com/microsoft/pyright/)). [#1084](https://github.com/python-attrs/attrs/issues/1084) - Restored ability to unpickle instances pickled before 22.2.0. [#1085](https://github.com/python-attrs/attrs/issues/1085) - `attrs.asdict()`'s and `attrs.astuple()`'s type stubs now accept the `attrs.AttrsInstance` protocol. [#1090](https://github.com/python-attrs/attrs/issues/1090) - Fix slots class cellvar updating closure in CPython 3.8+ even when `__code__` introspection is unavailable. [#1092](https://github.com/python-attrs/attrs/issues/1092) - `attrs.resolve_types()` can now pass `include_extras` to `typing.get_type_hints()` on Python 3.9+, and does so by default. [#1099](https://github.com/python-attrs/attrs/issues/1099) - Added instructions for pull request workflow to `CONTRIBUTING.md`. [#1105](https://github.com/python-attrs/attrs/issues/1105) - Added *type* parameter to `attrs.field()` function for use with `attrs.make_class()`. Please note that type checkers ignore type metadata passed into `make_class()`, but it can be useful if you're wrapping _attrs_. [#1107](https://github.com/python-attrs/attrs/issues/1107) - It is now possible for `attrs.evolve()` (and `attr.evolve()`) to change fields named `inst` if the instance is passed as a positional argument. Passing the instance using the `inst` keyword argument is now deprecated and will be removed in, or after, April 2024. [#1117](https://github.com/python-attrs/attrs/issues/1117) - `attrs.validators.optional()` now also accepts a tuple of validators (in addition to lists of validators). [#1122](https://github.com/python-attrs/attrs/issues/1122) ## [22.2.0](https://github.com/python-attrs/attrs/tree/22.2.0) - 2022-12-21 ### Backwards-incompatible Changes - Python 3.5 is not supported anymore. [#988](https://github.com/python-attrs/attrs/issues/988) ### Deprecations - Python 3.6 is now deprecated and support will be removed in the next release. [#1017](https://github.com/python-attrs/attrs/issues/1017) ### Changes - `attrs.field()` now supports an *alias* option for explicit `__init__` argument names. Get `__init__` signatures matching any taste, peculiar or plain! The [PEP 681 compatible](https://peps.python.org/pep-0681/#field-specifier-parameters) *alias* option can be use to override private attribute name mangling, or add other arbitrary field argument name overrides. [#950](https://github.com/python-attrs/attrs/issues/950) - `attrs.NOTHING` is now an enum value, making it possible to use with, for example, [`typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal). [#983](https://github.com/python-attrs/attrs/issues/983) - Added missing re-import of `attr.AttrsInstance` to the `attrs` namespace. [#987](https://github.com/python-attrs/attrs/issues/987) - Fix slight performance regression in classes with custom `__setattr__` and speedup even more. [#991](https://github.com/python-attrs/attrs/issues/991) - Class-creation performance improvements by switching performance-sensitive templating operations to f-strings. You can expect an improvement of about 5% -- even for very simple classes. [#995](https://github.com/python-attrs/attrs/issues/995) - `attrs.has()` is now a [`TypeGuard`](https://docs.python.org/3/library/typing.html#typing.TypeGuard) for `AttrsInstance`. That means that type checkers know a class is an instance of an `attrs` class if you check it using `attrs.has()` (or `attr.has()`) first. [#997](https://github.com/python-attrs/attrs/issues/997) - Made `attrs.AttrsInstance` stub available at runtime and fixed type errors related to the usage of `attrs.AttrsInstance` in Pyright. [#999](https://github.com/python-attrs/attrs/issues/999) - On Python 3.10 and later, call [`abc.update_abstractmethods()`](https://docs.python.org/3/library/abc.html#abc.update_abstractmethods) on dict classes after creation. This improves the detection of abstractness. [#1001](https://github.com/python-attrs/attrs/issues/1001) - *attrs*'s pickling methods now use dicts instead of tuples. That is safer and more robust across different versions of a class. [#1009](https://github.com/python-attrs/attrs/issues/1009) - Added `attrs.validators.not_(wrapped_validator)` to logically invert *wrapped_validator* by accepting only values where *wrapped_validator* rejects the value with a `ValueError` or `TypeError` (by default, exception types configurable). [#1010](https://github.com/python-attrs/attrs/issues/1010) - The type stubs for `attrs.cmp_using()` now have default values. [#1027](https://github.com/python-attrs/attrs/issues/1027) - To conform with [PEP 681](https://peps.python.org/pep-0681/), `attr.s()` and `attrs.define()` now accept *unsafe_hash* in addition to *hash*. [#1065](https://github.com/python-attrs/attrs/issues/1065) ## [22.1.0](https://github.com/python-attrs/attrs/tree/22.1.0) - 2022-07-28 ### Backwards-incompatible Changes - Python 2.7 is not supported anymore. Dealing with Python 2.7 tooling has become too difficult for a volunteer-run project. We have supported Python 2 more than 2 years after it was officially discontinued and feel that we have paid our dues. All version up to 21.4.0 from December 2021 remain fully functional, of course. [#936](https://github.com/python-attrs/attrs/issues/936) - The deprecated `cmp` attribute of `attrs.Attribute` has been removed. This does not affect the *cmp* argument to `attr.s` that can be used as a shortcut to set *eq* and *order* at the same time. [#939](https://github.com/python-attrs/attrs/issues/939) ### Changes - Instantiation of frozen slotted classes is now faster. [#898](https://github.com/python-attrs/attrs/issues/898) - If an `eq` key is defined, it is also used before hashing the attribute. [#909](https://github.com/python-attrs/attrs/issues/909) - Added `attrs.validators.min_len()`. [#916](https://github.com/python-attrs/attrs/issues/916) - `attrs.validators.deep_iterable()`'s *member_validator* argument now also accepts a list of validators and wraps them in an `attrs.validators.and_()`. [#925](https://github.com/python-attrs/attrs/issues/925) - Added missing type stub re-imports for `attrs.converters` and `attrs.filters`. [#931](https://github.com/python-attrs/attrs/issues/931) - Added missing stub for `attr(s).cmp_using()`. [#949](https://github.com/python-attrs/attrs/issues/949) - `attrs.validators._in()`'s `ValueError` is not missing the attribute, expected options, and the value it got anymore. [#951](https://github.com/python-attrs/attrs/issues/951) - Python 3.11 is now officially supported. [#969](https://github.com/python-attrs/attrs/issues/969) ## [21.4.0](https://github.com/python-attrs/attrs/tree/21.4.0) - 2021-12-29 ### Changes - Fixed the test suite on PyPy3.8 where `cloudpickle` does not work. [#892](https://github.com/python-attrs/attrs/issues/892) - Fixed `coverage report` for projects that use `attrs` and don't set a `--source`. [#895](https://github.com/python-attrs/attrs/issues/895), [#896](https://github.com/python-attrs/attrs/issues/896) ## [21.3.0](https://github.com/python-attrs/attrs/tree/21.3.0) - 2021-12-28 ### Backward-incompatible Changes - When using `@define`, converters are now run by default when setting an attribute on an instance -- additionally to validators. Meaning: the new default is `on_setattr=[attrs.setters.convert, attrs.setters.validate]`. This is unfortunately a breaking change, but it was an oversight, impossible to raise a `DeprecationWarning` about, and it's better to fix it now while the APIs are very fresh with few users. [#835](https://github.com/python-attrs/attrs/issues/835), [#886](https://github.com/python-attrs/attrs/issues/886) - `import attrs` has finally landed! As of this release, you can finally import `attrs` using its proper name. Not all names from the `attr` namespace have been transferred; most notably `attr.s` and `attr.ib` are missing. See `attrs.define` and `attrs.field` if you haven't seen our next-generation APIs yet. A more elaborate explanation can be found [On The Core API Names](https://www.attrs.org/en/latest/names.html) This feature is at least for one release **provisional**. We don't *plan* on changing anything, but such a big change is unlikely to go perfectly on the first strike. The API docs have been mostly updated, but it will be an ongoing effort to change everything to the new APIs. Please note that we have **not** moved -- or even removed -- anything from `attr`! Please do report any bugs or documentation inconsistencies! [#887](https://github.com/python-attrs/attrs/issues/887) ### Changes - `attr.asdict(retain_collection_types=False)` (default) dumps collection-esque keys as tuples. [#646](https://github.com/python-attrs/attrs/issues/646), [#888](https://github.com/python-attrs/attrs/issues/888) - `__match_args__` are now generated to support Python 3.10's [Structural Pattern Matching](https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching). This can be controlled by the `match_args` argument to the class decorators on Python 3.10 and later. On older versions, it is never added and the argument is ignored. [#815](https://github.com/python-attrs/attrs/issues/815) - If the class-level *on_setattr* is set to `attrs.setters.validate` (default in `@define` and `@mutable`) but no field defines a validator, pretend that it's not set. [#817](https://github.com/python-attrs/attrs/issues/817) - The generated `__repr__` is significantly faster on Pythons with f-strings. [#819](https://github.com/python-attrs/attrs/issues/819) - Attributes transformed via `field_transformer` are wrapped with `AttrsClass` again. [#824](https://github.com/python-attrs/attrs/issues/824) - Generated source code is now cached more efficiently for identical classes. [#828](https://github.com/python-attrs/attrs/issues/828) - Added `attrs.converters.to_bool()`. [#830](https://github.com/python-attrs/attrs/issues/830) - `attrs.resolve_types()` now resolves types of subclasses after the parents are resolved. [#842](https://github.com/python-attrs/attrs/issues/842) [#843](https://github.com/python-attrs/attrs/issues/843) - Added new validators: `lt(val)` (\< val), `le(va)` (≤ val), `ge(val)` (≥ val), `gt(val)` (> val), and `maxlen(n)`. [#845](https://github.com/python-attrs/attrs/issues/845) - `attrs` classes are now fully compatible with [cloudpickle](https://github.com/cloudpipe/cloudpickle) (no need to disable `repr` anymore). [#857](https://github.com/python-attrs/attrs/issues/857) - Added new context manager `attrs.validators.disabled()` and functions `attrs.validators.(set|get)_disabled()`. They deprecate `attrs.(set|get)_run_validators()`. All functions are interoperable and modify the same internal state. They are not – and never were – thread-safe, though. [#859](https://github.com/python-attrs/attrs/issues/859) - `attrs.validators.matches_re()` now accepts pre-compiled regular expressions in addition to pattern strings. [#877](https://github.com/python-attrs/attrs/issues/877) --- ## [21.2.0](https://github.com/python-attrs/attrs/tree/21.2.0) - 2021-05-07 ### Backward-incompatible Changes - We had to revert the recursive feature for `attr.evolve()` because it broke some use-cases -- sorry! [#806](https://github.com/python-attrs/attrs/issues/806) - Python 3.4 is now blocked using packaging metadata because `attrs` can't be imported on it anymore. To ensure that 3.4 users can keep installing `attrs` easily, we will [yank](https://pypi.org/help/#yanked) 21.1.0 from PyPI. This has **no** consequences if you pin `attrs` to 21.1.0. [#807](https://github.com/python-attrs/attrs/issues/807) ## [21.1.0](https://github.com/python-attrs/attrs/tree/21.1.0) - 2021-05-06 ### Deprecations - The long-awaited, much-talked-about, little-delivered `import attrs` is finally upon us! Since the NG APIs have now been proclaimed stable, the **next** release of `attrs` will allow you to actually `import attrs`. We're taking this opportunity to replace some defaults in our APIs that made sense in 2015, but don't in 2021. So please, if you have any pet peeves about defaults in `attrs`'s APIs, *now* is the time to air your grievances in #487! We're not gonna get such a chance for a second time, without breaking our backward-compatibility guarantees, or long deprecation cycles. Therefore, speak now or forever hold you peace! [#487](https://github.com/python-attrs/attrs/issues/487) - The *cmp* argument to `attr.s()` and `attr.ib()` has been **undeprecated** It will continue to be supported as syntactic sugar to set *eq* and *order* in one go. I'm terribly sorry for the hassle around this argument! The reason we're bringing it back is it's usefulness regarding customization of equality/ordering. The `cmp` attribute and argument on `attr.Attribute` remains deprecated and will be removed later this year. [#773](https://github.com/python-attrs/attrs/issues/773) ### Changes - It's now possible to customize the behavior of `eq` and `order` by passing in a callable. [#435](https://github.com/python-attrs/attrs/issues/435), [#627](https://github.com/python-attrs/attrs/issues/627) - The instant favorite next-generation APIs are not provisional anymore! They are also officially supported by Mypy as of their [0.800 release](https://mypy-lang.blogspot.com/2021/01/mypy-0800-released.html). We hope the next release will already contain an (additional) importable package called `attrs`. [#668](https://github.com/python-attrs/attrs/issues/668), [#786](https://github.com/python-attrs/attrs/issues/786) - If an attribute defines a converter, the type of its parameter is used as type annotation for its corresponding `__init__` parameter. If an `attr.converters.pipe` is used, the first one's is used. [#710](https://github.com/python-attrs/attrs/issues/710) - Fixed the creation of an extra slot for an `attr.ib` when the parent class already has a slot with the same name. [#718](https://github.com/python-attrs/attrs/issues/718) - `__attrs__init__()` will now be injected if `init=False`, or if `auto_detect=True` and a user-defined `__init__()` exists. This enables users to do "pre-init" work in their `__init__()` (such as `super().__init__()`). `__init__()` can then delegate constructor argument processing to `self.__attrs_init__(*args, **kwargs)`. [#731](https://github.com/python-attrs/attrs/issues/731) - `bool(attr.NOTHING)` is now `False`. [#732](https://github.com/python-attrs/attrs/issues/732) - It's now possible to use `super()` inside of properties of slotted classes. [#747](https://github.com/python-attrs/attrs/issues/747) - Allow for a `__attrs_pre_init__()` method that -- if defined -- will get called at the beginning of the `attrs`-generated `__init__()` method. [#750](https://github.com/python-attrs/attrs/issues/750) - Added forgotten `attr.Attribute.evolve()` to type stubs. [#752](https://github.com/python-attrs/attrs/issues/752) - `attrs.evolve()` now works recursively with nested `attrs` classes. [#759](https://github.com/python-attrs/attrs/issues/759) - Python 3.10 is now officially supported. [#763](https://github.com/python-attrs/attrs/issues/763) - `attr.resolve_types()` now takes an optional *attrib* argument to work inside a `field_transformer`. [#774](https://github.com/python-attrs/attrs/issues/774) - `ClassVar`s are now also detected if they come from [typing-extensions](https://pypi.org/project/typing-extensions/). [#782](https://github.com/python-attrs/attrs/issues/782) - To make it easier to customize attribute comparison (#435), we have added the `attr.cmp_with()` helper. See the [new docs on comparison](https://www.attrs.org/en/stable/comparison.html) for more details. [#787](https://github.com/python-attrs/attrs/issues/787) - Added **provisional** support for static typing in `pyright` via [PEP 681](https://peps.python.org/pep-0681/). Both the `pyright` specification and `attrs` implementation may change in future versions of both projects. Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782). [#796](https://github.com/python-attrs/attrs/issues/796) ## [20.3.0](https://github.com/python-attrs/attrs/tree/20.3.0) - 2020-11-05 ### Backward-incompatible Changes - `attr.define()`, `attr.frozen()`, `attr.mutable()`, and `attr.field()` remain **provisional**. This release does **not** change anything about them and they are already used widely in production though. If you wish to use them together with mypy, you can simply drop [this plugin](https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py) into your project. Feel free to provide feedback to them in the linked issue #668. We will release the `attrs` namespace once we have the feeling that the APIs have properly settled. [#668](https://github.com/python-attrs/attrs/issues/668) ### Changes - `attr.s()` now has a *field_transformer* hook that is called for all `Attribute`s and returns a (modified or updated) list of `Attribute` instances. `attr.asdict()` has a *value_serializer* hook that can change the way values are converted. Both hooks are meant to help with data (de-)serialization workflows. [#653](https://github.com/python-attrs/attrs/issues/653) - `kw_only=True` now works on Python 2. [#700](https://github.com/python-attrs/attrs/issues/700) - `raise from` now works on frozen classes on PyPy. [#703](https://github.com/python-attrs/attrs/issues/703), [#712](https://github.com/python-attrs/attrs/issues/712) - `attr.asdict()` and `attr.astuple()` now treat `frozenset`s like `set`s with regards to the *retain_collection_types* argument. [#704](https://github.com/python-attrs/attrs/issues/704) - The type stubs for `attr.s()` and `attr.make_class()` are not missing the *collect_by_mro* argument anymore. [#711](https://github.com/python-attrs/attrs/issues/711) --- ## [20.2.0](https://github.com/python-attrs/attrs/tree/20.2.0) - 2020-09-05 ### Backward-incompatible Changes - `attr.define()`, `attr.frozen()`, `attr.mutable()`, and `attr.field()` remain **provisional**. This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged. If you wish to use them together with mypy, you can simply drop [this plugin](https://gist.github.com/hynek/1e3844d0c99e479e716169034b5fa963#file-attrs_ng_plugin-py) into your project. Feel free to provide feedback to them in the linked issue #668. We will release the `attrs` namespace once we have the feeling that the APIs have properly settled. [#668](https://github.com/python-attrs/attrs/issues/668) ### Changes - `attr.define()` et al now correctly detect `__eq__` and `__ne__`. [#671](https://github.com/python-attrs/attrs/issues/671) - `attr.define()` et al's hybrid behavior now also works correctly when arguments are passed. [#675](https://github.com/python-attrs/attrs/issues/675) - It's possible to define custom `__setattr__` methods on slotted classes again. [#681](https://github.com/python-attrs/attrs/issues/681) - In 20.1.0 we introduced the `inherited` attribute on the `attr.Attribute` class to differentiate attributes that have been inherited and those that have been defined directly on the class. It has shown to be problematic to involve that attribute when comparing instances of `attr.Attribute` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class. Therefore the `inherited` attribute will now be ignored when hashing and comparing instances of `attr.Attribute`. [#684](https://github.com/python-attrs/attrs/issues/684) - `zope.interface` is now a "soft dependency" when running the test suite; if `zope.interface` is not installed when running the test suite, the interface-related tests will be automatically skipped. [#685](https://github.com/python-attrs/attrs/issues/685) - The ergonomics of creating frozen classes using `@define(frozen=True)` and sub-classing frozen classes has been improved: you don't have to set `on_setattr=None` anymore. [#687](https://github.com/python-attrs/attrs/issues/687) --- ## [20.1.0](https://github.com/python-attrs/attrs/tree/20.1.0) - 2020-08-20 ### Backward-incompatible Changes - Python 3.4 is not supported anymore. It has been unsupported by the Python core team for a while now, its PyPI downloads are negligible, and our CI provider removed it as a supported option. It's very unlikely that `attrs` will break under 3.4 anytime soon, which is why we do *not* block its installation on Python 3.4. But we don't test it anymore and will block it once someone reports breakage. [#608](https://github.com/python-attrs/attrs/issues/608) ### Deprecations - Less of a deprecation and more of a heads up: the next release of `attrs` will introduce an `attrs` namespace. That means that you'll finally be able to run `import attrs` with new functions that aren't cute abbreviations and that will carry better defaults. This should not break any of your code, because project-local packages have priority before installed ones. If this is a problem for you for some reason, please report it to our bug tracker and we'll figure something out. The old `attr` namespace isn't going anywhere and its defaults are not changing – this is a purely additive measure. Please check out the linked issue for more details. These new APIs have been added *provisionally* as part of #666 so you can try them out today and provide feedback. Learn more in the [API docs](https://www.attrs.org/en/stable/api.html). [#408](https://github.com/python-attrs/attrs/issues/408) ### Changes - Added `attr.resolve_types()`. It ensures that all forward-references and types in string form are resolved into concrete types. You need this only if you need concrete types at runtime. That means that if you only use types for static type checking, you do **not** need this function. [#288](https://github.com/python-attrs/attrs/issues/288), [#302](https://github.com/python-attrs/attrs/issues/302) - Added `@attr.s(collect_by_mro=False)` argument that if set to `True` fixes the collection of attributes from base classes. It's only necessary for certain cases of multiple-inheritance but is kept off for now for backward-compatibility reasons. It will be turned on by default in the future. As a side-effect, `attr.Attribute` now *always* has an `inherited` attribute indicating whether an attribute on a class was directly defined or inherited. [#428](https://github.com/python-attrs/attrs/issues/428), [#635](https://github.com/python-attrs/attrs/issues/635) - On Python 3, all generated methods now have a docstring explaining that they have been created by `attrs`. [#506](https://github.com/python-attrs/attrs/issues/506) - It is now possible to prevent `attrs` from auto-generating the `__setstate__` and `__getstate__` methods that are required for pickling of slotted classes. Either pass `@attr.s(getstate_setstate=False)` or pass `@attr.s(auto_detect=True)` and implement them yourself: if `attrs` finds either of the two methods directly on the decorated class, it assumes implicitly `getstate_setstate=False` (and implements neither). This option works with dict classes but should never be necessary. [#512](https://github.com/python-attrs/attrs/issues/512), [#513](https://github.com/python-attrs/attrs/issues/513), [#642](https://github.com/python-attrs/attrs/issues/642) - Fixed a `ValueError: Cell is empty` bug that could happen in some rare edge cases. [#590](https://github.com/python-attrs/attrs/issues/590) - `attrs` can now automatically detect your own implementations and infer `init=False`, `repr=False`, `eq=False`, `order=False`, and `hash=False` if you set `@attr.s(auto_detect=True)`. `attrs` will ignore inherited methods. If the argument implies more than one method (for example, `eq=True` creates both `__eq__` and `__ne__`), it's enough for *one* of them to exist and `attrs` will create *neither*. This feature requires Python 3. [#607](https://github.com/python-attrs/attrs/issues/607) - Added `attr.converters.pipe()`. The feature allows combining multiple conversion callbacks into one by piping the value through all of them, and retuning the last result. As part of this feature, we had to relax the type information for converter callables. [#618](https://github.com/python-attrs/attrs/issues/618) - Fixed serialization behavior of non-slots classes with `cache_hash=True`. The hash cache will be cleared on operations which make "deep copies" of instances of classes with hash caching, though the cache will not be cleared with shallow copies like those made by `copy.copy()`. Previously, `copy.deepcopy()` or serialization and deserialization with `pickle` would result in an un-initialized object. This change also allows the creation of `cache_hash=True` classes with a custom `__setstate__`, which was previously forbidden ([#494](https://github.com/python-attrs/attrs/issues/494)). [#620](https://github.com/python-attrs/attrs/issues/620) - It is now possible to specify hooks that are called whenever an attribute is set **after** a class has been instantiated. You can pass `on_setattr` both to `@attr.s()` to set the default for all attributes on a class, and to `@attr.ib()` to overwrite it for individual attributes. `attrs` also comes with a new module `attr.setters` that brings helpers that run validators, converters, or allow to freeze a subset of attributes. [#645](https://github.com/python-attrs/attrs/issues/645), [#660](https://github.com/python-attrs/attrs/issues/660) - **Provisional** APIs called `attr.define()`, `attr.mutable()`, and `attr.frozen()` have been added. They are only available on Python 3.6 and later, and call `attr.s()` with different default values. If nothing comes up, they will become the official way for creating classes in 20.2.0 (see above). **Please note** that it may take some time until mypy – and other tools that have dedicated support for `attrs` – recognize these new APIs. Please **do not** open issues on our bug tracker, there is nothing we can do about it. [#666](https://github.com/python-attrs/attrs/issues/666) - We have also provisionally added `attr.field()` that supplants `attr.ib()`. It also requires at least Python 3.6 and is keyword-only. Other than that, it only dropped a few arguments, but changed no defaults. As with `attr.s()`: `attr.ib()` is not going anywhere. [#669](https://github.com/python-attrs/attrs/issues/669) --- ## [19.3.0](https://github.com/python-attrs/attrs/tree/19.3.0) - 2019-10-15 ### Changes - Fixed `auto_attribs` usage when default values cannot be compared directly with `==`, such as `numpy` arrays. [#585](https://github.com/python-attrs/attrs/issues/585) --- ## [19.2.0](https://github.com/python-attrs/attrs/tree/19.2.0) - 2019-10-01 ### Backward-incompatible Changes - Removed deprecated `Attribute` attribute `convert` per scheduled removal on 2019/1. This planned deprecation is tracked in issue [#307](https://github.com/python-attrs/attrs/issues/307). [#504](https://github.com/python-attrs/attrs/issues/504) - `__lt__`, `__le__`, `__gt__`, and `__ge__` do not consider subclasses comparable anymore. This has been deprecated since 18.2.0 and was raising a `DeprecationWarning` for over a year. [#570](https://github.com/python-attrs/attrs/issues/570) ### Deprecations - The `cmp` argument to `attr.s()` and `attr.ib()` is now deprecated. Please use `eq` to add equality methods (`__eq__` and `__ne__`) and `order` to add ordering methods (`__lt__`, `__le__`, `__gt__`, and `__ge__`) instead – just like with [dataclasses](https://docs.python.org/3/library/dataclasses.html). Both are effectively `True` by default but it's enough to set `eq=False` to disable both at once. Passing `eq=False, order=True` explicitly will raise a `ValueError` though. Since this is arguably a deeper backward-compatibility break, it will have an extended deprecation period until 2021-06-01. After that day, the `cmp` argument will be removed. `attr.Attribute` also isn't orderable anymore. [#574](https://github.com/python-attrs/attrs/issues/574) ### Changes - Updated `attr.validators.__all__` to include new validators added in [#425]. [#517](https://github.com/python-attrs/attrs/issues/517) - Slotted classes now use a pure Python mechanism to rewrite the `__class__` cell when rebuilding the class, so `super()` works even on environments where `ctypes` is not installed. [#522](https://github.com/python-attrs/attrs/issues/522) - When collecting attributes using `@attr.s(auto_attribs=True)`, attributes with a default of `None` are now deleted too. [#523](https://github.com/python-attrs/attrs/issues/523), [#556](https://github.com/python-attrs/attrs/issues/556) - Fixed `attr.validators.deep_iterable()` and `attr.validators.deep_mapping()` type stubs. [#533](https://github.com/python-attrs/attrs/issues/533) - `attr.validators.is_callable()` validator now raises an exception `attr.exceptions.NotCallableError`, a subclass of `TypeError`, informing the received value. [#536](https://github.com/python-attrs/attrs/issues/536) - `@attr.s(auto_exc=True)` now generates classes that are hashable by ID, as the documentation always claimed it would. [#543](https://github.com/python-attrs/attrs/issues/543), [#563](https://github.com/python-attrs/attrs/issues/563) - Added `attr.validators.matches_re()` that checks string attributes whether they match a regular expression. [#552](https://github.com/python-attrs/attrs/issues/552) - Keyword-only attributes (`kw_only=True`) and attributes that are excluded from the `attrs`'s `__init__` (`init=False`) now can appear before mandatory attributes. [#559](https://github.com/python-attrs/attrs/issues/559) - The fake filename for generated methods is now more stable. It won't change when you restart the process. [#560](https://github.com/python-attrs/attrs/issues/560) - The value passed to `@attr.ib(repr=…)` can now be either a boolean (as before) or a callable. That callable must return a string and is then used for formatting the attribute by the generated `__repr__()` method. [#568](https://github.com/python-attrs/attrs/issues/568) - Added `attr.__version_info__` that can be used to reliably check the version of `attrs` and write forward- and backward-compatible code. Please check out the [section on deprecated APIs](https://www.attrs.org/en/stable/api-attr.html#deprecated-apis) on how to use it. [#580](https://github.com/python-attrs/attrs/issues/580) > --- ## [19.1.0](https://github.com/python-attrs/attrs/tree/19.1.0) - 2019-03-03 ### Backward-incompatible Changes - Fixed a bug where deserialized objects with `cache_hash=True` could have incorrect hash code values. This change breaks classes with `cache_hash=True` when a custom `__setstate__` is present. An exception will be thrown when applying the `attrs` annotation to such a class. This limitation is tracked in issue [#494](https://github.com/python-attrs/attrs/issues/494). [#482](https://github.com/python-attrs/attrs/issues/482) ### Changes - Add `is_callable`, `deep_iterable`, and `deep_mapping` validators. - `is_callable`: validates that a value is callable - `deep_iterable`: Allows recursion down into an iterable, applying another validator to every member in the iterable as well as applying an optional validator to the iterable itself. - `deep_mapping`: Allows recursion down into the items in a mapping object, applying a key validator and a value validator to the key and value in every item. Also applies an optional validator to the mapping object itself. You can find them in the `attr.validators` package. [#425] - Fixed stub files to prevent errors raised by mypy's `disallow_any_generics = True` option. [#443](https://github.com/python-attrs/attrs/issues/443) - Attributes with `init=False` now can follow after `kw_only=True` attributes. [#450](https://github.com/python-attrs/attrs/issues/450) - `attrs` now has first class support for defining exception classes. If you define a class using `@attr.s(auto_exc=True)` and subclass an exception, the class will behave like a well-behaved exception class including an appropriate `__str__` method, and all attributes additionally available in an `args` attribute. [#500](https://github.com/python-attrs/attrs/issues/500) - Clarified documentation for hashing to warn that hashable objects should be deeply immutable (in their usage, even if this is not enforced). [#503](https://github.com/python-attrs/attrs/issues/503) --- ## [18.2.0](https://github.com/python-attrs/attrs/tree/18.2.0) - 2018-09-01 ### Deprecations - Comparing subclasses using `<`, `>`, `<=`, and `>=` is now deprecated. The docs always claimed that instances are only compared if the types are identical, so this is a first step to conform to the docs. Equality operators (`==` and `!=`) were always strict in this regard. [#394](https://github.com/python-attrs/attrs/issues/394) ### Changes - `attrs` now ships its own [PEP 484](https://peps.python.org/pep-0484/) type hints. Together with [mypy](http://mypy-lang.org)'s `attrs` plugin, you've got all you need for writing statically typed code in both Python 2 and 3! At that occasion, we've also added [narrative docs](https://www.attrs.org/en/stable/types.html) about type annotations in `attrs`. [#238](https://github.com/python-attrs/attrs/issues/238) - Added *kw_only* arguments to `attr.ib` and `attr.s`, and a corresponding *kw_only* attribute to `attr.Attribute`. This change makes it possible to have a generated `__init__` with keyword-only arguments on Python 3, relaxing the required ordering of default and non-default valued attributes. [#281](https://github.com/python-attrs/attrs/issues/281), [#411](https://github.com/python-attrs/attrs/issues/411) - The test suite now runs with `hypothesis.HealthCheck.too_slow` disabled to prevent CI breakage on slower computers. [#364](https://github.com/python-attrs/attrs/issues/364), [#396](https://github.com/python-attrs/attrs/issues/396) - `attr.validators.in_()` now raises a `ValueError` with a useful message even if the options are a string and the value is not a string. [#383](https://github.com/python-attrs/attrs/issues/383) - `attr.asdict()` now properly handles deeply nested lists and dictionaries. [#395](https://github.com/python-attrs/attrs/issues/395) - Added `attr.converters.default_if_none()` that allows to replace `None` values in attributes. For example `attr.ib(converter=default_if_none(""))` replaces `None` by empty strings. [#400](https://github.com/python-attrs/attrs/issues/400), [#414](https://github.com/python-attrs/attrs/issues/414) - Fixed a reference leak where the original class would remain live after being replaced when `slots=True` is set. [#407](https://github.com/python-attrs/attrs/issues/407) - Slotted classes can now be made weakly referenceable by passing `@attr.s(weakref_slot=True)`. [#420](https://github.com/python-attrs/attrs/issues/420) - Added *cache_hash* option to `@attr.s` which causes the hash code to be computed once and stored on the object. [#426](https://github.com/python-attrs/attrs/issues/426) - Attributes can be named `property` and `itemgetter` now. [#430](https://github.com/python-attrs/attrs/issues/430) - It is now possible to override a base class' class variable using only class annotations. [#431](https://github.com/python-attrs/attrs/issues/431) --- ## [18.1.0](https://github.com/python-attrs/attrs/tree/18.1.0) - 2018-05-03 ### Changes - `x=X(); x.cycle = x; repr(x)` will no longer raise a `RecursionError`, and will instead show as `X(x=...)`. [#95](https://github.com/python-attrs/attrs/issues/95) - `attr.ib(factory=f)` is now syntactic sugar for the common case of `attr.ib(default=attr.Factory(f))`. [#178](https://github.com/python-attrs/attrs/issues/178), [#356](https://github.com/python-attrs/attrs/issues/356) - Added `attr.field_dict()` to return an ordered dictionary of `attrs` attributes for a class, whose keys are the attribute names. [#290](https://github.com/python-attrs/attrs/issues/290), [#349](https://github.com/python-attrs/attrs/issues/349) - The order of attributes that are passed into `attr.make_class()` or the *these* argument of `@attr.s()` is now retained if the dictionary is ordered (in other words: `dict` on Python 3.6 and later, `collections.OrderedDict` otherwise). Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programmatically. [#300](https://github.com/python-attrs/attrs/issues/300), [#339](https://github.com/python-attrs/attrs/issues/339), [#343](https://github.com/python-attrs/attrs/issues/343) - In slotted classes, `__getstate__` and `__setstate__` now ignore the `__weakref__` attribute. [#311](https://github.com/python-attrs/attrs/issues/311), [#326](https://github.com/python-attrs/attrs/issues/326) - Setting the cell type is now completely best effort. This fixes `attrs` on Jython. We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatibilities. [#321](https://github.com/python-attrs/attrs/issues/321), [#334](https://github.com/python-attrs/attrs/issues/334) - If `attr.s` is passed a *these* argument, it will no longer attempt to remove attributes with the same name from the class body. [#322](https://github.com/python-attrs/attrs/issues/322), [#323](https://github.com/python-attrs/attrs/issues/323) - The hash of `attr.NOTHING` is now vegan and faster on 32bit Python builds. [#331](https://github.com/python-attrs/attrs/issues/331), [#332](https://github.com/python-attrs/attrs/issues/332) - The overhead of instantiating frozen dict classes is virtually eliminated. [#336](https://github.com/python-attrs/attrs/issues/336) - Generated `__init__` methods now have an `__annotations__` attribute derived from the types of the fields. [#363](https://github.com/python-attrs/attrs/issues/363) - We have restructured the documentation a bit to account for `attrs`' growth in scope. Instead of putting everything into the [examples](https://www.attrs.org/en/stable/examples.html) page, we have started to extract narrative chapters. So far, we've added chapters on [initialization](https://www.attrs.org/en/stable/init.html) and [hashing](https://www.attrs.org/en/stable/hashing.html). Expect more to come! [#369](https://github.com/python-attrs/attrs/issues/369), [#370](https://github.com/python-attrs/attrs/issues/370) --- ## [17.4.0](https://github.com/python-attrs/attrs/tree/17.4.0) - 2017-12-30 ### Backward-incompatible Changes - The traversal of MROs when using multiple inheritance was backward: If you defined a class `C` that subclasses `A` and `B` like `C(A, B)`, `attrs` would have collected the attributes from `B` *before* those of `A`. This is now fixed and means that in classes that employ multiple inheritance, the output of `__repr__` and the order of positional arguments in `__init__` changes. Because of the nature of this bug, a proper deprecation cycle was unfortunately impossible. Generally speaking, it's advisable to prefer `kwargs`-based initialization anyways – *especially* if you employ multiple inheritance and diamond-shaped hierarchies. [#298](https://github.com/python-attrs/attrs/issues/298), [#299](https://github.com/python-attrs/attrs/issues/299), [#304](https://github.com/python-attrs/attrs/issues/304) - The `__repr__` set by `attrs` no longer produces an `AttributeError` when the instance is missing some of the specified attributes (either through deleting or after using `init=False` on some attributes). This can break code that relied on `repr(attr_cls_instance)` raising `AttributeError` to check if any `attrs`-specified members were unset. If you were using this, you can implement a custom method for checking this: ``` def has_unset_members(self): for field in attr.fields(type(self)): try: getattr(self, field.name) except AttributeError: return True return False ``` [#308](https://github.com/python-attrs/attrs/issues/308) ### Deprecations - The `attr.ib(convert=callable)` option is now deprecated in favor of `attr.ib(converter=callable)`. This is done to achieve consistency with other noun-based arguments like *validator*. *convert* will keep working until at least January 2019 while raising a `DeprecationWarning`. [#307](https://github.com/python-attrs/attrs/issues/307) ### Changes - Generated `__hash__` methods now hash the class type along with the attribute values. Until now the hashes of two classes with the same values were identical which was a bug. The generated method is also *much* faster now. [#261](https://github.com/python-attrs/attrs/issues/261), [#295](https://github.com/python-attrs/attrs/issues/295), [#296](https://github.com/python-attrs/attrs/issues/296) - `attr.ib`’s *metadata* argument now defaults to a unique empty `dict` instance instead of sharing a common empty `dict` for all. The singleton empty `dict` is still enforced. [#280](https://github.com/python-attrs/attrs/issues/280) - `ctypes` is optional now however if it's missing, a bare `super()` will not work in slotted classes. This should only happen in special environments like Google App Engine. [#284](https://github.com/python-attrs/attrs/issues/284), [#286](https://github.com/python-attrs/attrs/issues/286) - The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance. In that case, the definition that is closer to the base of the class hierarchy wins. [#285](https://github.com/python-attrs/attrs/issues/285), [#287](https://github.com/python-attrs/attrs/issues/287) - Subclasses of `auto_attribs=True` can be empty now. [#291](https://github.com/python-attrs/attrs/issues/291), [#292](https://github.com/python-attrs/attrs/issues/292) - Equality tests are *much* faster now. [#306](https://github.com/python-attrs/attrs/issues/306) - All generated methods now have correct `__module__`, `__name__`, and (on Python 3) `__qualname__` attributes. [#309](https://github.com/python-attrs/attrs/issues/309) --- ## [17.3.0](https://github.com/python-attrs/attrs/tree/17.3.0) - 2017-11-08 ### Backward-incompatible Changes - Attributes are no longer defined on the class body. This means that if you define a class `C` with an attribute `x`, the class will *not* have an attribute `x` for introspection. Instead of `C.x`, use `attr.fields(C).x` or look at `C.__attrs_attrs__`. The old behavior has been deprecated since version 16.1. ([#253](https://github.com/python-attrs/attrs/issues/253)) ### Changes - `super()` and `__class__` now work with slotted classes on Python 3. ([#102](https://github.com/python-attrs/attrs/issues/102), [#226](https://github.com/python-attrs/attrs/issues/226), [#269](https://github.com/python-attrs/attrs/issues/269), [#270](https://github.com/python-attrs/attrs/issues/270), [#272](https://github.com/python-attrs/attrs/issues/272)) - Added *type* argument to `attr.ib()` and corresponding `type` attribute to `attr.Attribute`. This change paves the way for automatic type checking and serialization (though as of this release `attrs` does not make use of it). In Python 3.6 or higher, the value of `attr.Attribute.type` can alternately be set using variable type annotations (see [PEP 526](https://peps.python.org/pep-0526/)). ([#151](https://github.com/python-attrs/attrs/issues/151), [#214](https://github.com/python-attrs/attrs/issues/214), [#215](https://github.com/python-attrs/attrs/issues/215), [#239](https://github.com/python-attrs/attrs/issues/239)) - The combination of `str=True` and `slots=True` now works on Python 2. ([#198](https://github.com/python-attrs/attrs/issues/198)) - `attr.Factory` is hashable again. ([#204](https://github.com/python-attrs/attrs/issues/204)) - Subclasses now can overwrite attribute definitions of their base classes. That means that you can -- for example -- change the default value for an attribute by redefining it. ([#221](https://github.com/python-attrs/attrs/issues/221), [#229](https://github.com/python-attrs/attrs/issues/229)) - Added new option *auto_attribs* to `@attr.s` that allows to collect annotated fields without setting them to `attr.ib()`. Setting a field to an `attr.ib()` is still possible to supply options like validators. Setting it to any other value is treated like it was passed as `attr.ib(default=value)` -- passing an instance of `attr.Factory` also works as expected. ([#262](https://github.com/python-attrs/attrs/issues/262), [#277](https://github.com/python-attrs/attrs/issues/277)) - Instances of classes created using `attr.make_class()` can now be pickled. ([#282](https://github.com/python-attrs/attrs/issues/282)) --- ## [17.2.0](https://github.com/python-attrs/attrs/tree/17.2.0) - 2017-05-24 ### Changes: - Validators are hashable again. Note that validators may become frozen in the future, pending availability of no-overhead frozen classes. [#192](https://github.com/python-attrs/attrs/issues/192) --- ## [17.1.0](https://github.com/python-attrs/attrs/tree/17.1.0) - 2017-05-16 To encourage more participation, the project has also been moved into a [dedicated GitHub organization](https://github.com/python-attrs/) and everyone is most welcome to join! `attrs` also has a logo now! ```{image} https://www.attrs.org/en/latest/_static/attrs_logo.png :alt: attrs logo ``` ### Backward-incompatible Changes: - `attrs` will set the `__hash__()` method to `None` by default now. The way hashes were handled before was in conflict with [Python's specification](https://docs.python.org/3/reference/datamodel.html#object.__hash__). This *may* break some software although this breakage is most likely just surfacing of latent bugs. You can always make `attrs` create the `__hash__()` method using `@attr.s(hash=True)`. See [#136] for the rationale of this change. :::{warning} Please *do not* upgrade blindly and *do* test your software! *Especially* if you use instances as dict keys or put them into sets! ::: - Correspondingly, `attr.ib`'s *hash* argument is `None` by default too and mirrors the *cmp* argument as it should. ### Deprecations: - `attr.assoc()` is now deprecated in favor of `attr.evolve()` and will stop working in 2018. ### Changes: - Fix default hashing behavior. Now *hash* mirrors the value of *cmp* and classes are unhashable by default. [#136] [#142](https://github.com/python-attrs/attrs/issues/142) - Added `attr.evolve()` that, given an instance of an `attrs` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied. `evolve()` replaces `assoc()`, which is now deprecated. `evolve()` is significantly faster than `assoc()`, and requires the class have an initializer that can take the field values as keyword arguments (like `attrs` itself can generate). [#116](https://github.com/python-attrs/attrs/issues/116) [#124](https://github.com/python-attrs/attrs/pull/124) [#135](https://github.com/python-attrs/attrs/pull/135) - `FrozenInstanceError` is now raised when trying to delete an attribute from a frozen class. [#118](https://github.com/python-attrs/attrs/pull/118) - Frozen-ness of classes is now inherited. [#128](https://github.com/python-attrs/attrs/pull/128) - `__attrs_post_init__()` is now run if validation is disabled. [#130](https://github.com/python-attrs/attrs/pull/130) - Added `attr.validators.in_(options)` that, given the allowed `options`, checks whether the attribute value is in it. This can be used to check constants, enums, mappings, etc. [#181](https://github.com/python-attrs/attrs/pull/181) - Added `attr.validators.and_()` that composes multiple validators into one. [#161](https://github.com/python-attrs/attrs/issues/161) - For convenience, the *validator* argument of `@attr.s` now can take a list of validators that are wrapped using `and_()`. [#138](https://github.com/python-attrs/attrs/issues/138) - Accordingly, `attr.validators.optional()` now can take a list of validators too. [#161](https://github.com/python-attrs/attrs/issues/161) - Validators can now be defined conveniently inline by using the attribute as a decorator. Check out the [validator examples](https://www.attrs.org/en/stable/init.html#decorator) to see it in action! [#143](https://github.com/python-attrs/attrs/issues/143) - `attr.Factory()` now has a *takes_self* argument that makes the initializer to pass the partially initialized instance into the factory. In other words you can define attribute defaults based on other attributes. [#165] [#189](https://github.com/python-attrs/attrs/issues/189) - Default factories can now also be defined inline using decorators. They are *always* passed the partially initialized instance. [#165] - Conversion can now be made optional using `attr.converters.optional()`. [#105](https://github.com/python-attrs/attrs/issues/105) [#173](https://github.com/python-attrs/attrs/pull/173) - `attr.make_class()` now accepts the keyword argument `bases` which allows for subclassing. [#152](https://github.com/python-attrs/attrs/pull/152) - Metaclasses are now preserved with `slots=True`. [#155](https://github.com/python-attrs/attrs/pull/155) --- ## [16.3.0](https://github.com/python-attrs/attrs/tree/16.3.0) - 2016-11-24 ### Changes: - Attributes now can have user-defined metadata which greatly improves `attrs`'s extensibility. [#96](https://github.com/python-attrs/attrs/pull/96) - Allow for a `__attrs_post_init__()` method that -- if defined -- will get called at the end of the `attrs`-generated `__init__()` method. [#111](https://github.com/python-attrs/attrs/pull/111) - Added `@attr.s(str=True)` that will optionally create a `__str__()` method that is identical to `__repr__()`. This is mainly useful with `Exception`s and other classes that rely on a useful `__str__()` implementation but overwrite the default one through a poor own one. Default Python class behavior is to use `__repr__()` as `__str__()` anyways. If you tried using `attrs` with `Exception`s and were puzzled by the tracebacks: this option is for you. - `__name__` is no longer overwritten with `__qualname__` for `attr.s(slots=True)` classes. [#99](https://github.com/python-attrs/attrs/issues/99) --- ## [16.2.0](https://github.com/python-attrs/attrs/tree/16.2.0) - 2016-09-17 ### Changes: - Added `attr.astuple()` that -- similarly to `attr.asdict()` -- returns the instance as a tuple. [#77](https://github.com/python-attrs/attrs/issues/77) - Converters now work with frozen classes. [#76](https://github.com/python-attrs/attrs/issues/76) - Instantiation of `attrs` classes with converters is now significantly faster. [#80](https://github.com/python-attrs/attrs/pull/80) - Pickling now works with slotted classes. [#81](https://github.com/python-attrs/attrs/issues/81) - `attr.assoc()` now works with slotted classes. [#84](https://github.com/python-attrs/attrs/issues/84) - The tuple returned by `attr.fields()` now also allows to access the `Attribute` instances by name. Yes, we've subclassed `tuple` so you don't have to! Therefore `attr.fields(C).x` is equivalent to the deprecated `C.x` and works with slotted classes. [#88](https://github.com/python-attrs/attrs/issues/88) --- ## [16.1.0](https://github.com/python-attrs/attrs/tree/16.1.0) - 2016-08-30 ### Backward-incompatible Changes: - All instances where function arguments were called `cl` have been changed to the more Pythonic `cls`. Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form. If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart. ### Deprecations: - Accessing `Attribute` instances on class objects is now deprecated and will stop working in 2017. If you need introspection please use the `__attrs_attrs__` attribute or the `attr.fields()` function that carry them too. In the future, the attributes that are defined on the class body and are usually overwritten in your `__init__` method are simply removed after `@attr.s` has been applied. This will remove the confusing error message if you write your own `__init__` and forget to initialize some attribute. Instead you will get a straightforward `AttributeError`. In other words: decorated classes will work more like plain Python classes which was always `attrs`'s goal. - The serious-business aliases `attr.attributes` and `attr.attr` have been deprecated in favor of `attr.attrs` and `attr.attrib` which are much more consistent and frankly obvious in hindsight. They will be purged from documentation immediately but there are no plans to actually remove them. ### Changes: - `attr.asdict()`'s `dict_factory` arguments is now propagated on recursion. [#45](https://github.com/python-attrs/attrs/issues/45) - `attr.asdict()`, `attr.has()` and `attr.fields()` are significantly faster. [#48](https://github.com/python-attrs/attrs/issues/48) [#51](https://github.com/python-attrs/attrs/issues/51) - Add `attr.attrs` and `attr.attrib` as a more consistent aliases for `attr.s` and `attr.ib`. - Add *frozen* option to `attr.s` that will make instances best-effort immutable. [#60](https://github.com/python-attrs/attrs/issues/60) - `attr.asdict()` now takes `retain_collection_types` as an argument. If `True`, it does not convert attributes of type `tuple` or `set` to `list`. [#69](https://github.com/python-attrs/attrs/issues/69) --- ## [16.0.0](https://github.com/python-attrs/attrs/tree/16.0.0) - 2016-05-23 ### Backward-incompatible Changes: - Python 3.3 and 2.6 are no longer supported. They may work by chance but any effort to keep them working has ceased. The last Python 2.6 release was on October 29, 2013 and is no longer supported by the CPython core team. Major Python packages like Django and Twisted dropped Python 2.6 a while ago already. Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release. ### Changes: - `__slots__` have arrived! Classes now can automatically be [slotted](https://docs.python.org/3/reference/datamodel.html#slots)-style (and save your precious memory) just by passing `slots=True`. [#35](https://github.com/python-attrs/attrs/issues/35) - Allow the case of initializing attributes that are set to `init=False`. This allows for clean initializer parameter lists while being able to initialize attributes to default values. [#32](https://github.com/python-attrs/attrs/issues/32) - `attr.asdict()` can now produce arbitrary mappings instead of Python `dict`s when provided with a `dict_factory` argument. [#40](https://github.com/python-attrs/attrs/issues/40) - Multiple performance improvements. --- ## [15.2.0](https://github.com/python-attrs/attrs/tree/15.2.0) - 2015-12-08 ### Changes: - Added a `convert` argument to `attr.ib`, which allows specifying a function to run on arguments. This allows for simple type conversions, for example, with `attr.ib(convert=int)`. [#26](https://github.com/python-attrs/attrs/issues/26) - Speed up object creation when attribute validators are used. [#28](https://github.com/python-attrs/attrs/issues/28) --- ## [15.1.0](https://github.com/python-attrs/attrs/tree/15.1.0) - 2015-08-20 ### Changes: - Added `attr.validators.optional()` that wraps other validators allowing attributes to be `None`. [#16](https://github.com/python-attrs/attrs/issues/16) - Multi-level inheritance now works. [#24](https://github.com/python-attrs/attrs/issues/24) - `__repr__()` now works with non-redecorated subclasses. [#20](https://github.com/python-attrs/attrs/issues/20) --- ## [15.0.0](https://github.com/python-attrs/attrs/tree/15.0.0) - 2015-04-15 ### Changes: Initial release. [#136]: https://github.com/python-attrs/attrs/issues/136 [#165]: https://github.com/python-attrs/attrs/issues/165 [#425]: https://github.com/python-attrs/attrs/issues/425 python-attrs-attrs-bd2446d/CITATION.cff000066400000000000000000000003271476453530700176460ustar00rootroot00000000000000cff-version: 1.2.0 message: If you use this software, please cite it as below. title: attrs type: software authors: - given-names: Hynek family-names: Schlawack email: hs@ox.cx doi: 10.5281/zenodo.6925130 python-attrs-attrs-bd2446d/LICENSE000066400000000000000000000021251476453530700167570ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Hynek Schlawack and the attrs contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-attrs-attrs-bd2446d/README.md000066400000000000000000000163031476453530700172340ustar00rootroot00000000000000

attrs

Documentation Downloads per month DOI

*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). [Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020! Its main goal is to help you to write **concise** and **correct** software without slowing down your code. ## Sponsors *attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). Especially those generously supporting us at the *The Organization* tier and higher:

Please consider joining them to help make attrs’s maintenance more sustainable!

## Example *attrs* gives you a class decorator and a way to declaratively define the attributes on that class: ```pycon >>> from attrs import asdict, define, make_class, Factory >>> @define ... class SomeClass: ... a_number: int = 42 ... list_of_numbers: list[int] = Factory(list) ... ... def hard_math(self, another_number): ... return self.a_number + sum(self.list_of_numbers) * another_number >>> sc = SomeClass(1, [1, 2, 3]) >>> sc SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) >>> sc.hard_math(3) 19 >>> sc == SomeClass(1, [1, 2, 3]) True >>> sc != SomeClass(2, [3, 2, 1]) True >>> asdict(sc) {'a_number': 1, 'list_of_numbers': [1, 2, 3]} >>> SomeClass() SomeClass(a_number=42, list_of_numbers=[]) >>> C = make_class("C", ["a", "b"]) >>> C("foo", "bar") C(a='foo', b='bar') ``` After *declaring* your attributes, *attrs* gives you: - a concise and explicit overview of the class's attributes, - a nice human-readable `__repr__`, - equality-checking methods, - an initializer, - and much more, *without* writing dull boilerplate code again and again and *without* runtime performance penalties. --- This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation! ### Hate Type Annotations!? No problem! Types are entirely **optional** with *attrs*. Simply assign `attrs.field()` to the attributes instead of annotating them with types: ```python from attrs import define, field @define class SomeClass: a_number = field(default=42) list_of_numbers = field(factory=list) ``` ## Data Classes On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). In practice it does a lot more and is more flexible. For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger. For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice. ## Project Information - [**Changelog**](https://www.attrs.org/en/stable/changelog.html) - [**Documentation**](https://www.attrs.org/) - [**PyPI**](https://pypi.org/project/attrs/) - [**Source Code**](https://github.com/python-attrs/attrs) - [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) - [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) - **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs) ### *attrs* for Enterprise Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. python-attrs-attrs-bd2446d/bench/000077500000000000000000000000001476453530700170315ustar00rootroot00000000000000python-attrs-attrs-bd2446d/bench/test_benchmarks.py000066400000000000000000000041211476453530700225550ustar00rootroot00000000000000""" Benchmark attrs using CodSpeed. """ from __future__ import annotations import pytest import attrs pytestmark = pytest.mark.benchmark() ROUNDS = 1_000 def test_create_simple_class(): """ Benchmark creating a simple class without any extras. """ for _ in range(ROUNDS): @attrs.define class LocalC: x: int y: str z: dict[str, int] def test_create_frozen_class(): """ Benchmark creating a frozen class without any extras. """ for _ in range(ROUNDS): @attrs.frozen class LocalC: x: int y: str z: dict[str, int] LocalC(1, "2", {}) def test_create_simple_class_make_class(): """ Benchmark creating a simple class using attrs.make_class(). """ for i in range(ROUNDS): LocalC = attrs.make_class( f"LocalC{i}", { "x": attrs.field(type=int), "y": attrs.field(type=str), "z": attrs.field(type=dict[str, int]), }, ) LocalC(1, "2", {}) @attrs.define class C: x: int = 0 y: str = "foo" z: dict[str, int] = attrs.Factory(dict) def test_instantiate_no_defaults(): """ Benchmark instantiating a class without using any defaults. """ for _ in range(ROUNDS): C(1, "2", {}) def test_instantiate_with_defaults(): """ Benchmark instantiating a class relying on defaults. """ for _ in range(ROUNDS): C() def test_eq_equal(): """ Benchmark comparing two equal instances for equality. """ c1 = C() c2 = C() for _ in range(ROUNDS): c1 == c2 def test_eq_unequal(): """ Benchmark comparing two unequal instances for equality. """ c1 = C() c2 = C(1, "bar", {"baz": 42}) for _ in range(ROUNDS): c1 == c2 @attrs.frozen class HashableC: x: int = 0 y: str = "foo" z: tuple[str] = ("bar",) def test_hash(): """ Benchmark hashing an instance. """ c = HashableC() for _ in range(ROUNDS): hash(c) python-attrs-attrs-bd2446d/changelog.d/000077500000000000000000000000001476453530700201235ustar00rootroot00000000000000python-attrs-attrs-bd2446d/changelog.d/towncrier_template.md.jinja000066400000000000000000000014371476453530700254530ustar00rootroot00000000000000{%- if versiondata["version"] == "main" -%} ## Changes for the Upcoming Release :::{warning} These changes reflect the current [development progress](https://github.com/python-attrs/attrs/tree/main) and have **not** been part of a PyPI release yet. ::: {% else -%} ## [{{ versiondata["version"] }}](https://github.com/python-attrs/attrs/tree/{{ versiondata["version"] }}) - {{ versiondata["date"] }} {%- endif %} {% for section, _ in sections.items() %} {% if sections[section] %} {% for category, val in definitions.items() if category in sections[section] %} ### {{ definitions[category]['name'] }} {% for text, values in sections[section][category].items() %} - {{ text }} {{ values|join(',\n ') }} {% endfor %} {% endfor %} {% else %} No significant changes. {% endif %} {% endfor %} python-attrs-attrs-bd2446d/conftest.py000066400000000000000000000014551476453530700201560ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from datetime import timedelta import pytest from hypothesis import HealthCheck, settings from attr._compat import PY_3_10_PLUS @pytest.fixture(name="slots", params=(True, False)) def _slots(request): return request.param @pytest.fixture(name="frozen", params=(True, False)) def _frozen(request): return request.param def pytest_configure(config): # HealthCheck.too_slow causes more trouble than good -- especially in CIs. settings.register_profile( "patience", settings( suppress_health_check=[HealthCheck.too_slow], deadline=timedelta(milliseconds=400), ), ) settings.load_profile("patience") collect_ignore = [] if not PY_3_10_PLUS: collect_ignore.extend(["tests/test_pattern_matching.py"]) python-attrs-attrs-bd2446d/docs/000077500000000000000000000000001476453530700167025ustar00rootroot00000000000000python-attrs-attrs-bd2446d/docs/Makefile000066400000000000000000000151461476453530700203510ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # 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 clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 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 " 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)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." 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." 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/attrs.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/attrs" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 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)." 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." 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." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 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)." 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." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." 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." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-attrs-attrs-bd2446d/docs/_static/000077500000000000000000000000001476453530700203305ustar00rootroot00000000000000python-attrs-attrs-bd2446d/docs/_static/attrs_logo.png000066400000000000000000000167271476453530700232300ustar00rootroot00000000000000PNG  IHDR~; sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<TIDATx{|Wyϙjl'%sv-9&NG(oRJ Iymmӷ6P @ڄ[1@bllegfumؒeZiw?vvvfV磏sΜgB)[D2!"c @[[$k5 ſKD7_,#.0z{{#Afc3i;Vx] ."LnQJ}׉$3_NDI\ "a n wL&oTJ}_ !nLӻf^L$VJRR ۘ9?ZA p]@H)pM*f)m̦Rԅp] [n7+f*.D"I){WʻYž c(Z4,ᴡ~mDP6A,{ua]wL< !KS1J4 _O CDS FW\ y%JE'&&SgQ=tzrD8H6݆+.t)n*o0- ~rM8mD"6Qۉd^e[fV 0_ W0Y wPÓ,3n!`;̃.n4{J,[E."iPBD>4?T4{޺*o}X]Me"z@a>Z"`e օГ" :::Z[[_ @U#Rj`%{5[B /3 DկkS۫n^ }Žu%Tv{#3_1zU8~MD p;7 n.H\T!Ht{l@AR|5]/,UjPZd7km$!̼Rd2Q7G"|SRR .f~-3ȖZvUp Ol_:Dtaߩ/)ޝd[oANNQK)8.=(?($; !uӹzNNQ%WJAO"a[ QL& f~r+u=N?, E?fMiޖ|1qm,8j \W[c歖e=Wpƍ?YRQ8d`TΏu: e]iLpo(r"f?Ү>e#f~iKMӼh||J}Mo1MfS\ɔf!ۧ4AT+7mcG_ -EDV\4ͯwaTӆd2١zw '|!m% ?3  ЯlpoyTjI#-R^)ĨYv%˲4J)"˲[F!OؒdRKr }[F!G^)gМ_r6ͮjƝ?h 'bXQhiC<p!;fDt>I~Z>4j&f\=PJL&8f%3{j5nɂ J)?Cq>vM;p~0#MRR[n]f~زm!o!£>ʽDzG*V`#o2l%"!8.x/,!"3̜ :Jys90^>"Dٴo[i(- >Zݧg@?CC)Q4nkk ;x_Z?T^4Nwa<=f%d͏{fn3TiZZZ<14 OD^14"ru$kŭ6O)R~1_A[g }}}G3mnN7a-B]Rʧļ4lxȃg:}hhhRnWS1H#x>E4i?m6xt]!}xf+gSD{4/yOm7 Q'!f~":JhEfW3K>n{At 4ܜwhh(iRjN4G3z (4򎏏?1DpŔ3BX'YTqfzQ 4ט٢Z̛ ^4n4Rlx?(n[-DӰFFF&WZכЏ`ro\5ĉ*?br^zŋw(d0wY5\oA68-N-lhaYo9T^R^O.9ᴡFv[VZ z2---jtttN,ސhȭaw[RݩTjIhtVyo_PpOVy_oJpRf4ToAfU^Al#(Ɣ(gpI 6W\ѬʫLӼ? W~ns !x<*!.vc'?lY_Ͼ~}.Ǧi~ d A+/Py.rܖK)wĕ4ϖ*H&\ne!8Ѵ3d2jdddw[[--- !ޜdRW\yKGFFJ{:zhرcᎂ#oH)⎀bs:y" swW` ; \!eHRǏ/B,F'ZZZ;UoBBBBBBBBBBBBBBBBBBBBBBBBBLrDt 3_vc=]pRT4^`Rj3ELӼ^42MgJD`bf談B\?11q#tA,4,aM? 0K (&OiV8 |>pR^f̩P(CNFHuFT gH$$)B(U+LR=tUյ@CЛFy;;;pJD"G?pVM---IW9k5z7bYVi%B6R"ZbԚ1":'d9F}}}/zN(|OCEdk>ׅ% ̋&y1fBDt p%uwwrSDJQ": ` 3Rm7|vP%\b\6o|D3P)L&3T<ZӴ2m壡>Y rR[k)CL.32MBa@ .Je2Nzzzr2[_.<'"i;a/rs]4GMPJKP"0J:" :BtiéR" f~ãokKv!;"@G}E_a}hoqࣚ=d?%@˓^L#4E_Fq+W@D= 6foIfBD#K)7Ŗ_t] TsDb3W_Rx| qX]Vy[ZZ4@a:R: e0˔i8@Yّd~X3uv͹we$(owwYΠeaRd2&"AJz{{#(Fe=o{|sׯp],Yl'M/ !N(Q,'sg9SH$/ v1Ͽ&''\R fj&h388x*'QL3n"s !&9ZLDKyh# 3B(&zcJEBU̼V[w)u)XI4(>w.H)v۷g_$(~\BOʕѧRW#'3pe5fO9nor(K)UUvwԏY&E0sW7ۜʙ˲>E`-DrpIBdrr졡n:-H8hcʧD"eP e}ͩ=' ø5 9-b{[g6>oY^"LnbkT^0~7;T)`r|M5hIFD"H\N2XROPtY60Fr{'}qkl}c^GDv*gylS^!2jیkkm>̯s*VJ}.O4M5~yT=UD" !eB2v"*k̿(w^i$wsSggH$B `3/aB08+( / ڪvdr(Xhfjv sO v\IS5khS6jv9m=b+ŊDfinD"~Χu}n\ RTtbbr Ue7onqڷϥOx D`:GyO[7NWj!UNfwqU̾111 T~> r(;zΎ9X ?03PJAJ '8 =t&:5>Gye@QbMn89qi_dR.pCVRjFv |Y)]fb&nടN<3Z$rnzdRJg62/; 2NA)wr评Gwͬpf XZ_&_"©=2@>?ufXT*PiNYEUa0=GJ6B*/.خ6PHd}f2mil9"H9y+- v=":`L)5ml"dMDt*[U.`zaO0+LLsV%Tae:c0>㺮oB\W@IZQJ6~Eya{79nIDPGZbUcm7'q4[ H*$3GrW8]nEN=m"+78ܗ1MK(cI_۷gOgf>FD%p٩v`E*p ms+֙Bv2yC>'O,C;MB4u3sT_h'~P-re57u޽;{HJ QӴ3_ `^Aܻm'N=pU|7dɒ޵^}p9)o9?Ee/ylknFHr5c= ADw{.Hc-.?e8? Dt'3 J)czpLӴeBa%$≉a{[rӡXF/I)&+ ' %B:f8Ixp8q\MӜRQjI܎]H.f>϶ Yc{2S |6p%3_ 13c(ڽpBQJ噙h$%\9vhP(9n[P^GjigDNqQ5| L[O?gZx33 W":gnv{f3_Zả&ԩ ?*e(̮ Klg=Tn"|Oj;t]+߶,}bl?^pJXU&}%p[oԿ+(>Ç/ɾ"*kHGs|I)!#X\`fO}*WJ}tz{7~~R\!'vاvKkro99,:x?|0}eU>EZU6*0nݺEXD̽(|I(*.f;*eqCWWH$r0wI_5MZe!pBJ)GfW;hJ^"eq@e]2 tc<E"3oPJ%l%fcT̝u7٬WHd"b(\9cDtDg|2WL&̼{i3/B+&Q b~DtT)eify/Ԑ>^}IENDB`python-attrs-attrs-bd2446d/docs/_static/attrs_logo.svg000066400000000000000000000201711476453530700232270ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/attrs_logo_white.png000066400000000000000000000127111476453530700244150ustar00rootroot00000000000000PNG  IHDR~; sBIT|d pHYs+tEXtSoftwarewww.inkscape.org<FIDATxy_Uu?C s iM ʤ"ʼnyVPUWj]검ֺ.qhm*E@b @IB&B|Ǿ~9~uϽ{}>Dұ[ǁB`mkTH@IIvqI-g$1/ia?d-"iU啤M^7c 2ˁ#Á~)nC^$-ikJm &AkS%Ml"}%(HJzi[%=)I7}>DKV୒[*Ц  Hl.9 qpb"R&HmɵnJڥ{/By@$]!uI#e51ح2KŲOq=PY`lb< nR5o|aŅ^W\5oH Nȃ 3{a9 kz9(JTzyw#M PmI?ff-p  Ia_rEIc% W2 0E|7"R.Ρ\ *H#.^u0&wuu{DVa3zMH 888w$ \Qyr^jI9p;6JTXSS:<.} mf+nTpkϒ:4Pի QyH$]}n)(SnC|mLH`n}OafrqSAntps'^qn)(Sd4 Y2-0[k$Y3mo%=i>#\ ٪b|=$RIwwPS*#Jzy箫B횳}R;yv7IzZUM#&w(S`>PzKi IjI'I4G+k%)J m)eUD+$#i툗 .$D//oIK*?/4"mfAkUan!cUMҧ%}A>NAMH f/s5<;T$cMʐAo^-jq^I5-Glfv=fWzeqxyE@OkFLrqCcHT]V[ߓt0ߴ,mXjfl(i/`] I-l&L97YfVvHHjxP]w}'JzcO9R WޕubFKS5b89w <ϕߺ wo9L|_vs2s y3DyWᛩ{ךٻڜmNC)sF;[֥?7 }r/MQYfi!z"Igg4R慣jZ^'k^Iߴ+iuFռr?4-GE^ٴB/.<΃pa0X LCVZYeD@W% $IهHʙM&kUwFdpܹ k2R3)*Զ9ޤyY*)o,hGqDb7:Zz#ha83U1Gs\V7-%kʻw/,\ֽ-VP^%SkfkcAxԙvkʛR!lΒ,d}Oo`=#(ז|j; J'ה<.nKctR yEzIrb,{u[gK($uk^3po Y\ Pv+/Vod4!QIz,$/#i[λ^X3II#E]$$}&ɒn/l1.z=(途w[ ȃva`$>* oAe*o/lٙeݙ硲"K9 p/$@ΰEw|&`=۬TH$D"H$D"H$D"6ILf'\4cf7%Od!w|K$hq)c<2MfIՎ| ȓ4NuϔSnQ$ok_7%N%\I4!S_o{bFkb{NmvJMyC,pG]K̶%O3)B3+Av&қh Q^bأX+D(/tҌX Po'G&h3[Q`NRIG I-n7:2r% "Isy<,yIdK eH#U$yJzQI_tge"P$It-%JI"){^V(D; rIͲHìm"Uit%*(;utiF&itJYYoxl&s9IA\R~J./}C Ǎ:y"Ef{u]]Wٯ;8]Y3SJڅ 06oMavP^I{l@єVsTI> ˞gfW{ouZ&gIڂ;Z x{ ؂!1w@@;e +/Ku M,@^S#MĽ8 ~6|{pyo6Ce{%)xp43҆Kވ7[:AB7{@qrЙsmntAxsA=vx^3kw[3?`fΓqN+JҞ|eJMӗӚ2Sʼe> \Nc|%GԾuYހ3{>G00$*3[YQ~ݎtŸd *ob|{73pEYhnINC"k}XlK~V*?32ү/0I/ H[WG*oꎧ[%jB|`gx$}MNNL~"VڌI  !prO`k' y7KQPU58Mk2V|(>p2p>.qSnm9=|ty~5C#ʛIlH*ZQ$ϸ{t]Na&qZ`fyPw bT=ݶZ]y[@ft*pѳM9SdX3kt=̞IZP{Z HO]Y$D"!9&ݖ!MNG_vT^H*afF$m:GYmNģTxk󒤫Ocf)defv g5;!FqBe)^w7 _% MW|v/&HRq$L]Y{%u1Ur0r\WL6CxDNUg3+Vz,r^,% \BJ0n4A8p84PނO=&ot\i9ڌǾ}_ 2;9C<MZhPI_'+`NZ2pxӒ },BۆtjQ8|kz?>;g3^K,gtVNId= python-attrs-attrs-bd2446d/docs/_static/custom.css000066400000000000000000000004251476453530700223550ustar00rootroot00000000000000@import url('https://rsms.me/inter/inter.css'); @import url('https://assets.hynek.me/css/bm.css'); :root { font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */ } @supports (font-variation-settings: normal) { :root { font-family: InterVariable, sans-serif; } } python-attrs-attrs-bd2446d/docs/_static/docset-icon.png000066400000000000000000000034511476453530700232500ustar00rootroot00000000000000PNG  IHDR7iTXtXML:com.adobe.xmp n5iCCPGreyscale D50c``I-f1``+)rrR` Š\\À|/­+`II-N[,Hb  rAn)Mʓ X!!!!V`pa0e>>?|d Y]ĐɐΐPC)49i`SVEp_12U pHYs+4IDAT(MKT})$!K2.&h'hg+Dܴr(nZ"5|cdLdmiƳ9<K*DɇltĠ´F{Qexer~Ĕy(. 1it;Dwi%+&uxS36 86`\7,bX؄nM =u~ [GKz膯J*:%zTV]sը[ٚBӆoV%J6T/y6|Y4p7IENDB`python-attrs-attrs-bd2446d/docs/_static/docset-icon@2x.png000066400000000000000000000043411476453530700236210ustar00rootroot00000000000000PNG  IHDR siTXtXML:com.adobe.xmp )mEiCCPGreyscale D50c``I-f1``+)rrR` Š\\À|/­+`II-N[,Hb  rAn)Mʓ X!!!!V`pa0e>>?|d Y]ĐɐΐPC)49i`SVEp_12U pHYs+IDATH_hey?lSS=kMlZ^dN XIօE6 "X$EW(tBAVD$ .iۚ.sТs]{x7<߇ӷ% ˸{>)O@Ms1#qhZ m6I?mNQLHIUmhrvH;>Y i1- D6&5S9XyŨFw[ѱZqe@UzTK4eH4|ˢDy`! f^6}\O]v"YQjmz;T1L^Ա-Ax hY@X,! lԕZuuj6B!CBJd[ jP+HF6˚ 4 ]#`~}]U{F?GXuDv~Yrt?[M&Zogڠ3'[d"BC< 0@RXUW&됰p.FIk1[I(E!nf { F=x-:56@A0`ra7|X`" ]Ů'7Kc'ub~VGLyȦ߈RS.!a0&- o0DZm;\W ͵#fA@K/#Ǵ>}ބ4ŏ^Q3\:ǝ&v<[3UI<怽fH}@[nH:gAn3\0;#p]MXNŵPG ֠INxe}ܹ6y}/r~^HָRMhb$1(j0A )nmEmKHsb2AhhY6@MzUu'RBdʅ΁%NiI Ԯ@*qX/P%k <!L!u '8wǧ4rK3%Uʚ'!Nk Y=IqIJWI{4ޘdUp sk%")L-~Jޒԥm?uApt4b%l04gp{ ]Mx'20zB{s;i.Vj;\$T*p@DnPOW%VR3ss@}B>]/&HQEc6 Ip 1D#Fé:Y 9 $?=cUyw  tΟ7hqыb1ʗ/ʹ aDE@8(~Pd62@bxD3a%??mpz_lDc!h==7k?y" [{Lլd? ~`{A^] v |& <u)=$ Ծ?eY'\/ZGDhˬ HoniG,aV|!mSLKiӠȳѠEá+_YB{Zil~@wah?1]\ mHs?]썙= >_@T܍Q >> 00QDZU7PM.&_К;x:&3[оDҝ'x 4v'>YuƚsR>t el\ JJq? tԙ ^B$(&][ оgv]ZF|?Q1PУaGoB*R8aƴFrث@!??CZFotI=ahWdGnr[I\Otk?,..BmEYoM]ʊZݾ@e4bi8ɟY\XȍӬF\lD5LmEr[!|̜d'KVFG˟ *|z#xyD-mUUkXVq]yVJΧ̟,)@ HXe42 tOHx ??vqfќ8Lie5_$˵e3B]k_ΊC@5~"wQڲAe6Cekf*5^?B?)le3ߒ\ Ynlw>S;{q>y_`x$9/z{XG) fj?6 6˙jgS+=)Vf@`` X fgMB7dG:O5)P~Q߈FY7Ȧ9svBԯMCTfiNrpb!̦N0mortw9{}gƻl5/Fddš7 WI]E3]c[-~>ap?` Szza'wQ5]5p &v^0C$]̶n")($Uc *BżI_-}ǭxg`H`b;DjLLYALσxgǿO d M`J|f pTչXt ^H#_$(_,$l#^=<[ș6,!*O [CI&$x |pC^x'lDU`[%| ZMΦqyAEMF44]'ec Vvi+S<1?F"'xd;!'Sf0%P 9BXٸSPTQ'UV+ 9pQtEkY|IޱćXkHqHhVגN[cO33/ qZ<:&:Eh呶H1AkS %%Eb\:Nk`jbf)j-;U4n&Q: _S\/,Qmٙ&*슳)o:q m\v Cd(cCC;.@͂tG=F'EL92Mg:i$3{;-4}UFDij4 t6Gf#+F7 ȰIHlX\8oJ$"CKooFTgŪ\tA8I h0_ @s b?t;7oDXD=,C&{P_}JpI&A~0Z*9m}+-yd@%Co-Nl7zDAYJ(h&-""h@ФX>%b75 AH"M QGRn/V:,tG4]z\&T#PӊC2 +-,<מZf\_ ؽ˽+zHDw#ICѬMz7#@,>A HJ⏅"e6枋rItG=?1]Dž9@5>`Pj1ھ2[%@B~a ,&;2`\N~#&y!f^; sM_ 2~T|\#W8#WE1kVu2 xL BBY;Ύa /VֲR4d{r 9oy; @ exޮ f~g*{? |ʁ0=:ֳhmAqYmj|A18p`x= ٠.y\uD(>@t?7{|bh{b^q=4;]Bmv58WU[?{U`n+젮ę}/y'xF ӢGnJI?avXZ'OЅ/mYb,Aƭ[|?r|(pqحHB/?I@-bQrڴuN7ZR}9!nX_ 6LUtR夦B\]#??H#IȌBii:RcB掄&ɰ78P̠#B%VaX|'σ"a 1/vl[9`'jە@§A҇!_x:vCYڟvk@7r"%rȝH\Wx`&}-cvb.e?-lhmLwDҚD܄ۃM݀Kq3Uwr:e^3)=]Q*CwRtoI]w7dcI ? ٮA"'Ls'輰xbW:k:l.(;l&n:͟=T~ғ4:X`4Ok$[*w"l0js |n8幯e.vN1ۀ`DQ,ɞ]Xo>emz,}O툦AƃImOX!vn%6໫ -#-ia m!M"!2}!c>`Ge}vmw^_Da B:u26d;SΌc|_@j^/62"1Kxmאַ@`Bw>K(jMaM:'4*˨z+*(BLXnTaoYT@6/KX]<1߀p -X802e{u0ߐrcywqc 4и Q:z[ G0rk2~3)G/wGkdȈET-L yU>oFa3·|e0k'KߗZ2~57[Q ׈0k?QvAx[)tgha>h-Td p9SX% e f6W 8"+Fea~@O9AmC_KD-R< ;&gMq8{&=Van= 1˶U_TQy$o'xWPap{f۳ $u(\ (8 _IH.v \X.ʢK%Nt]G,:}@tiDd"^se`HȽZ`;G GRꐷCӻ:?:{wu Һ^ u. =7Ҍ \NfhѼpe!'*7y0O`Ś0c^e2 ~wo >m}!I}Udy|/6aN&7x x;F#;GKvz& EbpMchl<%ʶZ~ބdIM|E{Xi8d$^Wx5w*BK˰3QmJFl#0鍡)` D\qp܏GOf9| +BjLԮ>*Ҧ <[Z00cZ"f\!qZָxjBvaXӹS}yS1=#z.-ha:9=uPXojb0KuMPD5D6j\$>qhC9xPVC܊B3A__+si%}]:T$] QGqljNpA%jJkVc9׺H0sk}RZ Fv<APo볣 "ìժ8Cf 0P]6XUA%}!l‹`)k.0m씰 h i PN~ĹT^0<"TmύdE6iChalٝa!lV  ;%K-\ǷIO+pl+3Zsp+Oɶ-A"-f`AA+ >[RUZ碉m; m,nFlt< r:[܂_}C#i*+sXܥ_Hz-@yA )eʄ3BBWJ*/'u\]@"7(h~RcTe򃘇I}r77MS;wPAHdgEᚮA k)H}XՁNӃ;v}E!JzBq-q}\48R^yN̖VYتOU|UץF-k ,/w; @B-h UQExJ| VS?Ǟ 1Qn!zɁ\sB. !ܠ-.n [V8h Z3LT*H)-􅒬X4s#v vp_4XY+uT;xgJuˌj6x'^'"" GTg# M#a6Ҏe ֦"vE??rh3o0|4U|rR,C $+$P?bKB ;k ̉1o\JFI7%6N%->-qB.E 6Fś#Q; RC*<2,`yHF,R7\*$"  BFڀ 58k-;8xH q~w?F}T*R54tpFX<D?J1&BWdH wĥ*4͚1.u#f!_ʢ #pKgt=cr bhmdNj(~w7kJLMؙMz݈,FT",[Mň~04W \PEcaɊ ѓ#*W)o`m qU H 4$r&5ٶߑDQ T}?Y<\=%>~za 4G&hARӏRlAqK7 aoCN56^@ےQRKZ@/?d 9 x@h |:=$(ښR6+??q@q[a-CC*Єb 1 /.$Hm)%o2 ^nv9Hy5JEBz+4^.d1  ȳ>wD+@[OmEkd[ZrtaDI1E-u{K<6~MntMwx&5/&=ĠǵGB21N0f-DŽt;GwܱE3z*ߍ=/$""*+mctB!by&&-Z?Ϥ!ث K ՜<|OZ] :{+ks_~ \TTI510+j\ r]0Ccb||$Y>çWs%6RWu %Ӱ7Vb&OD!?!`!cZA+,7˚wxU}؟!,@Wτ7L0KWNKuqBbf| =N@m/SdɃ9YwYa.X,?r E:χǨj0a&[dB lgYM[nzѢF1E?GL~ vz$- LpQZ{ͤye[y&rh?*<4 }ka=L82>rlb8y+4Dyl{xq˝{gz&&;=/kz( Q{!?=A. ng)'62z=qb%|,WG7I % = w<' l1X[L*&6N$q 2 =rʽ\ O]Mo\"9 @]#~@a-"80%_nZ+XE>*7d d-Jw;K$Eg?#Jל=\dSRс?/qŲJ\>J:JCU}˔[C$$DbG>_/'0G4Z$fwgD  tz\ ?O'CA^aMℒ=-T]̟='-#ȍW1ʻ?E AgB{?7rSQBE&\H-f #4b1""=T#PAre\pWO ;hC#pL^Ibrtr(U+>4庮_39@"g\-{ST'&QdȻR'gn7G2O6Էhy瘶W z4 PV6$la+ED;(m/Zrn(nf{wQH!՞U@垸,5~C1!IgQo~֘ql 6[DzuˌBJOt 0 !5::zX Gp/82f._eҔ$t85c"Wm-Nf SiwnNwmfvG8{b,\jg߸h#P7AaO NF295`24ﶄO EF=Q9ɞ#Wn+~Ǎ/HȆ`Lc4,xb!L, 1\ 2g"a>;e ш>@Y)@S} &_0BX^NV7,FA$ER9Mdxȍ0B3eVV0#'y% C,b'oXAkqT.'vⒼ* _1dy(O0>k:9U PvhBjEqSջiZ ;vW Hg](bLi<#ʋn̕5>:"j*S>++N)70Gq2{RyȜRd3.2U{-`k.0WpSPjrsTɁ.rl`rIlMUʜRa6B+&g* i sv EfEu~`uyf"U3+[F,-nS,@;¿mC- 6~Zʣ>Q$pP1DKg5 1~%X[s[ζluY,ifAh%7PrB`c9.9. 7-OȶWƧ]h+9}$i+uxAg.UH|] WR!fown\׮=cc$*cjIA叺L.V}z`Q Z$k.e0y*bu^dc,9}*Ȫ!SuSx|Wd6ebl:`?)!7u;hmz|poo[hz_a/׷ iE< : M%|뉰*G(a B6_0Dcu b|[l}!U+sg*)x!Ni_ $7ѧ2th0rpsph~ "Fna"96֠"&72RH h^AGxY\8 G ?qӻq] Ժ>C*uC]Haʖ4 δBb.c-Đ76 93ayLO\,Xdꑾ)$g ]%4 "Ka[AV]n<d/F5O _#5@e| Tp*䊙ՋԦrö?WIx% 9$5 "|JD8c[2D[r- r(,Ɯ%આ`C@!X ׀/Y-4J$m 1ŖV_t́VPlA|+m=GZY0M k-N!-ɥjBU Qw?%7{t4/@QeH@md <0p?]]`iDQ_X,)4AͲ–44f,| <CeG@ 6lqۃV'!ig_p`KoSLZ_RxWr s o$ qDF pd"&/ȥC 1iqz_/<(u2@<4ԟKHs`Ǿ6BC4;S"DfElEQ ]Ж-pd@pPľ)8Fda $euH, 3qb?WZOឝS]hJ)>EdZ$̽ DU+T‰>EhB)yv/Dq6&o+/qӓ'0^}m)?ϥ!zӰJv{l;|hC;kQʄ;⛉Ly׮@.f]D1"931Q 9!uʀCTJӠ `k_KQQG$/Kq;&f Wa.yx^2fۖʸK1yy3F8u(eB0_Hu@87<],:?ƱŮQ s"5q]jc,Y ɖ6y'}ڟ|"D{S_ "A$[F8m\ y Bhc|ɍ4y h}"gs0O 7!  mOukZƻF d(^ϧj/CA Oһ +PF[=qjUql\!|v$-弐)E z3)Z9 ˔ę',"(si(KU]KT)q$dV|WY5b99<Ϝ>r&-͡/ RJOԇf.;(>"?R9%``RXl0EDx n#p+F \' .C3^{]7\ƈp a F&4Ȧ9̤OQ{*FWxD^A/(Zbo[>s&D{Y qyy؟;m(Dsk *zN9p' /t(C 䁹ʨ%ę`3m: {n'Bd{9 mu ș()/3`tT` $ao_`y!| ~}Gx}]3vݾ|޷k JNrzUlr"lI@Z#)^^Y*ɹ66>urчϺ_X/({GS50w"f[H@CN d3| ;Z o ؙ!f :[uO-j$T%R i 8ۂ,CJ'2[Rq[ c w|oL"<E:246ރ%fVKdL"PcNPg{);rrNB)i85c @5 kŵOY JI[Q&|7|yPLr d 'YSK(WiRYbfA Dbzo;N2`' 8N7iPi847xkUmnV[@ބs@5CBƷXZ,|3|B\u&0M\8WI]BI뜦ʚ3H՜29F 3~@)Cx!bXNƑ?z$ay%M0yB263f#Xi!F%staܓ9al} #yxT=]uгZ{hf;/ð-iDP2]#|lLUPXslm w79HnaNܼjp O>{3*ʼhv&Х=sF {@Q*}Fslq2=FpFݎKAR-S 8)y)BoK|wm4"FJ\V k#dq#ijNbgVKCRf,Fxp X^"Go\S5^⢍@qz8M]Cry5$%S~Β"@~mJlOTG`wj`֪fkާbv3B'ZEߴR0fD$a`qdg׵f6[ n\YŤԸT 8q[!EVh² +h+:NM#+qME3׃M֔nmHzQn{'&/Qk4\1dOXޤ磫:2-}ɽ-pte({V {oH|ɮUαa"*Kn_ܽCj^6\0Uxtf0W؃Aa9+o jD?svʂ!t$t\o5<=‚bhW @xoE1MlY4\ fLǤ.(Qo$MaK.~">@Bu? ƼGsxlpbZy揊&aؑ CqEZ`T]wun!~nYCcP% rUt'AVK(MrXfQ".a+@ 78??A Ē7$p uκ-y ie]{vEC;=!&7y4.9"*/6LM}7CZU|4Ɛ @y+VJQ ʝ*ʔ.^k YGUK W; N#GeUsҩ#~! k]ɨbr al{ #feW+d13mdYKfcՋVeVO5 % (DRLDQ*Vۑ = 'ûf?/} (RC,"'n.D玄8`$Z?vì>&%H%QB54!e)CgzAZ?O?aC%(<HӽG,;jվ%L,e&mpsi<w)A+@ qEK1`:߂)^TZ):ϷPH4z 1Ln'XS>iհϔvkL*R)]|х8yYZU"n0ʂZ^#kH́iSRbŴEӴ_,n`#l9YVU.c^V`?y6;W%ksP[D:*@MnU]mj7>Xx_غB8 7=y=DKR=Ԑ8YI؀KִJX[<_| ~p , zg$VK~T8qD n[|~p? l<0XTbHB: cG;4)e6 a|Eý@1==IHp+6Ȧ9x,Õ ,R0kQXw9W"l򄖷؅uAtnF֦ y%,%}aɠv؍[["cwd1r/syà2>82 @Ȱ 천3DRToh<4?'D(i3d45F/v xmNޅePyR X0$SDž<dHT&e;mHm/2` 3C@O 0 +j< Ffb )7Bfb7k88X)')j1Q_ 5(S4R :1ԭlMx I / F(pӕ͚֠R"ЅYXXD1#e/AkȮ狼:c\wbFI éO;c|/Svq:yI}MXr+@,z;V uC/g|vE#v?LdP3Mj#ܺ>w込EF>):7ya<}rdAϭur$s6Mdȫ16;>~IRL*"P.0ݽj)1-x"̈^?nċq-I w,xT|(l2ǀ2 1UtI7>wxBA{_]Vz62:@6;#pG Pg@B~%݊<X0zQ@1؎,=" B̽1<q(+qp^;/ƣG>h +1qNpOr,P#C${ s3S9wn( ]w23L$L1gX;k[٠\nP5tA .V Ӝ}cWy^|̤wAwُ['7.#Y#;9Lz{I-jzRBP =s\wettj/Ncu' n3FK!wix",6r[='/ STaB<[S49pjI*< fb^{z&/02l4"r~Z'$%}Aj@%^+5[Gu5dz)ʦ& U`] "=Jʻ_]MF<,Q7< #^^;)w2*υ:v%f98Jt̰ړR]nZ>`ĩy)Ayz˷]]s3xqۧЁoW:-"VɛǬzQ]I7`>qށX̽ŸT}+FD씗c˸ό E2єh'Oz^#oj.\MۅStt5J5ͺ]XD` 3} ; fPtoLi7y@ņLjO3#DfU9ۀH&/4uEؕ+fȸ6񓮛;,PT`T3/R(t;N=l4K#JPwY$)ǖ0Ί f 2$K,#~籸3C/^6: wtk1CrҝO@g1p.C=v(VrU@Op9z5zcMÓb՛DzBmg@}[LK5">%mG#Plcy!;".Ӏza7U[4bR]װy[ $w7 y?^YL8&Z 0؏\1&jV:G4q/Dm F c_j7C ׼ǘlrL&Y,MBVqoem1ey ?5$<̾ IS~eLu[6gx/ž9VL~,$,A 7En]2gUޙf+6L1 - q[4EK[.]] ?ytݒЋif2 p `uXqRl*-]hn\ٰr1BG-/p y)lх4{:. Qq6ܶG,4p }Z@K8[t)YW]'yI[F6d%7Ɣ , '# f[ [T¢RͼFeUq{]0ѺdpF\!`3/<}-6"5zzu3//c&,@HذnV-ՐCY]W#G;VGowɁ:a%d(|!-y/qW^?S}4]4Fg”i$S6fF0X_ts2$ % *g*BS|EɎ&LKdH #~ED>{Z@`1`XR<}zR=-=OqC$oL=<0vTAMQ,.D!~A q+ d $0ꁚi1A *[@o+M%hLRLJ1B„ Η$/W]¸m92%}ϊcN*0GPr][t.یxq;icaYX/P[H@OQ\21O_wEs<-=ˇZ wtQm6511-)%! gyf27{l-K^.5Wz+ZcV*=ԟa"SfS'J?PIz(7RPTP ,d@o?Rog#Әy'5YH1`B!ȃQ%K4e0tBW4 s t%BPs yBv](:1nad}qBEˇ- J+@bJO(P(:)lDoY6#\F AH '$;|蠑65i͈X(辠tkBlEWgSQPOPQTWZ_ekrǬc͘览8yqH $9_^c^yN^R< m:q>rNc~`P!38z8o{Ff7/9BFJMQSVmRn\3q=8o ۘ50j\ҰU9b #bE†5疥Vd#╡` T=J~R;L᥯7ɋ> xPX(|BD6qR}z<};:987%m k*IhX!F z|H%lhAI!p\ b᳂ g& @ƒ XxlXPAR0P.C Q}YoFA34g1VNF/b: S兗*]t1l V^X\peʴJ)S4bS,JJN4%RJڒ|؏PQ'raȏ l@?>{dt\`y3 e\Ƙȋ+.x>  dv*)✆42`b,GT QJMIE~!Ꮔ1,VQ< ;ԡa]=((Ɵ?(}]z;z͞ |L'wwdՐJt9E9x#ÛKb۩Nbֿ=%K)T/ˮr⸼1qѶqOmE;23*A`,L_^]gSV+1UI^L-JPgOMN7M()A"鎤>3*:Ata %A.L |zJؙ3'\"F^ T_vSŃV^ڢoL|||>X[iZ8+o}/~}ol?y`?͖WopVӇ68<^<`poyl1xlrg>"Xaj$ @>L@M bOSB& COOt|?7yveEDR?pϧ*67ʯ?7>܇~KA?<X` ñ-*'-~j?ݯ~<ꜛYyH] Fe?:z ?#!'4q4 OK+!@&ƶ}OqÇη/>oG?B|>տ[v ?uέMAg_F6&7?edcm@_F6F+mm_V5{*uC?*ܛ*ΏbV|O{ >4IuQP}TzbӖ]RM[?-j4/f'g|t~TGQ8mx?+ _A];+*+A4IuUr. ?EeLלnQ9^V;~@i_8TxrY6mك?'#SQѓ (EA;DNO@;D?E@'#訞rHUnI?jzBeܷtqč_ÓZ%Ǵ*DŽ zOV ԷimAlX:wk|yYqܠoQԪEo>?Y~`AYn@Yn?*nR?JK@_K@]96wR YX'8`؉8 yAbZcb=> ɽ3OBe?Q́r~QT;:HyA!Iܙ춏!~ żBUv:1 ~pVUxSE4DI k ex$gYMH~A  A,"1h}9%aU;p|cod6@qjV3xym#@D8fP%XQy Ē +ހ-x˨!>T[NPZI]ܧv&j͂5ndц6NYu֯?[Gdqd Z2sIEX1*l]!B0;``L21K|y ~& :J L_Tz//.=QgGݨ²a`'?de'N"1X9 29=<>!$M^C\kȕqov;hqr?<&q.^RFp @+8l1bdf!wy nCsjt:ƜTQ7dœ4CNOƼoT V܈Ə<pةb|l[NOx]iLdenܨN./Cwl8hMڰ}x+]}Si1K \V b.u^V;4./E_Yob;#z1+?6r:¬xg W>8 V؄T)]b)8iB#rc4+z 27YE$anDĀ;lLSa(fBquܘQ7U~czu|[Іb ky%^)-3#zh!MCV31 d."WrhlG]+E\#cxtyza ;r(GL⨭ܗ j$t)4]CRP\;F{󋟓rx26Ìk?C V@:eLqح= ƘI v*s߫Y[CpܴE@X a!5!#;@s)k<~N gxpэQܺ`0uhZ=OdgB ܪF(2n~"zSj34uhoM Li0? \ uB-O1RB )=hZ88Udt_"m1ocPTQ4X|9!',W6Tx@vm^I.v7ƻք-#3Ľ%Or ,om1yW՗xd ʘZҽE ގn-Ӧ.oxզ`SӃõ8jBZʆ†޸ Ak 3 Z掽mG͈ٕ&)aq0; &moy$8~0#AS#/kF Bʧ. 6Ζ%l#'8)%ǩ#eG1Fn)a}Lf͈;rk-';@.ū =]H%`!|pGct!6 A^5HGz+C˲:Օinnd)]]BBњ|]Gm~'zȕ.hnHrϾ:RB=8C`Qu9w('́3qt3#:VJ qx(b)=O08DZׯ;F >K2@ {V%h܊BiyCQگ *L2!0(ebe/F"8DH˅Fmb؃W${.v D]VNEZCr)J@gS Qo5!z۠$Ki8r-{QXN1#3c F;mV\*v/]P(o]em^g`I| "pȬq#:U=/uQzӣ{OԹ<})"/R.i-hɢvvi'0;= r2|>8% 6Y%-[]JZ\ug̸'-S`'r,}J`򻚤19q?F*̓l4U;h7_J< ބ5x `3$ 77vb3'xQ50k^nțy67~#o̠P ,a84 Qȝ {(G_3C3 |o X~;q:l#E.7UڶI<*Hk8בBe!k?Ck`K4}  e>gYek^w4(=lyS(8R1[!魧g>gݯ UEe ؤw"?qA3Ή tG\5WE"Ib *EhguvN2_RwQ It66]b=GM788ujȳYVAӉXk,盭n>mZ񪖷`\n[kVٰx@*RbByF]loyn 8ցo;Q{[q^H"ys9T @ŐqTzkM>{{*KYw:¹ \BG #9j1 K'tE'¶YKsߘ9/^$&yH=.#t;v^iJUV747iml\ ϋ RsJc)^@I t^>ƒ$a ["׸KB2 |P&3l, 3G9 i|O"X}-0V05d.֑ăD!̵/ӁA6 b즊ŘIE @ dJB#>QGyơcs t ;q%61%kcNe,EN'oXʎy̖wCjNq 1mzk [ɸ \NGO \DR^5B8ˁtiq4ո'|d %3P(d)cÕf*s'pC1hw&>@,9ǩS+)s#y9n]M^<&йZ,=a2nMyiL.<.)>z \CR>Eu/q{9m?ыc6MZ$lzrEVZ|<`79 G_G ^Dc˄|d>PaƀśEU8":ґON0v7CoQQ7[%o8 wݳeP OX eX֥O } Pf4}$߻@S\a ?Gm/PBya"3@Tߒo9F1,_?`qp#>fΰO]F}p3k1ī`>}@'LM'$ >ɏ#,AI:ñ8~0&x^ty"\cܳ1۶qLTDۀAS594h!B^@e&dށf:tb˕la-W~5X`fV[gRpӨ1nA~1;gr:j>HUtƟU / q.\Z+6Mz-sFa 0W@]FX@{FsSvժȽ"64uTA!Á Gtr~Pu ڨ[{X+|Ll@sB@ `RX:+h ARMUubKۋv . dk뒇aDޜv -uYH!?~&(W'Ґ;߫Y ō}~5Ui>ڭwmq2-,sQ 6ykci/G7Hh|8e\ya1S  ?eRowo%{I<n*UMrDrхlՠ܂cvg P2EÅ^`p7"sW=-&"nI6ybu 1oD9=zZJ-v)%\+j%ޒ#Έ< EAyIKXyt\ `Ä mbėR`0TQl4+ ô\ ~w@6,p< *_ PD/{+".E($V2߾3lVAKlbJ#p=uF'<:^Vo?CdE6s$,]ʗ*켸沉\O<1PrZu+kc3EC21 fFXsf0k85+?;n} @Vz=w"dVvF\02YN &Oc̀O +(!}45E·%kFD eTvQcD~)gkm38 =Hl:U)Mds |E^y]*?VZ9 isexjG,sZ3|E-Tc5^һ;~}(ʌU]U1C@bs,dgvgiEHHx+?|X_' pb!E f"!.dR8k ]vevk%:Ey΅~,`@ϲI}ό2p+oP̰ԇx#$ LPL+a%@ޗ&{}|S2}@k9' Ӥ>9@"*3{΃jsuB,{$Ѭu;q' xH/r!%9+yf1jip,ADt2M`pp_.PM04EC~7 r(NbO]f[Orv ZR8761ȦM18>Rg0MVQHH toi-Oܵ2 ƺʔގи(QH>}Ʃmc)☜W2NI>Y:+C*] Xg"ZcI4#yM(L% b m6`\焐p:0:.qS &B.U8 KS&rx' !fH;l#a^u]bz"wYx9r|n{תlA `>c{&I!-m'ӊ!(cR 5C^-S,kZ$3~1ph]R^;]OR50g{hcȣbyCzn ŠǐFOr.n/׊/Qb`#34'R]Fd 0E 6f֋Aw^$TDs7dȤ#=玞Day_Sc]9p2|t:r \)zTl =ĺ?0}h8 K@iܑDkr)ϴ|L6;;ў vO xP"_cx: $Y<U UB6eF`'u Jyh@.k;,dnf[A 6n*,®L "@]g0_y7= Unpn&Wn& xE/ 81g'H2+y{2 wm7нTh&{#mb "aj/Ui*$64 Z y6z + >(R\wt(%vt>$Qqxs˶=C߄ ss p|?XRg( ȃp*]CEIFĢvWNÖI78#A9I7Ljʚ; LgF u]'Z 0FH- a`f*1t 㑑.a(92qmCo&7EyC !,YT홑y&Rw~}-i!/zc|W"PI8B/#+3Af65z^!fsət{2o|䑅Oz0 {ieA34 {B{ۆ5kon/d lk8yi+}ܣ. +[4A!5Zxg~dl`nbݣ\x\̣t-\]2{!'৥Z8<2:! rȤ,wđyHrmwWa8k" @Y=y6dO, eɝYbY8dc%Fs Dum@1;xL8Ïk 1Nb_݈#Q_yjh'yBCmr& P`WzlqhCer_ Qp>A1['<:Ȃٝ@nQ]Ig"h_6qgY$_9Q53LM]$^24 HY1~QfQgCZ$=&=Kbk (IIH=o7MsΒ鴏)6#_r.[oFlA%\d])4_:u *ܫdNKyGF J7/x^'y Y # 7J D+$lZ(  Ӄ}e0l9OҨ}x @Gs|)&̐`dyL6221&NY3i*T$Mi`du}d+2ֈ{ H_jeet鬒@ف7ޑ<#|,<@!qch(2S4|@ê v#L3T"sKn=! w=3)lqt]|=C,dMa" :|OK7'd kZ4Z#SdcAWq*h1&[g؆6."ca$VN#q,\̭)U˭c俶v YCCg9EMlc'|Y~ XK_s!p= t_sin/J/΂-\|aCG^Y0+IA&^W{8.ܖt:7ѼB @<[+FʌXn]b|AOxWQxޖͺ# 5\q4B-bIƓ-#vٺyIT'mO|\%[y2-KХw% ($//j< 1<;RFxFHl:] pdLQip6/OSIRxE&0DwgXIޗxt93˅BXj/XKe7L \{#a e3{]HnЄI&!G0?/{jIH_Ys!ZxwΗQް!|8 }Uc D0'\FU.0v2DPp_%4x ǝ | %8』 EIa7W p "Á ``0_g߃bu80 ?`DZ-p0#( R (fe Y2ƛAFIM1ٿ^)YJ){v="Mcs#1S8:@;T+9bפ..J:^B\N#uA8`jrq܉B8XO75otl >Ϗ}àȭ1ېx`\âZ0h 4fw4֭ϓ##z͆ȍ<(C!D  \?%2 tzPqaK{Vھ::E/Z*CJg k5k_+xum#qq1g7܉bhj[_ju$0S#2XF6(b|J(/nY[bѢٌv ZAjEIu?V $.5,MfَâgwRį !\TƄ*Bq)~4uk#cuAY) cСwxtMX8m7 [a6lD5 _y3e3MZ oi ̓몲;y.xеe'5d.&Jh8Mm:,H k2OAOaf% Px (h֮^(k9HQpQCo;>d]W5T䜎 A}xo]@6)gI_! )t&xڼjt>&4Ly㬼CoE;$ֻKraIm^@x4~jqqf\*-rqr}- c4H D+iEBVM?vMR͖&WSVR  Iȝv&f8 x2Pcf[1`S O,mfOh-H̕ KN7V&e#!aHA Ԃ5 ]_Oа5̐}#ANn@k޾@ٽ6)][Y4+ -;dy֨@AJ^U!Q9QXop)qnjQPlO>7{P(1ݖXxeN;:%Rb|E7fBۉ;a3JgPݤ@kLApq'j8W;CX*x+v`28&4Bs$&& ;Q#ڲOp⯰P$)+6ǑU``9ڱC9괇t[a,ha#[pQ8yz`$ pɥYT@ 43 Lh3%4Y%֘t`٦+RhTGD 0Ю=ZAl+ Q<9Lxܦ8 MjR EWŁ[3VZRC1<kb|9 fR kM[Ͽ20~BEv9i1J60`n. B툫n%m>r_)ln <|xwo Mi!gvf!mnIe6jm ZsN sM6׸9F/ܨqdW?ָ޺ghiel12g6gܛ5#vGyhuw!x7Z`Q9R}`9a 5Rs:ua@v" tMx3g`uùM ]JðvGs+A;n(s: F+12Y|]&W&,Ź|)눓0Q;AׂVM|#|||ſLN6>|9 *cFe!cl|InF.?c4-_PAI&W1z2G9_X]홱]Ē^M݂z)s7(*Q&web/ՄA+#!Xr9jW?PŅldԼբ~.vEq]oHU9#  ,n,CDVJ4_RR\)54`!V%|!+ Y0^rtAM\HKPeM Ǧ-C@s C!M&c1!MT~әR4ZRGˠLcbd/=~ R!0f"F[]4 yM;.KQΊn3s~ǎvQ\'h'ZUZ3TՃ !׹4]&*⫍VpU ]N!I^? >P1k;=<: vRnC*:/M訴{~sVI\h L-5? wo<hH8Eoh8+(ֶq$SJi ntҵ%oMZki'y,Q *%14@ xnY]K fYekLTiJ`^\-${/3N ȶMʵɑsp=S{֝{|B\ukxQFsДt iQP:d֭mױD׍xķN/'*G|PrwVxɌok.CE["Lb터B8IpR9 悁b1[,aj#nR /QSiqVWRt /L@g#O6`;ܻ:)9.K!7GD~+s\hCh8̍8ًpA|p2eo9|1>o72deIX$H :,X&:VIq6JFlBm m#}oY+U!@zSULdU(;U>?=uV+~hu:NQ "VMi6-055ȬOM5.Bx\Ōp]O ba;7*4qAx ]@lSnlzJblU'xAvmK`+BPJB~}~))[Pz{U#:e!dZA\]vYicxtWS"٭Ujl|Cй'zFDyyuBV@ZOqvbMmw"jHP3}@ ײ=; gDΖ%\!YQYٗ1[8??1Z{7 A-n[1N7m%<_:(Vw2 _]T.{ArA8 6tLCm "))JU-43JB憬bx%[NVD {%V=}S5)TVg4-# }j Mh!x76fchڱn.Yr=E 8 Vɺ)n~7TZ}>[=&Ҳ%@'Dݔ2aQD݁llc>RM7h!\a~<;/MW-?.W^[ +ABQn_U{w#mj@R9W O;;v8A(;omona@*Qyi N Cc|NzŠ.u>3 B_9x.kK֤K~_.I`A'&=VW@e!' { kC.z9 ?~ (pDjev;w~b\>@ί"5cu!uBk,]A{ ̱tzaҟNgIJƍMʭ 4$[tXm/MZ LJW'A$3 AG< H0M,f5(bS["V50;=0$vuAsry߻v`hbΊ̠ܯ$1H:fWlbcDG`RS \pk;)qwGt)_4©qYQA"eu)t՝yFRB}~Dž`?RFy=Yk3yz e A\p,B"UWNGb, b1#8;)%q .8#tr~hl3d<1Aaژ'`͜0[No>5Oؗa;Wխ^e7F-_*?Gıƫ'?MXW9ɳ zuPI\hcVZprf@J>? &cf>Ě,i.A:czsX']N>?ǹH WK,: (,zY/hi"hdq&<@&X.j, X뽲 ߧU1u{ վ@WL M p5mZ=4fo}'VS@\AIv餸x/ a¹>ߠ!ˣs`6b9o '8@pYM1MCƭ̭0:BDD=)K ԜZAo<~rYT]3swnBOH]#s{y@?e뾂ؖݸ6g1Br_z %F||颟-X^g @MߗzwͩJB36)HD?DŽ;S^&.D SWl @Z_V`1|8K8F~>$p{~Bwsf;L*Ķ ߀,OA $$N?s`PZlXc<V8( E="!0=@&[U"KBS Q> A1Id"쨔Uҡ0;I3U_ EOZ U{gs"F\736>t5 1_ u \ˎ4 %5a4lK5OgD,su?ducV"йV+Ne=Ԡޥ:zWϦ*i+fY LH<伹@8EWC V ^ἪDneU~hUg&cؠj.׻׃M5.ZǞZot?AOhx0vFy:GeվM9ږ:R%!WKeS?$^|eT~ko -O5ľR _k?d`L[ P; 12+wFtQY9;APwsk zX5JP,c25QsXBwҝaNDkX}\Xq~FABFHמ1I@ 64^>_f#an.HœS ڤݦ.ƅ*];1 3F)5_;gvcC{ۋ }.L`a%UK,^V-HDC{~7#CrJtͷ9*6f[ZԬAYX_ڬ gL18~mΡ ;J^H$TEN C1Wqɐ `&ggӬLU.ݥmJŘDk\E!>pk UTs_aGHt²Nȃ)N knNy`;kRU(u&,Ol5h&')G3 sJ\[㜂':6kN0ax|kruEfz+{A+y,h3OzW{%IZ1FA˱wÏyis6xm?AiIZ`QO yz xzezHJsbaE_ Xy{c…tʄ|2^^S_o*>=p ~;u6źϕ#?%Vc[/&b[PnQY1*?S|~T{]5殸T'vcSnѻzԳLJ Q~о1J\1`ii. M)yA5 ]@ ^>|u$X\ޅd6n8+>}Ќ5A)vq 6.(܂E!a2I316 w 6=k*އ)5;/\ߘ|sk ?V I6; $[9; ?_`@ J+ Qƶ QPpWJAQCA`lZrG:ofon,M.m̌HPyfs ??qy(x?덜w;A8o椻`n-ڵ #H\ YOxEY(禂=Zz=ͣ&wH*ؘpL!ȃDJU87 ߚ$صwgS:!UcAoR4Wg9V?? :gș<t'8 q;eeɶf?"kR_߭$.BL|wxKfJ ;,APoW"Vkr3G&1y`e0[6zdf4Iy.ze.^o:~(te MlsFDM;&o?ydb]~eiL鬠 mA1WHo=ˤCp?#2nC,w8s9Ձhɮ7]xLWp?Ylho,ڨtRaІ@]-Zz+KFer®ϱ pTE?_.M@M;,g%9I6sӳUYj΍Gt#x 8r[Z|&w:u~EWKhdX\V0y귺ë hh`w FB8%\B?_tIJqZXmX8uy`l/lN, gykUނ>#edv>Z;0Y&L48kEVȎͮ.ڇW  k}0x4|=D7l ;! }`IG]PYuF8cAT3|P9@*❟n8lH9'uRz|U )>m!6a+Fftт_tly(6@JӼʃJ8k=adeaVMvˑ^՟j2!ϼb4O&|C[ByI,gs\s x~@!Q{!6bC܀?֙#IDMg&xjt:t.Q:|@`^"0m&1}3r`)C-4s"Y=| ^»}mn^\OaLhL;Mb!|0aa<2 7Q nயQOoۻ$42鞓v5 OB= Wd^)Z&/./1$"W elCֿ]!vHU6AYͮ[ɍu vzlZp' x[2]9`Iaxվ,|o:q\Bd~᧔P~=Ml|􇯣 E5=݄!bypk!H\LJ\Y쀖o  P2@74Fa͊tK"v]A$CQ7AJAՕ(GIÖ*4R5"#-* e{XK'U !>D,J 1G;? isrFVi)l Cr6M1D@@kB˂堥, T/--#ź -np0Mҁoy&DduS`ْB:1;>,*pxOnN- GÓpՌj ;Oٚx8x)qQ4LgI]W$3K6F^HLZ/e$,,S.6 ÉSm#d,!q~d'Af(hVR8d|q!kd\nŜ^61-+sTW-3DL^g3x*=fwË>qA} gށ0+q-zuV#?Elrg+B'A@hpجB]DK&/4jW ^݋{k &";bS[FۥgK n XO4iaL3|',ZZHVa{r`jiՑBtJ69Uըo LFt1jBMo\/cKhSeS&sYNWXu;,aDtITkKtv\BC ^PRyx}|z}EBۻܗ ,;B6X]+Բ_ RU>|!v`m\VjsHa: "a9v!Jʇ ~D ,c{#:&Jڶh&Ap\7倉Y*Pˮ:F(/YvkCh:Mju#{mZ74o,cBUq˯Gqu9bpLi@mE%=sݐJi7hz*Al GXWW~O"W+l2÷ې#Vt[$Po1}MqQx|މUx8!yAf vtlpR.GiWM̷Bm/lzg{H@B }sRʶr[`Ƶpaʭ-N'gΌa$ݟ풬,jh͚#TzxgFJ4 >9E7T5}=#b`۷#vE$zE3]{qjn4q v̉xˊ`bciiq|[hQ%xE dIByLѸX͡:Hi[B'?Ӥ^?\ŎwEDJ}twhq(pPitTtiNdh4_xx[o; cɚ&3TGqpFRl5Ww 0;9kH||e3n֐0awG{qV)144ԙZuXl.K.%?PfT1;,G@\)kڜ̀ /@FU%T i5s4$ɹ)׈ۘ/oZo3xVcؐ홮WY|FVb*2!ٷ4d8F0Y#'4)PIl 2xnX](eKH+X)Prka9_7h 5 wz:\ 56 V|ĪIB2^*%+P,K#^93v"$g}R8kʋ@:HE1`wԷ|/!$~(.)v+O$Sdkx!I,-Yp:u'8uxOCJ^39 PH]%7 o,O5ȮY`@Y*Csm( .m=_D-ڔ:5E唎y DYN`xd$wuQ}EDZXh6;fꋹl޸Dy6 VIM3Eܭ2ۉDszvʹv \WX38;R#,Q8)q}t2lsp8hZ6X &2(sF-A!=hBЉ5J,UxΈh4Avc;uONbi a7"hЇIB09D%30TL'!8Bۃv3UaU\px 2j>*ABo0v N7 =jp9qm骫K"WkY6x벝/Rx$ Q %|[K xTf]>7)̾/AXOoRC$օKqܱp*íV؄0Y,&;1 5YO>O_%/Vʄw5H}s9әgI%l7kȲ8xXoƔCT1 <S[#Ѽ권tgY\d#0${F(O2}l+9'e&0q&4;;뺌UFj|/j!z8Whwpգ MOw!3G;DA޹:,47ik<|WTξjw2kg`P{|Bt7Pf\' ,gV0 嶖qvڔp |}T<,&w=P8RƑ-u\fmq95V"BM$?=p7 9_Q=blU\ `F^ZkT,.o|}@VߥCvFxhk$b]3/\;!,$|B}7[D wHk#kƭjq✑9LqD.)QH/!ˆ{ v1 oTTڕvS nCzk";oĭ8X!LI>q0ϔ W Z9o )p,テqЍ)8 +^/) ]9Enȴ5tjU&˸ :Iw_Aht=Q]1xwV:+VGHZIUt*z%C٭AbSDM< ;&Di~[, @Qbj!(&벻x췺 |o+M2Z]ZoWVd)XrYUALIb.eIk'2#j˷Jm1xZ>jMb``/f5w:]֊Bh*^y`iTֲuTo_X'R׫w5wM'MaH E[Ne(/Qe_pN8U"R9é K A%릣 +5$Z i‹^nɚr#{,[W`vẬmض? !ܦc4eCxK^+' ݺ9$fS*lYXJUejY)A+/ O9}?l`zg\h]/ |l&1hbzҒ,#kh*7;!p-s4@q Tݒ`fA+. e$XNRlּ삱Agmw#ۙq߸9 lX wĄ]VgK%9SJ.GR3CsFtƚplJ-vCE"JXVs#l(39 .}E{[ qwNʅ!9xHpSǵBeKqү[x=l6lGBDwɂCSEu@7' AJbd j nNF't^j~?GH^F\D,W)4U#`uSnJvJJO@tqAw_t9"ĵ뢧kzck4/3+j]& ƭEVDzK M+ G@Cz].:-"8(1lo,7-LI ̃aR '6Z y\j]lֽYq4I+h;7wq!tm4CyN[ڃb2@wu]2&*VMt^ZM7l^: 석L*&K'7vE xlU+cRáK9WwE%~P붒쁑,f/tk!qt:mjd84,L27$gV1zo*t  )G:qCfUIu6SEj; xWDM8j sCW8mrښa>vP1]W^UDGzrB͂rxcZ=ɶhsK1x,#<Ot5}͹:&d ֟xvj^vW4n-\k} &EQ0ȪuͶu7v1@!$RPeU x8_p:?7)P,w܎u @|;l H\{^S8 (hvvoT!Y1"asf%g-G0Avkv#dJ ᕰD{0-`aڠI*dLS&MųA&$gd #;:BLz=, b ŅҩB*o̱r#CCmF,nõ %={0:aQgKթ`/re+4DQqN[1i9%2.냣xt鑕*w0̤Y̬]})Ap3kШ=9 ;҉:fȘh+{ su !p67y)^-0騝=փw#y Fj8b"ϛ8)/R1)3=)[\rXƃ0sk1332즟E)̔ xGo0c'we(b\DQXNiC\:!B⚘YnHPr[q}~E;:`>)Dscyu#L.?E]S {R&_$o v{ W.8dc;"e!7XT&q^Zъ;!uM& ~ >u]ڕU:H`bF>iʓ~>왋 5 h*[ա .c9mG*jWQ`g3&Iƍo)O 10JϙùY 1rfZ+ؙapq MEq#D'U^?aưQ (l#BI_\|DrF<ˊi(!e+i~glyM_}'j|9wX5! DBj /禲'NȨq5LDFFD 0,^ oM`J{}quIewn-mf`(LNf5o]4q'7O@HR;bǦְ-}\T<2VyM×7D0͸Y oJ$x)1R ][Q9Y&^7ɮ{@:I8^wPaBUR·<2$*l=BOlz6-}+>JmwџlhEߡXC[aj O?qt *SD{dn*+wh)("P5-ǻ?nH KEaF+=O(QJ}GԓG;ȵ5JN7}%#Nb{B[P|1Z<Ĩ@Fܟݓ#rfۀw%*Lf$ס,M~)R>倡Rk}ûV~[z`lx5zCiz^+lr"X-GLDVK[2/B6{́bgM`gh"6.˩_NlQ0%2[0# f/7-mb, ZJ5Bo ☮h&?oM] %@_4Cֶc֠&tF6 Z>p* j4K扅Z4[m[ f`=2"]fӔMdpT!!n2(ʀn lޕ80o7}oAQ w-CD¼NVF8l8U%CU #;KX !CM.) {M9У:+Tc,+сT%dnr#cFg)q73uZڐJWpӦ)4xV7.PDu yщj,o[3/+ " 5֝ rsXk:Zm8wPK6YǒUdBTby8Th!J.PAʵKN1X["1AIHuqf`hE2m7j2.tJTE];><. Do%lk˼8תG5zUPuNJX57Z% dょmHCcWϱlAFG2 Qg*^M3[ ;,*2 n"w 뢎_Zȭe* 2bwRc̘p%sR|3@̼6chqu/O8tdi#λ/Vsxl ZF^6 Yנ%p23E(C% ) z҅Ӄ.{Þcik'2Y}QI ;/)Wo:4wL0jkō Oᆡa(Be̛RtM E˽QCj!Q6>iL7tFg:ۨ&X W$3Ue6݁zX;`gfz0HPDoS |S^>&NwfX$I(j;&7g]F.go,- N "6L2Y ۆ<A̽ &IC}l-,V,?}CH3!|B<>c1ςTuQE+EZDpԊ5ąMPbpRG-fǯAM{zfzv&P=׿-YLd Uĸ8Cõ-aCp:MAM9]e:`7(,)`- _$kZV_k.^7V$f-ZB1mV~Kg \5Nr$ǖ%!ZRӪRU~¦lUod"[vKٿN:^CKj1TY@F)Qdí`sύ(;c5}AM";| l5 CgZ9@]gˈf9xDAtI< t{r85d%ːfѱM:&AƂ(+GB%_[>o}uhB^S^7¨B%Q Z耈.9$%rzNJM( Ѵ$ Oz\H 6M12 ,v461vTr7r<XV31s\ ^/-cVbsF9ў+:rSʗx9}L'YBlԅK0/~+Aw}|^#npQ8i{c{?"* fLi W GhE) A?uu`0Vd%ͧ[{7bSyDDЅwOv,u[~Ȝnp;3x6jQC7iޣ]Ww&g3M_v(uL6#2c \AaL$ PpATo=QTOK!d=*fA :F@ek;|YUm%x8N,8~dAsNcY-YHD%Y~Dj1wG i٭CҕQM;=Y޽#GQyAl;TgwgB F ;y /ޞŜ<ZZrY= eA9(f?0plk'IC?NVлNbfFgF}&!N !B<,)ke Ta%B_gimq&ϑM2󬳬tn2wevB#ZEi%a1ZYǸl'DMuەO3Xf,[s M\WT=A`"|^;]& )~iW{#XHlWADׯE4O!ځq̈́K]KC@ZKK(X ퟱ_YH4YIsNuBuD: e#Su-\C=hϰ+iUdXVH:k3Ҏt(}TSӀafͣf}C- -w֖^d%?p ;3!ey{zLU {n5;_'E_x+`5  0O}@> 7Ms~`a[->1 gI()n>E$4/*:7I'vMIo?oHU%'Qǜ9@Q i %a5>p$U@a#^/a, ̼pO[|6@lC z5y$%]O^F>n=<@q!/jci#  CaC̙͢[YqVID84(撶 Sq Ac*Eܓu: siweRr`ƻi{|tEC8+(C]ueѹ#s{pt46 :K^TӂïeN/MgZ WDvq 'oED sf䊵*[=T d_/ hvce *Ɋ˺ waŸqD\OMw&̣kh>61PlǴа;hռQdwP5 GȜy1͙qDZB7͔huJ.;@GB&f&XGj;= T%.UAOzwwV!Vj"BdŀRe3*`AnVL ikQe܄yLYV]u֜1 "$B+xt&M2yXfB,^`qT$I;1Ԁ -)'6ũ+GCGd .K[픥.SUk @u/N -EdUW5d9Əf*5͒sU-gGbBt`!VDކ&Pocy D#9 L='&3;0Uǰ8%Lm=rᓗd3HK1\%@b>-4`¡02cWs#9fBal[M"w,cW)Y}}o;`L_oc9FU̍WP G=m?,- B; Z,9{QK@ o:CX=Θkpn y^LC|VG]RLj.eI3:0][7DnRmM!CN+5ݢv>-uӭq؃~`;IU{I3oʩ*b@sj˛c+@>>B/RAyk7Qgf6 B:6a%s PZXKބ$0&=QʇK+\p \>bcTVEިgM#7J"E3- ^)m"t~0 q+@>Z=GuokRH*  D V#Wn7X$@PnMju:pGNAWkw:ć"ةw|P\Zd//KT;稅kjIqOZuSܶX'Ȑ"^f !8wzӊx "شeE3."#f^B,MDȾƲusb]q2ȲZG)}[4B@C&;ouf mNӔ kHh 8{3:,1[9J̙N\W*Q^j5u0oycey8͕@ki"Q|A>ө<{)_^wˑ5Zb&I7܃ABi-׋}4TÛfuٕKͮs{h9/\ |TU?_ K=CD麩QdqfCOX\ņ @cuQ7Tݪ@B^54Qo=B'AwWX:'$܊V/}Þ1E:ŭh89o\h%X:[Ncś!=DDogm~ ݸo8"0VMr*h)c"le.FEszB/IXvT70=sW1m^>x$'OSs?&P";w`WDQF@)szDEԖg#Rm쪴/یڦ->m ;s)8tXȘd2^OYYY|$hvCMA9#Pǁ_۾juFtq{i0G>q/SΏG3Tiq%{uId)7@B'ܠq0=5`0ZʑoTW.,H;[ofƩ|Ѽb@^ó'AoeTvڥ61,[[ nɃ>Ax/ Fm ]-tu]PUn 'CyRנy^ŝfq38D,HX"6icZy{b7%LDNV C&^X ;@d-( 5FZWZV_"Z˯2xM%(1 x L=sZ'~2Yf/ /$+ P<$>tCkMr e0ZL=̳o5@[&\E,t˲To;aTvWJʃ2wqӣ#5s1q8pI9|$kE}>4b0xD PpsIlV҂yҦ"wj蚮AL:X:^2דX7,ohEm/ԴvhDu֚nbj0%W˵ DMf9aVlgڼFɶTUjP0RE! eūkOq{f"!2LdJJ1#Jstl ! ] 2bCg*QA(bbi1X. l,#kFh-d4M<<ו1nSĦ&ȝbDd2\N SSTvi(Gp^3~ż}UP4{ g@ݕ[eb!o?.]s8և\"7y,,%3]?I|#F$Uэʞ+._em*ȇiO&; oNy`q1qf"!5dٻ=&i']!+ƹi-zceę:N1׀QX \gnM3 rYHG~kٸ{͉)`@ !q|14,KJks@,k?mw!w-!՘$F}aDrZ@}ҡ`n#7Of; m6іI^y?qv.!UZWإ 4Go9XJju 0&aQ+,OK;x|7uo\g{5< Bxܸt+&#I]z3q D2бDbDbVwΌ4/q:B{ Z!ST3e1E76CdˀE.i ښ$-myVd\EUl KTKMR +ɡ=$$*(L!j.tkԝ$%V =Hv53x@; EGb $˜Err`KKϫJ)Oulm} x9`fL( [[*^Xɶ1u9NAL!d|e *P];\N mMVFZKuժ_ 4s}qat $;p+oWk0+g–"OL+AaaōL Pڲu]<<\;jvղ(siȠ:nQ̶iJKgt .~7P<8vvy)sDbq,޾LZÖM O=h/I1pۿ1v&e,C$!G)(nD )m|=ГFZe*蕖?EqM D"4+ÝXfÒCKCZ1iהT_u47 gaOܗP7R߆75c[jOYy uV|H?^MQG`)@w:B6QnzۂKƋf${,Tb\eE%Eo'sxVOvE `2@;qv15Ҵ5ZØgu^]R0&w{M>,5926ڼnwa RUfmvrX(0seKW XkPwu1u+pykdl9<,,P\tD#X/?4jݣR1 tJ2qu7!,M|6!ԙg[?tE,$;B\=J<)r-DݍzPcVٿ BA ɶZU(6oyd7DC F:>!|Q7`fy*1vŴ~phŒ n KS- npV+~3%mA@`l2JkEJRXjJ9y%iC</xz?Sxľ bAO(9uL:n@pD@@KU?~_J?#_+^[|^3ǭY7:yi`n[0Kv}pe%VZRıu|4AU% bl0y Ji O8bI&=8Ue\e  iƸ!Q(OStvၫ"7uQP 28*y@I=-fqH 7_cq^ZS[++Oy8o\ae-6~7w+q$hNLgip^R{=ՂߢXznbZz Y"؝9#^ِ1~JH_Jekv}_a w\N0y9X%/010C7,0b :B!N|3rt[ERe 5b{f0W;vG@˷HCeH `D+Z'Hd0)X 3Y Rˢ3d`.BRq`lEa7 (ɸen[>qn:![ d+ވAhW)[kjUt*yz|w;VζBX9:H]3G9o'Eyk({^(}.:M Qb&;(%PzY)Wk7xg@$n|S9q#<1 Ka"y2a,0p V0 +h"_.׌ JpľIO8P[AnmAϷފЍxPle#^] k?m8P*ZN!<ӽ#]^ BD\v87tԆTۆw4ry2 rP ^2f<'+:6 ?w`|6!+)ꠣŵw5!q' E"lU5jdP(ߌr޾<%3sf1^& Ci矨(-"^;B.ҦBg0gգS*!QțMɐ(,34uچteʶT7|+ CO\7Uۊ3GH%Ї?3r8%4_)vҙۋkpvE"5gюV3Wܗ99N~ۨ6R-H,9Lzee&U}&I~Z5X37%8XޔR-0DbF}  'zhsUFȺ:ycݲ,^C2P_Q\/xKxjk(oix=+0KW0#rǝ~S1   5:|$3gʻr鑥H)ެo>39mfG2*h(e:k6O;ggĝXƉT9qX3Cqbr%J_;K#j56[,ݷcm7=mbd'jg=1ˌxKV7Q2c*{*O +(T ['OhƧ i/Cۺg58ݽɻ$0m]ۤ(SmK)fWyÀ5r3*fyÕX/(}~]*f F {ރ"`zpWt'cV 2Ϫ8DAruXQ*I}EXQ aIԂ)Gdp#G "UB_0h'skb j;}|_|#X̖}Ma_ ]3]5„PFb >r*hH~_"Xr75T+x=Zs]d."{Q73c.98Z`L$%o[47mo [H8koq(I<5a :e> ؇- X3[~Řg݌M03!"O-b?75̀7`-`!$ xO@Xl!\oux(O5[LuZ38Eue >]cXjN@G3RaXRKUU!E|./uk}F߬`b;~9T R*a%6Щަܝ,%f]+ZUˬғ;zê_zjF{:Gï F+.RՍ"R;Wichˠ0T6\@h*t󹭯D PP@n]Pϗ0-$GJٚ Gbb%nk5f5{A|EwKH}0MʆqW\ Inj!(&yw}IQz^5]ꓵDSV~Zs(a@2pLFyz5z/G̚n~kP?!Q5>&_G/[`JBͮ 9I.B}n吩kG`PdI-VgG ǀE#Dp;@9St<Qm (l$%"lT,G͜Z(8+3=nЋ t UOQ-D"lrn'xM ZbSy>pkׂd(Ý*.s F`lgNIb*aII!E/YoCyv2^4w FAOޑ !RkLmřgxNwؐ )1^D%VEqAe¶)E8vz] $#d)|:.<AEϏKWdΈه;i1xL /S.Q)[kEgԀ`~l "6y$v4t9yV́#@vEc}2M\ 밫a}TgHS^(9m}h_a o@$A |jz2]Lt5"5kV/1W`XB{9zX\1z.XeV_ݷ*~Cz&GBsm}O\oU{+4 z@e=^X'W-,\dt"Յ*  Uϵߊak,zǬ Q'7X4돲g gͭ =`Gm1*[fhEJV3[]-*S^KURa_]^VX}UbV?5Gٹz'WT돶e hZی Po}$W?&0.} ^Di_C/1ǙH(y">zzM=^)Pt*`g  Пп`G0ϑ ĞD a_CK@9!lAy:B dBUUᩧz0-l:s*TL#^Ddty~zg{m_Cpz7qZTcG@hYz/ N^-‰>nN-0Ї00oN)5l&PPL'{!$h{$"q4NL ^ Qv.>? շmX*T?}׀Mr h#xdf8ə"gV94$XE_%<2MNh2OL1RJ1UI1XGsbĀ0C&&mG+ TMa€A@—?@d(aRW-L]80qi Tޕ)*+,*(/';+bKoV̐ά Yٺũt`tE݀,~`9z<~/2?Mc?$Qd[H-i2E&%D&Vb~KKKK²KʪKҢ☸11;k5$SD ʺBQ=;Qp$pl,\:D%B (b{i^ᦁyhu|_oThHai F{x}ɘ/!sJ[r%_c2\əa1\wo־ysz fekR=E1:>ɫ6Aͥ'PW RC*4Q>xQ'=@ Ё `ׇtYk+7h2'7=~Å޷,5Mфs[v8fGzd:#aKqLj*H$?-(-j`tX`g7Px>`&uv/ɲJ{܉#=Kms/.ljs޳#ەM9ǙWlBu#RT20ݻPH*D_A>`{ o+3KsꞸŴWRUnuP=3q.'݇rP'ƽl_F "wäRiVac1&#j%MǙ<gL6њE=K\lVRf֝~ɒbDyϠHBb~GKݏR̽!pyWV@^Imǝ3#vSȠ!b7J+sfYѶqac& S3USt~{rOoX i_` OaF]3tX̌]!{BG% \0.s7¾ȵ9& lL[Y5A3g{khnXSwҞ-y{p_Sl^_G0­rcNY,eFⲐ9V%NW8%^br|y+fMr̚"4B4f|hX|:Yݹ~*z3Qlm}'nO38QIh㶔BVMX,aSެ|iyfċ /3m-̰eYC8欰/;jSպiYl_"KwPL{Br}~ލؑ-cLڴ}eVK1mBʰ: Z4ɰ4gb}X?Wփ]KOaz:ވMY[ރ|$.bMָEYee-K5*\a֜lYuFmF4^i2Iތ1';=˝cz)-\τ7@e{zEm] q+./jTƨ12փL 4h+Ll/kb}XXWoZ/}뙠rћ]doh_g`1XG{龄q;=χ\Ӂ7?ae0TJz' (D=KM_fD&B_ B_.qoX{!hOCG7j= O<<]Pcz>܂}~BO !{08'{( }y/}?*IC^Io:CP~~K'+1՛h: uN<`BԿ?@ rfOdU{#*hJGjIQ~;9](&hO4_p뵎z/"~Wc@x"zӧ|<@ 3&@R4Ha9 H6'}!QK5GĵF%>3=ceE=EugK Lؓ2kH^e䄰D{hkȈ7YGg`@ $~;(?Od5)^pxiRKI-S!3>.Sf-Vd-Yb-\`W-_]P-cZrZЬִEi["-ڲ2Qpb˞[ `l:mG>~I&UrK>dDÌR%  V$LLD l2(S(\OS.(B̟ @ƍ`ȇ TpdI%U$+d :  dLn mM*$n[SsJN%ArlDrl3LLb3L9 j6Ӻkζ8\*wׂb,}EVԅcNJ# N1,X" &<2dZţ:a/ZhA.8\ksSR\$b&*ƍ G60z=g:|^@l򦾁I?94^9TV-v@wZg&x[",JfʓJ$"De&{@zo`Ĺۻp ll'mͻlc;eg,&]]nE +&;4iP IV,E ?81/(p goڛWD{0Mf跻=>^~nucnVg.,sX0!j3 ,T {;@͸ڻp qxcӼp#{=~iO'IN2w9N"bS͆G[E/$.\,`* M2GZazA_d;W~S׀#{>~h// 3y{ffۤ ׈ 5'kPlE)W¸*S>:,xq-s詗6/V[Ƌ[{2wOy= vŹ0@;MV|:i}DM +hXjYlZn\q^ȭƴ3gWleȽY/c>YFJoP"~@h}c ʚ5$Z&gQB̰dge@rLRu\2u}ۓ ##V9V{0NJ OiH h{)sρ'a{?wx}6 Zk#X'gNKʪ\yi0bMqZe%N NL\| WڃM=\Z#2rUm=o?'RxT?ӊT5:)RMBl E)YiYcD50vlaƼʬИ=uge{6oT@^⾄sp}~>t 3&S(e&dWDƸLmuzuryV}{DcpML_7=83j94ecL=۶G:&} ,o*O@DLAC",{"BFEdLXh1/PO2/Ͳ/wf`)+TFfF\mz,贏Ihw} ~~qxk/tǭGQQ0l&]=~ DTAPԋY s FSzo0y?}Ҹ?:HF>+A{!I:G8멀~KyF<v~?Wx˿#?/@E`t#Yxj+M{&)pퟠ}r뫘+%^+2ńz4 N#g9b %5 ^]? ?@S♥1D4Kbhiz/vU=ҢXYw֕\UփIeN`V/WTG R?Գ'* qpd+`B.8M~yrK6&/Z)*423o(4b^miδҰYB3Eq{ 9ȌCOze H(f8| ,QX*L-fRegbE߂%&BF͋[ؙ-1/T./]*/e&/o"/|x:%r.)~3>?DTLXaL¾RbuereEKٚxARRRꤼI+CBҵڢbf?dϦIWHfxXda&khp .  QEh;Ɵߋ@J:?D@<>@^U.Ňb471[Yh$ޜe!棗 M0n/lDd!3qByWr =*=iPqX^=ND1. F ,)8;z/zl^_Gح .Npq**gHq8Ţ'J/6JiB % '꽃t_ (l̀5fYP}~h|vye/={F 3="oLe)|tuyJ Sc?"s8+{>f=yw e{{ts_;=>_ߍ/7ІWc ~mNɗ-N]1FW=a؃P/{&}^:hQ=*sH.vh^snXaW\evƗlaUJ^*Eh)ksKQz"6Jܣan\E^b[:C] ù=h|VwuCy[1ssB3+m'z^^hCDx9<9;y;s3-j]x;`B}C+/+?Y8{ Z<̗ZL_];S~π!y"=e<vu,?fxπNg-)DX,D\~z*Ys+2'EQ{ cWee{~ϐdu"H] wm!q;@ܐg1axks^R&SyU+t64BgaK{YhIL7=W׀?ҭh k"7cM$Nǚ56'mNқoOΒpPʋrQƅuR~xTs,۪E:vӂŰAgֽ*돼VZAж/ b."'#].[t%lO-ڠ3ݠ%ᢌtSvUt뛪EjzvŴM3dt~G{$0xkҽ%q>Ì퇹H[(kQ+آ1>ǒoRxƸ)C9*:+k\%z kŴYf=BcIaHkL L m_`DAN| yړ15)blR˶)H7*_Ʒ*8+[+X>,U9zn|Ӗݚ){8r?=پ7 b%mCG֘}oI_M Xdle|½]ai@/s䪘mLYw͉5sܛ26U|)?8 I!{ndQU{"iDt3NCqP[u"2Q[Y=Z=UV?aԻu0YFczvb~!?Hxd=ոoNSJ GEQ{)(hOTW9WMmZυb;Ɇz5 >nE%Ao9d ! }=@&?E'ޙ9'17(tr)pOU[Zu}\f֏QgP_=V?w$KznPOӻ'x ft~)rzB ƪM 抭&k-,FK;+ KlZΌXܙ4i]e[W_7OnR_ nz>  L=X/p|bUV}VzWp,[x,\wd-^u-`tZ.br.dpx_¬y++3cS.TQ86g0G#*ؠK>2>BAEҺ?h)aV*Jj  []85/vkd`e^`\W`RO`FG`t~$n SB!A *4H@,p1pt xKl&d_Jv'}RgfFf{ueu%/ Q@[,-diq DA3Mz/Dv&/#/6!X~|F6&a _q]XjLqcNg]O򶬢6AIY4TRDO 6Prd -8j=}G 1=7.v)u9}9qjn7uxǜ9j뀀nBphFdaI0zɳA 7HDB~cGGPDӳo߽Z__:śٓ.~ega\S߯]sG|N_Y*#DXL\xzC"~tHɰBLbǯGP eD\+d#W Y/Иѽ5e] wVݎ!ɫFXGl@)!AǓ'bl>Ca?DGP 0/+ ikҫǝ$2TS;)QfVHYAz¤fF`HU { B= A&*z-^-*F y!}##k8Q~Ry27ki/TlըŃU(ʽ]|}p>pO% TWxTnl3\XE"(/&&$ƾȰ[zlؽ皼bMnd;vŎmr^yl{BpTJ-"Qa-0aqbA#()6I$P[^lqN7@Yӎuc"ڭλ{n_y^8I~zj͒WZ %V`R.(MM!rr}((z DͺW$=nw#1rǸ8S,u |]藆+"ܾLJzhn2ZfK*TZ] A1@LFId-]rEO~"ר 3: {pP~Vc!KwO  {̺!v;"{P#s)mb^d*Y:K4'''co/{oU{uo#(aousî|4;-=C;7(M.YϳNnp횱Hk/qup2gmKG\*-{%};uu4t+BJ#sG;$ywIQ%m?*4 FTa1E= .{{1k`QBGP11CG\qG,5Q%;%y'-iBN*rRюXZrhzLڜ;[2OV'!E+}"DI^g\L$9%oG%D;&B&i} e,JUdo)275I[r{Tg>)h{SBѸGPbW&*wOSj GIY{i?bZ뷖z>h7ـ9~:D`ӛ!d~Qj`HLPTy{+mVW뻶,>*/"Ӣz7(!^D`+7p#.  %Eˍ Ŷ ӵF%#vVW ژn뫿NOר7q!zzEd7b|+TTyUm+]Ӵ/iWbfj)n9ŭm-Z/4W.;ԛONC L `&&^z/po7d[B G1v?ѰX)j)6]7(:-P82/4104g+}EP?4:T  z1Pa ڰHXz:Hٹ̌NV+QY&o_8:4 .l˷ +yILR@$bsnd^`%I,=GYY<$.icF$vb;&Roۓl *Kؓ@*-DA\2krЁlX0"73s=83́#ͮcmϽMN1cOHtH5&_ؓ3+JJF(V2M YaI 5=hY1FNdoq&YCm a5A{6749;No# 낀nDٳ#;0%jYMh.5McD؉Efᇱpbo/zr)TS|b-E]"P NPCڷիnr;ٶsgEhZa9a $HnnT$Sd/"(P7бspopsL;EP̉ڭ,q nF]Zʲ1"R]KvZlj />C}cObp7Щwtv#q U$K~ ?,{.嬫Zzb<<#AcȐ Ohd!B$dT15F}z@gZnV/5 SKAC+%PF2wp#;PGZ6r,,l2ZQCIS֍)KO\4 !I>GM`!7Сtftbu-I`MNrAb_ i؆ZB87n.da5  +PQ*,1YE($ľRf|'gBÙteߞRη~T)^A;vq-0WLVPKaKLFX445MGJ=f 7/5hLM9m֜3˿6<WW,v!ڨ)|1Kcmͅ0R8\MYz쑘{K=ӟf}fgfٚv~)ȖS&{buQ4B M %.+~*G؟pqIμYn阙zf&߇ )<R!ҌdYki>tJ&?$uAr쩦%ē`WYn.5_28);B˓tfa'ǀsr"ܹE'5G*sd]Ҽˀe1VT=a/D(}yZnv-7FdTW0y7_;tUP 0-"603a[սg"쇤{)dn!%'NɹeFRrb3'#N@gbA?Cr$ o8%FXae7U CS^E^`'WMLeLTq͔TQeiY}xXA([# 9j^ z##,SUkiNW=Q_CE:Bvb%!;+2/67 9?wYy}3)WtVm]ɘ [\S`_tP(}x:ŠȢȂ3zRcpCp/.BW%l/ \ߘ1 닰N^dlAU9}͡8)́HqxhAh铇Ge <ͫbocI3KGqFZ~*w@ig DPx:1:S} v=/76/Ak(`NF-͞kK}{#.zc9x v}! Cw<9xZ( \SZeEq0zMFC sTR9y56:/^*y7X %WyENG_RTyHB[B\z =b=sqtwv ;C (ᘆ4kšݛӊy/&򫕔@9(*b(l>Q$H SrvT)ه2' ,,X9굸k|{B.dï `ƷO<^R8MEj"C " OLt1# @%&cJvvxy*Tr "0;cűKq~)Lr2Jʉ'#Lj`1RadSؓ@O 7/#411634324ld9NĶH Nahr,r~dΝ1ćV%OO:8!yQ*쉊{þfnUQ8lGZ5ےK3eXlyfj *x]+ЂCK'֓-T(RiB썐¾ɯ`_A e1Iehe1xϮCx'3ыgJȕ;^ ES0<1o5`إ!VIXV*^P-`1eP(${$%ia'WP[H}=#_|yS]ض#wloz)TQn-G1Pа^`J!FɲZz쓖{M} ʼr 9Eaώ^1ksA>HQG|mzC/et+~P^4 [ANk /ȞꉱWz:샠~ Rf1v\],<z+py ~=&z 1-R|]"ٴ'ҰM+s[`Ԫ&ӀI=UPa4<籘<]D.vBߊ>`ɞ7J픉ZqjrfoZfe:.0kWh¨{+~)?!L_A9{F<#˂-hu#>)Tڈ[$&`$uEir4#V_M{&)Ĕ__O0קuR-$Â-$; Eb ~ckJ+u^zUD#l:F&5m#W-2cW=a45؟kRX;+,&w^I?EC/^vׄrEB76(k읰!zO©O0a﷔J L,Km I?0ǽżsC` ޕiS`ԕI L?iCY{= ~zyԴƸưdʠkeLri@}k4sr7` dqcKw{7>,wh~`t}O\|'weAiAiYA i1ɈIؽQ/[Fte+o+lIYAYY1 i!xyx$h X@;lt=s _RʣlX`xP^~KޓpLp77/-/,6l06k05151ehrd̨q鶺)UUeJB<=C_?Tp %9`UUĨ*cӯi t{,bb9ÛbI˲VGmK[521Բ3.^==Ή>aVSP)*lyQu#na 4~s8z#ۧ(tIWTIȑ ,F?i {#>a?Yˬn3o,zkyRU`fƭTO]^CМ%[HK@tEPcσb`_qvZb;Yqm 6W_7˓aFm Tƞ_AYGqWΩjQMdAyu$cK''#()a 3 %.%.$-6m)$g-)ޏé A;Fvow\KiiBt  b.{$ha 3 1221 /ޒhʱ :U(ugGrv92$KܲvLY{zl  ^jGj*XحqOKʈ+_+Ə}2IHUb-gc1Of\Z! b짚'^F}LOl#Q'X!'L`[@ʭdbN$װQXZ8Ũ=Urz쩞$ pB(?`;P;[{py RwnRP ]FcI̅k-vTaآ$πA5UPE #|CQ޾!'nL.Z~)LBiM`SZY*MA b쫢{R2ok8v B^ {^`-шPD8dsЋ#p9@UZU4ve^'{UyY.1UBvMZfAvyj,3F[MJT=SތF""ڐ:Q^3GBiIlo^{= kL&|2rwH *앪z>P`Q'Qgc'XfAx5U,,|1yA@(b=#kF'aF*U.F.qYdȬUad2,\HHq8Ƅh;SភkzH2_D.)ᏽC#D&9-1eX=`t{ћ(8ћOyE'[CLAxa$n3\2.Kݵq1*챰%ɯ0{Fv_@va__ WzMVʦ[+eY OE. [7.roh_XDzx2OL%(p)%h14>D-m6Yo}Nڼ'n]*-4\NJՍ 4vJF,fMۋ8#"t fPiBܼ6xHKD-2K[ܝH:%[,,Ue<ą}TnU߇4 x@OC+u:QT꣚7} ߌaK[(k<,42U"X`%EYc~M\À"aHñ!?Zfmt8{w<5 t =*t- ,/v6("FbN;cq܂{z/ˆ\B3 ^wk=ԄjdO&>jpTL8!~xfVdkn1ӛ3緲GW-ԊmB:q>3{*!&v&ひ&bLPAF} ~^eW]v-)җMsf9s%FPNFo,`{jc 9(9,2!cTfT 0C{z]pI'{RBMi^qL(_5ŚJn㴅NƠ]LGzHRmGI6 VQe1!G#WF )~4{'+̕y<喓Qbn#]G Ӓ"oN=>ِ`x!;LC1!CM}`_#׆[H?a#-̔z<&_6`sQ0e$AXB9I eߵG9 {nBŮ=EoF-vA_VnS$*R_ThN f G':&0jLi{)(C!!O,X/i/đ}^KC,'g i8 E_;`PFrt דb/U9 ! >3_ap}qN; N.^f3-,Znˁd5 'wJ 4K~jzC ;zj??ލt!`qS[XM #p0JYPUVS^ܤ̀I!@yn :}GPFEx+YcG&e)d)F3aS~jP";ǽM'!h)8F53/7n@eF\MXTV\ި˄QTUS`bׁ︣~| wq2~-ʄ+2ʼn/$49} "ZSW[ˈUTV9}:~? ˃>׌;KA^CC:E}jCbK\RY[ɈYTWT D`D`CRG_򆜍)lDy^c@bk kC /STBZj%jú"쩰!(>aC1|#uA[#cҘ5a)X+8vOq I E`B yh$e0(X9-1cY}U_?euđ赼1Τ7֌A?sUxYp<1u_E_B$f(Y/,s[cȴ*-~( A4K`RHjosU;;mAzaZ<5e/=8C=?Bt z|(+0?.)%31bc񣞕H[*I84 T/~ H_{PPYFOvhqXɣRȻr ?9e:إm ьWa@<$|t#yAքDcGLO^SKJ)#rNFb?0 q,I%G)TE.nUw3^V=>#z{A;҄5·.ʊ*T[`baL1Xlc8$o1 j`5D~HփF o=j{e 4 ta! ME.1Ơ٢#r*qە/,A!fB#D"{jdڮ" byЅ0# B=#e-(֬72_YAʼnCɐ@Γ;n8 5i@|ˬ`H<m=cw#f.(WC-oYe})w˕8ifvb&e/! =B#gC(W.C-p[e(ȰȽA\!tvJO=-ECPsIa&W4 ė< S47 G@F/|C1msTY׺uGVOڃf(vu@4aK  )aEF؅VM"~ax`C FǙ#ݮ"hbǫɢ Nz ey[9Õbʱ $,^~X3^]v1ڀf7r(u;,]>q(u1EfY++ЕγeZrYlk3M8UEܱLF}iaFIe݇)3DYrub <@K=ϓg7 3e rkyܣSBЎqlEaYKZbhHr6\(v8ʋ5̍wbEO$]&f U/v5u y\f}b&uOcd %CЄm eC廟b3 1c -eefY \80ĝ*OSQ^Ԩ X=I ‡ ?ʇ;̉7Ќ1Đ+w1$0i$C!FhDJhEdFNWGv1$iwб?V.kWh>'~Ok6Ĕs'v&zbFaLg !R4RJ^rYDSU!^OzI?'~^OzI?'~= QH0zSBؙBM1vHcoKH*zue kMUTaTOzI?'~^OzI?'~^$F!iHhCL;g qbg m'f4}"⎽-E#O!5A4UQ=PbI?'~^OzI?'~^OzIߓ#y+o 1e-ĉ)Dod;?W\ְTE@E'~^OzI?'~^OzI?'~Ok6Ĕs'v&zbFaLg !R4RJ^rYDSU!^OzI?'~^OzI?'~= QH0zSBؙBM1vHcoKH*zue kMUTaTOzI?'~^OzI?'~^$F!iHhCL;g qbg m'f4}"⎽-E#O!5A4UQ=PbI?'~^OzI?'~^OzIߓ#y+o 1e-ĉ)Dod;?W\ְTE@E'~^OzI?'~^OzI?'~Ok6Ĕs'v&zbFaLg !R4RJ^rYDSU!}4hМ9cL2dƌ#&L0_xҥ -[hɒ˕+VTBe)Q@yI&L,Q$ #G)Bd!A:rqF 3dĀ…g *Rpnfb^VRJF>:2*& tyVbwNxusqomkigecb`^][ZXWUTSQPONMKJ(N)aSSCb!BC6lА! ,XP„ $D0``  8p  @@>M4hΜ1c 2cƈ& /_x҅˖-Zdɂ+VP2e(Q zܭ6Zԩ4 Ż{skc[SKC;3+# ºzrjbZRBtdTD4$t"ЏOώNM #DC4dpB $DA $@p@6B1@ IH@r$a B!@1 C1 )rDk/{_Ve'+;3{f'jXuƂlm؆ݰh7,\:ʠ2 P֜ 2$,Q1Z5se%C*ZЬ2^PㅌЊZh6&PF( eٟݩw쨂*BG KvoxR 8i5 zMyKyVvܒP&[hDu1(&"w7RodBR݃D#)q=rQqo|2 Oi[w5ko"^o ZfbuZfgڼ޲썑QmV%qe2W,l$륍Z 7z#ʁm?\qZ nCF~vN/7V_E/Kb =M9,~?Mw g%yzV4\f=Mr8i& ,U|PBS?:hK TJ.i3Jd]Z0剰2L*k1iej-F߆a#wqB$;C% §ô&%z"z4ц4`WE􍼗Z0 PFHnqOU8hhg= X,Ċ8$-TXd1hZ~QQheԲ .i̔+Gx_)Q&C0!ԃ98'; p|P`N't N8ރ7a٨05>BlW60/҆H{> 7C63 n}cUrHLghx0@ciL$а`*D 4".iӥm[Ϧ|Ũڹ=ڮPYW;uum.5 v yu h@"Gwns팣uqt)vݮ-j8[M}TAsZ5+**4wȃ`;ЙB#l2aKFnݭ="t@v[BwBnKݭ݆[P[I54\ /!`Ysoy>ߥ9ތvs;a,z2~>*h!^_~{-?Etr&vvϭ ځϙ7N4,0( .H肥,`zf`BQ"_ O# i3x`H@D(i{@`}HҠ(Ҏ 0=@z 8 g @@U Hdfj!3TPMaʞc{*P}'E+1P ڎ)UR&j)U \i3>Ѻp|bZ€}&l&0b}[Nj5YޞJC!|&odӧA[x:x+K)Y9T)V+Z9BIx _5l$>(17,'榈. 'VzMR)}<\1X4P"CYF?ǜ6o.,e.6Y/:grO /U +EbTjּZ񰶸aqͻWµoU_WA K`,G,=: ! ,Qɣg"G4](8Bt 1$F/ǫy%kuAiP%A.!0KT*)b(ӅT%LHU"E M>:t6k۰T#Bl뻢:!|( h[!]v}R"JȵCp˅(bֈQESS 0N6iT,Č1DCǀ3x nc _t:~trKɭ*X=1H1hJmęӥϤHACC?쉚yXI#}v0XH~C\wnRvI$`P4)lZzJQA3˵R S.tjhʼ$AԈTPO՝:P6k%_+kBg?ȃ2*_O.'lKM{ee`XfIbUάWL VB59@}n6mVDiđcU De qӶ!V Th\cAr@, C>wNrrIή&982#UE䅮d'5d`JJCKR'NKHDD rɻ C  3Wr|:F~2YI-pKQ[2}c3|]qeqCWG-Y$=XB" *taru)iDf 8r1h.fyή9 K-'f~Z1t2vຌ],z5Eq .Z0KN,Z2Rhdutu|eI"RD#C>~2r4oԤU r$TM+hv2'fi]D[gK 8 Дk!ԬW*]`_Rc>(pⰈLGYB3|'oUa <20eK1o' m.%TͺY" jM0تFP׬ #nj1h! VP! eB ],}"@8èrP#B:{Bl&l*b0|Iг#la'<#2Q@t dT(;syFBMaڥ[[{ZjZ%]:ڧt\ ](\.|Jr.x ࢀ ]-N}ݟk%ϵZQs*12Б@btXĪV&԰:!Iauqb.ս!6Mf֭qܺ4]n®Kj®v*j֊js6عR\)pL ǃ(r&!瀨rD)Q7'Iusf.΍##FQ5ڄ6Rԭ4W&/n۶VjjV+&rnt&p>h*@tq0]&Ltot0MEӵItm` jӭm5n %ԥA4{SwVudVWuZ[Wu\WYw/z~v.ef3ytN@]n=h&8Q@/0}W.߉/bh[qmGKzs:K^ | 4uƺGKݤ.Fi T7fʞ`$wEJv$m h{-4FҖaH[*CE3GZ3ItM}tQy VTSq`5Xm]oоL܇{.o`x#mȊ0,,`  hTD3'@mK)~N0J(P,-_ZE5]A`C9c8P jk{/}%T~/}† X@N\ݘAF X7>Ⱥyqͫ\6x|FY@ֱ!e>̴u6@amJe)vNiėBL.|7P<dB/ hy ,]( CO .d=\v@prQávøŭpK ߖ-ݖ:ږö],}d ?|%OD{>#Ư#,]%dJwIYLʎ"hrʼn'gG6pVmh['t=Ov>{W#$pPq' 9iܦI#B:|1\<_ȧF=Ԥ3?֠$% lP89DN郐: B6 Mb$SHdpǏ!]Ś"۱"ګ"#ɼHy/c@u".΀ 5⮾؈JbCJ9+7D*Gg$F:w(Rȓ#'nlVSգsρf9oc2=RKv] #k׵]Y<zY}W-: ER$#C2()䡣af0S6F'Q Տ.>'!W,y'6,#k6"+*B,UL0JixtD1ԨpN;j\|^8E<-F< ƓGTwd$ aXj`U2c1T J"~.ryhuYeADe%,f IKd3a'Y יT'E?I!?\ ds̄g@z i c'JXC@zNnq8D՜L儊\\Rdp4*J3{JEP%T Dŏ?țF-ဧ:]Pdz@6И@ƴ)фtp<LE褘WL*dyvVi TtO55LD ԫDԘƕdzyT&OZ9y |5gsOI5>%ժ|JDҌ1 `8DA=>;d *U9*T)[ꉚ*GZT$kKhk:vbNL4:/T҉f3-GY b=/bAKCWF.9xp(j$(\6e1X8FBiiB0ywexiM jhVPӢiA &' f.6?JTjԋStC燫 TP$3.-nx="-'G 35Z!xB= \CeQ,k7yNbb0%3 Ġ3Me4b\ kBt+t CUMV( LJ fP!E]7i:\>l#jP.o?|Jmo٥5g!LI0 EC3f3&IRLRzZn^bi|Ԍ4ɩ !>vpఁ~IBe aPgJp =]e(x$EcGӲc,Wb![T#_H'c@)MTyZҼd$"i "AN?y8nЄmH(mв0`(3dET SPT5Z6Dѕ ⫘`&Kt62uT% )[!҅IN5/sj $'2 OUWD` Ι,{oE!vEpJ!,dV I14!^*e`]h*Ƹ"]Qh#cLHJ?+PDCB-Vg8ǂljp% @y?(`0U1 "L` 0%/|18` -7\ sXE!H!IG!PsWif K4(.ECx⻀/ 냿^⇣_!~;tfU,UA"RG:&a4dRq F,;{^l$h(\UMl4yz%By7ӱuvl7EޏdQ#uDU#V6P[2IlߓT(MT8ZABTI؅⡘l1s@v RFw{P߽ vCkW#V)J :(SML "îJ +OwYLmae4 ^ |d4 4S8N^5mlh*dx']-ܭqNneꄸ)QYyz@NS.NR8!#N 8bȕQb!7@Z0 QQ;:P'"9}~9.«&=ڻڣezZ.鄶/MQ- H҆@)ģm p{(EoQ0!΂ rhqhB 7wznjVjj:ݳM{fEC4(HkM`"h@E24 b\<2]?n$Pĸ{Gwk.r]T>]-TN]IW; *̘D]: 21!1P1^ d&ݟf`Ƶ{vk.MZ~ݭN]TZݣeza0t2:^-/x !"^W udx] ٯKq>!nSa@³kviVlnԮ*j̮6+fu@t:Z]-@!h A d@ ’ud]#Yڡu)к^hݟ[vrQsPݛC[7vinVjƮ tDpH 4 X] Q.$V3kunX]Vׇ!quyVuq,b׭4q`,nKjksܵ-wm]jFxqwGkw{~?]n]wh[E.tQ\s߭@{l>Du>= gklSܟق^ >!m-#mMҾ]&muzBuVG]B+6͙w@{Lk(p(pj\$FEYqBR K4&0pD3YV U[t#diDҚ5XacG 6=v/@iIN r:@*rl0dT 5 l.`eSAWM#_4]0Lf֬(h&uq|-/e'1 #>Ȅ( t4@~0S0 .0@Ng9 4*fS@[M_o4u mӊ H9dX4?WRB`,(/cHgYh#&bʎv13F$B1lA¸Q 3fg1]`0{5saia^a ss8^sEhx% ^iA MJ(iQLY*CE- 5Y*lO.9Rc AEN J'"K*zBU( VBg"MG-ɕ\SEs(l1_xJ;k"nZyț֓#pZ=e02w{T葽'O$~LH")&9T÷qgn ̭Σ`8hɆJF07`|J0d17FB9 tЉN ʎ:1;b*C+G_,g©P>8" bs'dSmgؐ Z+" )=G 8\ /+K4y,j@mV<4 ^!#M =4 0d+3OlUׇ\f$fYXeY2%"Lt`zcVkY}xľ?ƻ))_ 38@c3&8F_[k4P@Qx֚ E0+:YLfaZVEЄR|ѹ4#h@!B u@6X< ǯ($j ;3po'uJ+!WB!o6D &,l$YJ0O\%*`M0՘BP$?Bv-Py^ aB;B1d}WyJ^Ĕ7f^]8L8Ur(Wt1i TH6ɴSUK3UMVN *_7T=B$Ahkϛ 7c,Vldc\$?֢8vēXtgh//w=@ʁrtZ<@9d\nk<$` ƉʁRU TJIHoDO\8f'^lR)EuNiDPyGxO p2Z]P˹3tVuKj93Eͷlh8| @:ڕY Ktjp9h4hJx{H(65XކX96~V\t|n}ܬcY'\ o(4|nChYPbBK6GQ(?R5u245"54]?vad'߸Z&4d~lsOw;=F@WA:1pbAnF:zb 5B,T/ Z,;V=xB" ٚ!Z(%Sh2ΪoL\3Z4,'xn qws .PGFsMй jl$ma!Z*'ه1_#05J1ԅCy:xNԄJ>9H:_AfP3xY޳x.h\ s\`N凹! pXxkqVs5Uլtv_:OlmztŸaaD{>z@!3eBiwB9ǃp;']p2D !643#y*gEUΈ*IV7"PP¦$avzyblS\`\YvҐzĐcPC>!>rInHhJ\T:f !S#ypxV.$}d5nG׸,pIG:hhbmd +T%[j9U)I >/)̀䧎Ó4Q+[-_F@A G8xBWmEdveB8l6 11֢P#bX#M፳9T #luF![\yB"ʜdi0 = \r<؆e@@k0ppRH.4yJZv gAiwF7.́V 4+=ΐ0!1ENz5L…z$C R,4VirHL&5frq^idy'zZ«=껳faf -gl&~JĄ1 k/RJRrreJJԐ /Lc "LK h0=faڥȫ5R"UһGD1 @W{RUw .zZpqтK>1Td@AЀ%&BT8Hl$K钼*ȫubZ$ëE&+MvT]k댝- h`'&+XM`jwP33"O(X=Ph@ G 5nh]b 7kFn6jIj.׍ul]91ZW`*2U9=0csHɘ ]P%,pjÀN%yYaHo ' ޛ[Sfxi *jjXmK-0%[QAŀQ`.A7 mÍofw][viKn6,޸74/.MCzuζF^ݠ-"-EҚ]%mer'ՕZZt6\]y.ڛw ]:O" rlp\!;d#Րޢ &4^&Dwxwj+wljkvaKuôTlv]mC;f UFIYert\'Bިw gåټW77Fn ^ Fcw e4жb&iJxJ w v7~o/oE\Ap* X@M@A‹~FxƒX`LFp 9Pn h4~iʠ €p3k/E>cQaH@aH]$CJ"ؑȌXDPV$"3" m` n?0> S@]Nw7tհ9s@P{ՀK (@8TN_'o6f߅KLJ2hPiPTH Bӌ؜L Ƥ:ҨrH3nHR= |&nDZ@$>\4XDJ#)?^<țuupm,nZ hq۪mKH7-YPdZRG! T-{O1Qj LjBXd1o cOH6.4`\6R].,%8YDFI$$>ߑg PEwfBȑ1d 9!Lj0K^)> M8RY:9z|Ȅ( P^8)7eS4l4t)kٟ6 &O˛vgNk[?ڰk#=5@\ЫSGH"RE,lKA'M%Z%(}#,Gc S{h؛`ޜ5+ROF?5D(\ qF2`:(&w`^1Z2a(=lBPmj8h:fF-< -:s!EU@<Vmaw]u,`eN[8LQIV!&X -GU6@q|8ӁJፇ[-.Ac;Ɛ0MQEy yQqA!_C${;b\`%9jbÉK6 )68Ut]٢Q1Nf (cvC&+rXid(FV4%j` 7q67jߗUF%}!0Pn޵vҕb밺 Ck٦+%)Wc,kN~(Rխxѳ†ڧF*ߘ಍+ cyf#a4QL2OD3nƒ'+iSq!D2@z:f {z ݉?m*v܀ FP2P)2NlmV `P^ˡr+_8 (eC'~68|ݐ7Ch]J6uazERd]%("R^, o&|܈0ZX92ߤyG{:?~f33ß H@"6Ёh uL@y–i~(5Y"Ej= w;; p^ gӃNR!L\@sŰ*SjLUUV#R:Vɉ8c6JgRpټZ ǻw7I& %IiM=x)*ֽZC,چOf^a؂1Hc4SZ\>A @@0V]ӽw/S@LRկKjߒV>"hXcY.67ZuaFf[zP) a $M2tw egm k\HUBY(y!w)E׽&a$&2Id 'pHs1ɂD81N1ij8`aN\C7 iuBGjʹ j)Un2`$\.yAW[x.d i">1*hd7E^w;cx1m<@X0)Ds&*H'Nr#f,mViZ)RL WKyBW{Txe.c⮄w(θ3XK $9|(8ׂۉ l"F)0$L R&+\qROpF`kzF j.\5^mAwot'pCs] 91"7İ#n2H\FB+#R)` lyNnIh w mflC4ZLNy^mSE#ūe= ;($ AțJlFEЉA(l>Y ,e#&zFkC# V9E@ H+P&L:'yy(Mኼ4TwdxPwKew]1f׮Lٵ;8K<Af.%w| ^5.r = ^8:* TL'!" 3xqo㥹B[)ڻ۫Kvr ]®`%A^Y%^Y8r2A9|}X؍E+V<H.T˔ xP`;8lFd ^+»[-uYu.2--6lqyaK[ [ILvBU^V(.ܕ2Oxwɂ=BGO ^i6nn]ˀqu:кldBk!hUFk'dYYUnU^`V8nKNž](ո4:w.#J+w}nN\ߵ95_zpA뢅uBi"\F.@X ],{ZAVAnՎKECNXQ&Skm"}Jr -]?3wonݥ4_nwvW%X]ٵ ZePv(V]-e*٭Rbvݩٳ;uF@P)ҮTv6]̸8w{nNݽQ5dt&,ҀWڥq}l`\Ǧ|l0ܠ+Txj MɻL4t׉BmԶVmnvri.i.A+޽}pa2$=!rtt\$7IUAK9n©R7^,UCwj.MgۮoZ;}c7\RM`0WJh˕Zr~\*kz|<\ '~D0;Cxr.]n6j7flݼ*NH<7sdx- }:* ak).% #x% aG"y3!Ԃ&ϰQ`ػA­Z2ז\@G}DBl Ȧ l eSL.0qY РHXZ[hoK*KҬHE )G_9k ڈM฽`R$A$G o>_B>F/P"ဵ `j 6, iIM fD*5c7j=K3~KfIgq̭͌͘Α|C8`BLsx"5Sl\Nv2 ĕyg\/sd_`~ ҧ%* $ħE0Ja"XRS!l 1VC:o>Foq_M䮓8нIK:$QtDskEmD(fR0H1ڜEId%+DuX2eb:mXd G~Ɣ?SлA@7N,@8xE$`Jxd9<[`x|!2F\6XrP۱nj;PAd7;|Cc7+L:yhBk$" "1HG("DJYRPĈ?=vNYH͒E^0FTHg[9bEr0HBsecE63;, փL4\mE,!C" z22Ɉ%ԓHQ /FA Mxl%W T/tjJWb/ K؀KhN '4Y3|D#>0Eށ@܁>Vb 8$80> hc(b  Qw:ʪ7Np*.S]=B!W"Q$=i-lrR2 6P Zy ϫ%&`=,hb FAa1 x~`\I3 *>Wa5r ;*.X 1,25Wb@"Hw&RL. )Eaq^m=43b !C4Ut1E'RXXEZ( +H“'R ,B9:m2]C[$卐vb S+6`%W>2Z"ЫpGMUC6UMdEA5V2"d&Ɍ=̡Y& Vtwz!Ɲr6`҃/R$UO\Pxrvm RhVM&Z W;yHKX.G`I0T@RHB2._7Bjw 8yȁzh0,'V!,r A,6/X VhNv}^m*)KyR * MdQd "_fQ$r6$ogrVOS!reTrV$h+E[C[=`gՁ1 hRCNڦWtj( .cHcG4ҸCIlF:x"^$%q .jN9yFb=PW):gx@S5zPV&Eޭ6/ *<E!eQI<$1 %{6hbQ+d8TI,k*ei V$70:AМ x>6zi@Vn0no"黆HF0T‡a i)İJ+w;@2'%"1j! (0iWYH)vRA; Sl}^* wDyNw;xB$zg<= .)a1XFp1 pSqڈDT X 2)/r䠉™ &a;h祱¼4PwuxU}7 bw]/u'L g˟pLx؀jBbPZ@10E,8~P%) I~WYpo0o 䥙b4TKq}^.w@ { }@|7ۖ ڞYf6| -g+=( aLu0/#laH4B-B^@;tgy},/r4V h^oo{A۽`  ,`-;ּ}65%xg҄#oz$+ a~׫4jm VQ*:^"yq W"4Y}f6vXu] .ig:HÀ4DHCWКݳ"uG-awb]xlBZSBMޥC[ԷxoY*5\+l]: h7ٍ;sgp=ifPnKD혻b4.XUr,{ ߅ Kޜ4k#xk.ݥᢻ8[dfBnl[&A-;uqdSؒ+F`7 ۅbn],x*ޅJ(P {Cwkn ݥᚻ7n[ c턱vHXd]2 Xkw vŚ0#mvd].qW V^]*xis*="Asxs߭û5brK¸B:)1E9(&T%tsxn ߥV6r˦nՠ]+FuhniVs+tx,:ɸN@(.Cz|4ܪ j4^xrX5Éi7k͆x]:uZ[88N @>_dkŴ^-XJ6nzB\/# VyLj0oPE Zj@9kvn_%Zy< _@SJ<G r0*'M8L3&=t6&$ICZ$Ψ<"FpMśiB2pЙ 6L_)Lb|ϡ ̾"+Lg_O8@AZ׌ ָPlЦժ[ 8h@WՙeyP9%f$Cs8a@#&ڥ :7dVj1O`|?g\A#6x;s3.%qlqe.#mN,<>dB `\k K l p9ZN!/'RIv#ʯCw89Ye⠐AMwn'[[/@ q9$X 1'@4r#4G[*V*v1)㭓kY )u}"PۻWXa%OO=VXΑ^)|p fHH-][*X rץCQܗ305XaJlF/3cm"q2 -"#@DP#z #@!$|? Hx"ɠ=J8,qD-e cP>NQ)MJ! ,.LZl̃ev'D)d+뀸8Gb=\C0qFC> [},x2S&7"hGB ^-amsBb lZ)=b%M R_iQ:&z]>aBhBJad# )h4*iF`(MOxvd e{ 1V X(#+ޔc2rR&N9b]ƩQzpM8J~qRfV,RRYH9dE+XL6Z*,}q!PC |3B.mxġzkG)= 0);"*ɒTYHuxN 3Sq@wNVri@lH"8b+'eI`BƖzC V]"5nR4& qaCi3!GjD٨ȖyN=3 q^R5+;PʹK,GX3/unRh,!#+E#D ;Dަ 9Jk/Xΐqwcݎ!|0 b$!aaf ,.zBWKy);(PANKL91!0BfK1N3HH}tU]4e-5uX:*WCtq# p&GvDEF:^e^F5Tr!2`J3ˇL&4`ri _CXD=M=*5SD:Xn'h%bތ%Bm8|}5@d#RGWktzDRv!bR!SLL'D1t`Y#$ SX"IKcAOQyt69"[,<2V# X1[+ hdTuR!VjJ(&U^4VvԒ hY&(B\ pjyCd %@a[CKbPDOSwToU8k%Ub Ԟ,"]rl @E Q'J2^-yޭ)eP+9jgT1\`,rR'$ $Qd8a"*A26|bRU٣$]$DRB l`DPł-uZZѻ5[+gpeFX^kB Y ($gpx\b13j 8:!H\f#hN_Hb4"5 䔱 4WwzKwdnol@e((TQ?0_@ДbƢ 8];y b7a^H*|hFi:kqu U 옣 E8a$Drۨwn4brETRdJP(mDrc*2"i< O:A]~h% Rہ٫ 6[f6e[&̻Z!Px{ \x>"F-RJ2ҊK{LQ2p4 rcB,hݵ3wxn]Nͻ~6V.nG oSVOnenYݸ\U-n5zâ<\2 MskLmi%޹{WwKCvq.dԽ&Nm6WVdH7L, {r†105"qѺRږۦaYrV*I)=(w` Ep&8PVB+2lHgkvˊbyP,8&460!A955 8SD= |]m9v xu؁`OzH&q91@m6(*m# mkY2BptHGpt^([2A\ bH@wB7@D!?0#( +1G,($\MQ1 Nk&%:FˍiyK[^a: 3s_w<WgZ\ho H\4I1. 3a.Bk )0&j-c09d%K- 2א8H!j%P.yVٲgxcK-[LrQHe f#N10|x#J@[(j39\\6Fl#4.I_MpiI$&66pB"(G({dYN|{ ,Tx[F ya3T12'02/0w!<17x+B/DIX!C GTf0hɅ$"x`St:6ko8+3(ebd-OΩe$^1>0@jF/ Pp 8ǡ#7+ั)rDITxl.n`c`&'p k/C 2^/\@1"C&_?{|^ThvhQ:!vbP§)ˆ$ 'g*hdg!Ѹ F9_*yqi0\+BxbB'J|z4٤ *.ظ k&2Шt#4 B鳠du -[4HNFAn04AkAN{zi>\ &*rZY*pTxNPAØMQ6_\)SGbpLqaGO,v8RNS)tYG*LQ@T_ZTP'5GZ5QƉL=.difN 2atVIU.spQf@.e+ ,iSɢ'}~RyE-U/D XI/EHs)Bb3q{`oꭑpi6/㍱ \֨p% PVX`H-B6L By3GeL$UdBqQa+m Sgk )B5{ H@6^fjíʼ4SWJP`rV3I DɄrIfG'R\R#d+.*4(B3̠)g>ORN4`Nh8K V SHq7(-6VjP1#8"ɤS$=PAH@~I,1JG0X\@Fc/(DAJ L1/Pa8 QnalQj ^Wi}<` 4UI$Q AP Bb4j` 01J)P X g,f0ȢqT'A`).;/UzF/S%Zp{0kxi/49.ZXFPD, 6= A qDN,x]`y45oۅVqMvM8]׆KX%^0!I 3  HaN!r0 ` HM"x (8 )to?/X9 xAakL!v!!aVvò'V ێ7>;kO(= <'*?8x=wDyp3yo޶X޶D4%䴦ys ytow7"ހxywcv~o=eBu)>Ma;K}h붷nB6;;mm:퉺;-j.OkNI:{o97Kp+Û'c'qma>\m:үk_ho\Vry[.ry[#ryqzq{Zո>mhJ8/|pOsC\qCJڠ jIPV66UFՑVefv%EKf@S\qɳ4,r;(G3 2͛,X쓥oͦ-Kݺnu5@+S%KS $kX>*V

4%❩ؿ2Z* ƃx1$Tw@u0Le!R!aQRAF'%@b'P{R=Ή4'9NDb$&(ӋW9^+sA0/跩4X 1ύ~tL#! y %_䓰Ry)q/AqY auZ91#coԸƏ88Nѱ n|,g"s i䡧Cy<ȁaa#i5.`b/Z3Y?k,Icwb @<ȕ"#~w݆TK{2/l6o.{Ϭc ֱy 92b"ndՑ\E_|a'keʥx[kkF?|{/\HٝLc(.eSYVN βZ0/e</3wfGp-=ok)ߥ| SV2e s^1U,feF}A\ܹg?߭k{g|.滘Sp-l?oɾc}_4 37?!8c ƱY&`7u>݃?"9 O~Š}O}ÃAt<"oiW<?<; |?%=oBIľ{r* ~oC'* D`P;8[ >ϣūp.|/64YR.pVr \#*ғzXoԴ/{}z&?Es 9{3#p2g"8LjΡ$x$3# HLGk$#VlRB+ =;ψo^5UA(sϙ->'o7$:Ӡ@ j8R6*ti*ўEOmz4_*rI**RM+>-t 4WC˞C}WH|ϴ>s3Yh/O]JU%$}Jد9SZ.LiR0aJծp YGb}u<Sl c] lN߄{̻Iď !OG[[0djihjihnichria544 {QslAM4ˎ4[MlL2Ӗ\[ Kw,㾲%R/|7~cg)a"öHmLmLmgLmILiK'˴&ʹh;N]8v$umMm&$Vr;EMY$[Go EWO8eFshD`<ьK3h[iFF6fc6mFk;n5ƺb;jQo$EI|7d7|o'm4#PpV " 6nTDm89$"VnAĻ;ԛpC 2{PpBw%?0?>'Khn!A0Ƃ0q5Ijr Ѩaۊm;񴕃yA N7A *pÉl?|>.^9~v5%\gW#Ϯfɟ]~x4~y[~{[ ~|K??ߟ??#\p>ĵc>k._}~CO ۫1ao==jiG#BϏToA/P'#&g^ty-^xWTy+Q9]|;#=C}5Uw4Ƅ lqrX3{XNnԎpP[P;i]SnNr:ʯ.o\;t?<罾ފyI^.˝#9af9šk3!wH+Hs*[r5j+rE7sra>V\:/ÿ>\[};f߮ɿuo5% X#M" 7FnTǭ[ֳmRFl\6k[fmj|MHlYv-ķ>ϡx_]{`F5H*j4B$ihMҠHܲYn?aj3ej76inL0€i}.a4~6:N1[}g3o->y*6Y|`fIÀ%͕2L"eed"8dZdN2 ˱ GmXqÄcnnc5v:Da" 1&j3ǻ14-/ =ƹ}sȗVקW ZT/PW^J2] .պZz+vhFt<Gdap6hE⠕@ T%+T$KT;A -UWsB*̫%Xgr?YeV)֪ AS5Y%Ú|2, ^*ZJLЊB݉T?Tź _D| (Ԉ3fd7Af Gmɞ6I޴Y2MmlX#徚*d[4cnpwp8snLhB2l#Jv1Od؟zKTް/|eͳ<9b;>`jNZw}]&dnr;PnLe[YX^Kew9/8̢Yd\17͜uOpC]E)|sn==rsY],ev؀c?`͌Co ?0l9;~o.aJ߅| p`` .>+Y dαeK) >Ik09xs̊'LEA߇p؄!x#x% &ώ6`o9 ¦Ex$ܹ _~wN;Sߑ܃AB!<3z?Aσg6x) ɂ}$&~04ϥ}M^  _><1_ <d@|Oc~1˿rU|>;i%Xϱ30|0&0 |ޗg`]j?iy,/ΕWl7y%E`=mye%Ņ/AZ7Ɠ9<'o<kXM>'1%w<?E^pzWi_&V[AUՃ_'C< o=|d wA-ȋ, D{B@h3Ώ zGiX=ErOE *z~7}%a̠4sAsgԨ͢6pִ3m\gg5>9Q5(Th@FѿpzYW&~ܷ}*>Q@sʊw: OWbhD#(os+V[T(-biL h[U"ȞO*~7x "Ë$eM֤tHxMҭLwxmziW _{ZNi"QRW'fԪkקڃQ5| {nxO47qjPT^yB}*ot5Z[{J-$~Zwr=.Q1_A=`SY 6ؐ1` GYe4)<и}؆ @bTI,S:~X:`ye#++qYXD 1׌xFݞ|?3.ahN@)M`4SB 2񅺕RݎZݦzj?xnzvzfz.UbF~kt^-VpL5֐;fMʛ˃RrźM u{%uk V+V ĕ  mZѫgb_^8 ƊW2qՍtLfc +MG4WR y/>;EB{Jh?:u@*vK (YmӀz A<ոӍ?p\IǖO;~tNAs"Z 4堣-,YEx* >7.wgb  ю]~¨"~jFB-gQ7LY"%5ypCU&ʲU~o ~I_ts|t|v< Pr Esd?H^d?Kni鏓=@O=}SeI++h9w=a^ `3GHpu4F5Gȷ! y%a'yMkLMO\˫6ZaQ.orgM1k&k ؏&hجn>iNso=w;`0_xY.3XcqxS;z.` [,'~wYߝm@7 =f&a3kp3wEK'8,Gkw~Gq~}wH߉;ϽK KpO0W- ︃i/pz>!&aMw}^{$= AaGPR' 7iE<SL~ |bY GI&wH T(% >/b0SJ\ok5Q |R?dxަ_f]v^ _9)Qe뛠}'2> E`2C}-xGyA2OG|WM_yy.gy+=?%$}WGΞ_catTy*(|UU4*5学Ӕ?ARG 6Qt3$Jodp={|?]T I8c70y.Gȑa(Hh^DRv$#:_FGz: k}1<>Gxy/K( D }ߚ>sx,e>9u @W8+tsM6~߁70'c|߹-\ g9'|p,nj(r|h8q! u On4h9m9iAyxǁw8ݴp! d0| !9+зpHV|L)^S yF(sb};|"#>o}H |Kxs }D(4АBBC I r"&.6l(tıPiSY(YhHIshh4jj-vx0ϭ7t:i OM"g#?H鹁>#.PHK⏒ş>-yFBJNtV\^@ f$ nn-l/h1h5LFzM ~#oi|7TXs }HGLH>ɞ|<$*wP.sD2q0mhYa)ijE?0`P~(^x!/<թ\?qމk3^pp\M6{2C@(t=@:yh]IAAǕ.CN)݆P:P u5u5uh@l BhNvw>Rch^ ~K~וy tvn >yCN8;7@uX✾}a)e*c61U+('B'{P5b'ո&n#|Ae չl:,A#j rtP3)zjBmtsMv-vVBkvBז6MRl7&ZΚCsNǍ^̃83TTq()k-sQjlDkYliY Ѳ4Pl;Tl1Xh#Zh^hbwFcIdDc,ʳ]/vQ]W { l`$j|qW^+hdXАjez- UCkuCcį j_DJSד|eX/+xPQsVЌ%KcAm[)X2Dhlu`¸MqC {CzCKz5pCLy5ƫ"^!YjoVO2u6X cݯ3hlT R7H"5CꇶTm (!Z'"x*#>!ÓAdM'K:EYщʀMXk2RIi,'8T%/u>~hU !ˑ19Qs=9QWID[|ZteEX@N".c}q(̆Cd*!#`+# ^R a=^ty++++ \9Ohy5~(|§̣>ǀ-g}XG~.X HXttB {?K oO/ϖ=9_(7c6rCf7|ㆀa.q7 j6jl&X#o6rD7݋@E!KC- p0 r؁g .K0) F?Afii5=NDN4tG* |gnv` ~+K)x<c,s=6̢&=wnþWܙnI>;Х{-\| v2}9a!b.1 S?as~{Xߡ{ ㄧq›8AL^_i2x ~ ~34ob kz+me + ~ _A`.xE ~ςo$+?j^GI7c|sYz,T_룰}GbAH0f|3@I xM^; x2^K <15eP,E_XgIH?ce[7ty|.O"|.<۲l \[jb3%,=kL>Ia{"wdy@ #Y %8y'9k$< =ΩPx'>>qO>sD&C4!g 'z#W?$샬}5?jx_"_e"!C2!DRt? AsHґB644 Og: HG@B5{hcg_1':"T|*;iK 'm6p2ՁJ↔ƔvÍ3oXxkYzj7ѵ l}T#b8p"U "6|鳖xTaEqecBT\јaqUElM,VTk) *ʎie֓5[Nl5ٴdbVݳ"Q}UWOW<D+~x g/*_8@`p`1fbDY3شc0M.VnXbq`!6'5OAy|f=Ԍf<\95rj#mjZ)T p5r4tAU*NUO˦7ZCN:8`|)1phl?g:@E퇈(O Q7o~V3f԰rMA4{ slqpM&`9NL</#'t7Fݐ wCI* 娐‚P~=;97m/$]`y:j<#fsyZu!sN pz_[+k&إOZ `GT%K>"%}D;.d/^/)bl,䱐SӨxsXI9۴ԢUB6;?GgzK׌M-c7#t#n  ED ,b")XCrpȅ$IC IIŞIƝKJǜGțIJ=KvsKϯ.3d :Ko+M/Q<:8l ;k=i3l9hEz$!Gu_GI9!S@F]CH !6042HJadBIB( +X /)|0`cЂa'# "b 4{sj b<}quyK|;xh s@ V]J"+VQ $x9T#0SHE& ><?u05DrY$`CX8JgH; mCL] u+DؓKc c"g Jްv ]!6$WH}A,<`>N4qAgO4Í# a`@A-n2gpa=:,GYt<>j@PO#ytmrJD^jh<@p;KQcsЦ P0򳱍 y$?(|^ktƮCuA\Dԍ}ω>uN#rܒvP>}?=ՇV DY퉲3D5;.PXKӺ'Ě蚋 l/l,ŨlAVcCC[c7!sɍ֪|eܗx5~عg&vL0;Y XX)ksE282b(q3j e8j:4ASlvKycxDKFDv7%AYCd_) 4^G]97E sbt607[p,ak ^>jNxy B%V,V6`RČu@^#7d v:n5[BЀ"B #4;le\)ٲb-qdzD{zEJm'-&\xe1YKaIVu{9^w;O%Rs)#4WMRJH5E[J (+OW0hUqѶ>u¬i\DO tPe[T euFXw{֝culU%AɊ,A겖/39 10"3 naD( x910#4`# 6D$v[ aԝIN$uu u]h3 Bc p f~BAPXAWIp D@L 4->x1HNnVtBK-ݓBK!^|`q`/:EGT Ρ3v6N؝n9' k!y={,^5to*#ݓKL ߚ&a+h)m^m'npm~alEŖMlMJsAs@s笹Sm}8;Y=to*7!ݡw  v`s[|f"pI&<㻋)w 17~NGs?oܩ|QA r}7s&|CHPh5`SCr^?hz1We}wF4,<Go7l_]^|4IA#=]>W{)}D`~ o4/pW dO-l |XehGBj9ftcKoz,ZO주OB,m ~A ͳdj^AS27c gIt.@1;LLZbzs.Dk,Oʞ '"wx>ST \  + ,A"4镡;iB+FU9: hU0M4i)O@&\쏼=bg~GqGĸE%YrpI@&m%LY%MZIIIL2c X͇9$X!ZYG2µ|MC샴18}ďh9rT\U5쨢q`֪J =j`ECck٣kkK#L;Cd͖6{,Y<~6{v}2߅(HêxHK̋bxhCDW `8%dݘ fSM䌴m⌵m6mތ7܌f &n+v/Ÿ(F70E᫈|>æ_!x< 7'd> rȉ2'zP@QE-Me8s5)hTmTiTm.K(=(Vg%UMo(lrS׆FP]`uMLP5lA+Z(xpZ).RDM+,vvP[,W/6$rVg|%>D5y)C . 3h 8 dKD!ĞϦiϤ N!*!j-Qk8WPbcZK?Ó>`9%_jCJp2vIN@fA\t3G"OC( NQABCC8m><ȑJnH-_S}ч{F3h臞~h싾xPak>y/}IB 4["so 'Ͼ$b1~u, ŞH ;s9Hqܲnj:t~K!tR<syK*H&j *)f LR6l .ɨ@dQĢQ B84;&;%y/Pz^yja@yA[aOН>GP_/y%/ PKKæ, X2<\arPȠ ?=;88m,`԰EpTg8Sϱ +aWN*t%"M H.6Y\l"dCQI$$뤤IJ A,+7} --$^^P3U 1զF",,B3K"( ;xAI K^`"蹴DҬ`N %IXYh3ixSzӗ8NvmQ_-b"o|cwdU02v!ݿ HoC*$Tɉ_hҏ>Q~T e\c)(bQpD"aB5x+n/b3Bıau E\QQ|5pt> =ҤJ@gzt#H6O(VUHU5tވO>=}'v&R0!tQc+'XCk6A' '4Q?ςEڥ`jݪuFU\ԉ Q;.VD] ׺LRڭp'ÉB2Fތ=5|9v}Z5? w|ӧߗx:]9X7uGbh8CYP%S!nE 8Yp.(YS7'TΧ܍ s9p(x4! ջ#}Kr,Te;# U+.&[ `P&B5?AjXb-Gϸ_>/߻v~JNx~}j>z`Ŝ"Q ?"&#99< Ax?^?>Gf!lvb |Co0bk{+l>C5 ~ ~LߤAAT+9*y(9k$9I"'o-8Z K&oDp |L$zN6D?0~D 3@&+`-"h2I߄Z변}BN_ |G1Y-H i=肴^IK.,P/MWc DEj-SmLKWZnrPyh]S죨}WF?~ CįX%l\&R5gLHd )"Phh@!jI6--./mᓯMD5'c9!;INMbΒO"E^H|_ÿ`(wCȖAr I2+BpTt2f9r␴5ސ6ܐ׆-&D+!t3(6@G~Fw*J|/BLJ(tg@C6(1Fɍ1 ;naӊ 8r0q<戳A1WnOCOO=7 ^Q>/ƣP&(Ўn 4Te"ٴ"'iEjD-dن^e݂@ŜϦ ;Ix:.p_{!mQ%xś^4Ϣ£}{ExUNoK&xSMoJQF)cRl,i( |,K894Ax&DbM - HɥPX^t"O"Öw}_ A-7D uN9%Z4 H]T<zJ|GEB@>;9|" m^'i^-b_ruWUŧ<^ca^{Q xГA/J=(N|cZ% T@J hTQh2#FGCSɝJ;̚T [3UO햪R; a7}}Sq= Iނ f>8@+lA0*BVC&R$ ,>YJ=R z Gi#MZRѣJA<tPIc:14-4T \(zBaU UF.RḀ(SJ'cS)%H F."7d)r3za ](5QmhTVh3y(oSWsbUV[6l.Vl|dja)2ɓ3HshgO! <~8vFk6n8~z}6x 0C=f%SJKH+ ,X>Z)a)92*)i4sS3Q DA6Z!B ԭgV+CL(=i{y&! dԝAҊIJ+0VK$+f%/`HfJ|Z򂒆J G,-9AbRH#BgBꍜVZ '~ ;P9s"(_xA }Ԥ"QL+1VM7|8VuSgH JH$C2%= IRb4)LlAyBUQ`,i_o2*/ bT U,vVQPEѓ ")T,nxءɔQT)g# C@>"v(cq*OԴ]Њa˜*D;ƹHSfGQQE0e¡ 5P`J!)!lB04␔)T)҆TNE!EנFEG<(p"YMؒQrG@ޙNR7y>N!)TQ FeSBï)~JI&<(bSE栋<4rq)خ-z1i|eE0H>}nksuA=X.yNLJ43T# =I$X0*{AC賏P??9xy#}3D;l! S6%\9Xww&݉k)KԭXS'΅Ґ:I9b a®Fѫ ԻBw;@f)\ᓩd'Hb%9@(Z@+_v> 8lw]+"Hq1HtA\#pUatnG,"p=d~ 'Tȏ9#>p-ѱkxfOOvm\zܤD|Lo{;xO͡(2mhC ҲFDɚ kGr% 6&?lN|Z٠ImJ[I8.5q`}sdqt,FVvtDxwܝv ]"BH5C䁙̜1L2*==ʬ(,N -;#̸謞y)=&L f4jlڵEUs`+݉@nw i!  BIaTz΄]١ L`^tY&Gט_dph|l``fƆ4L b X{frL-"> qigek'vj#݅Be#˥juZ^v\-lhqMlzd5`s#YجȊ5ZԜjYP# - h0:6v ~ Z52WݙFb-@RsTfnDRj5P*92uA͏) jtLeP3cjSԠf4S Ѐ 4LJZ!i]Ւ}ݳP^;Si?;~ ?Wt~ o?}ƜGy¡[zs{:%-¯ 9:_:*`~T#GySzzS'H_Bo|w0ɷ&7iY|"?h^^Gwf]|zWk3/z2'/2'+o`н_Q!Ɦިg{jΉ˙(УғAՇm}؋ۋѽ߃)A;X>ۻp{շ|ȷ|{_{0r(Wr{"7li9gg4 &eE?aOF_Y_b`o^8goA{"h]ӞCWamwC f.pnBM }z10 Ћ}X5}sY=s}}w}w=kQށXel4hYfMrYnlg1zJ$(#N5_E]KZTZ][g[uY'9WJtsIxgA/~fXZ]@Ou%A\DlZKXRuW[XdYpXoT&!g |@g'Z>9ϦZZR+¬v .~: iޱj髤ꩲJފ"_&T AUX*YWX j5єUEVGuthSIM)%tKRME9 uO7QTeQY/R`?ETiOUuOS/EPe4QE' U{HBhU)5D%mtb}PL=5䴘ZNPS5EQBMU4YӄM[O`5Qg/Rq/RP۴tMJ+4'}8-‰զ949"ԀKQ-Iݦ4u]IU[%Y tRDHZ!q̓uM'=7?=C=?5/~ j4$]$Eݚ$u5nDZj<#ߌ>+hk"n"l!"})*{GB{%gr&w{w2&_"A(ځUjEW Z( DZ?mM8rB_=$vWWAdGtv@@j]#DnHrdK{KDI5A[Cuq-#kX =} $)Svݏm?g 擶T>l??>r̽/{#G{Gr/C#>b7#6Çl؞?ك$Oڈ >% }Gm!8ka7n_ynk֣{6϶xm};J"@4 ^3>c@A)sœ^9-0g.Wp ݹs1v0t;Ds.S 9?a/.s^i|a{kt;脏&nbM7UǚuYG_9vOؐpG?ћ.;Eba-C޺z$ll{--vEa"Lu _23$VFqU\a[.;.^ŇXprclO#tyhW)?_5|g*$7~}b];)9L|߀!Q<__K\)*x3s^gW7K_izR%Q~O@/YR4}ӯ;}:}7 A Mo; o8OBP/~bzuǷ'1do^,[e?B{,|N5>d3|"}Yߝ}ݽPʏR:*^^]~O]>U~L~D~ /|=UϑɽA;VXRƯoOZJ_Aj2؟Ib[&DvQyGgf]Ek_ URCEq͍cTHvMzPPM5"#10ÈEfL&bOm:ۄ> ,Hn v (o y{#GgB&o|W*}$ߠMR[ RI;^*ޮw$|&go,|^'s~? (?i*<>Gn|G2>r7{G+z^;n x 'a3x&Ʉ^x FÊc# ݒ=9۔]yؗkԙ3yp:{~M;s<#q|o޸;Sq ?cc<ēo WNcnowpܝ}a=3,YIu{E8s \˚Yob ι?<Ëop[GgeuWӏNyv}{φỆOxa9ceuK0?9//r?Wn]nzٱwMKNϲ g>Q2@V]ƫuE3<łob]vǦ5I8yv$L+]FScŰe\_^m8;{v}w.rHZO3'\囹@@w{ OƱu,x}\:;,ωla#c~ɓ8Tgsk0S=f8h?߉!YFrc&ɱCyu)X1]vˉyp2Mu&{1h6+cǜe&o]1S,fɜ^>̹g;8 ~{k]Tpwg^XeOy/Y+OuU|Я2={ n7>|(o*7<ᔇ^SiAy&Ht7oT'MzЩʹ/9G|oD#_av>w}Sb+ .AQ}%Oc]WYZXiR?}Lo@?;y~/^> ^P> 8|^ ~41;a߅~ZwTg}#4<`x S8@iMH\X~nχ}~߇xg ^ŸZN h,<'Z^/UZdC|c+!N== >^@>&D w O9'k&W%IO@:54꼢#} .a}ߝ}}]> _ ďMT|EK7KįJ?J|!?7 \SAh}KxV_sd/qqqp| ;?E ~? v;Ak 9+gu& (gm^Nލލ n@flW󏽚elV9^Δ{:3^f{>w\ GOSzz8wz6gS{6g_C|O:YKCzQ糈:;Yb~u@F'tYtCKDWt@sH)]oRvl[igisig_&oBW8M.pB ]h7ߊ{3h2ܔr[iڜa:gmլ>MѼތ ެ ,o#ma֊FfIM4ZҐjJ-4\ZghN=ZKgRQ tV5۲/z;{'||1 -k/0czJ̖aƴ K͜,{z òȤ#ZmjƪI #¶ &  >l?.Kw~ä.!L Ģ&#5sEBLjӰ?è :j?뾰nʺK./Z."{Z{{//z|/o&xYxUīj^V k ]Y_riȵuw[]iy}5XYX`Oä́5V@Xg`b{ǚJ.Z7ں-hiCkY\fu!L [J8ժ*l+NZ[mm➊k^_<H|¾V`s`V_C{ l٪Y@5ُSfJ'6]TmQjEvSPo3A]$7'u*  2OF} ʖU([D SԖtbѩ&ކc;I1Zjn%Y{N5SOU_U@JdLl_?hZ{0Km.A IqS&ɭ4 RzGvv`F{k=e7DE~cTIϔOR/QJ7}Hb$wc=nDv}9ޅB| }'wkA DUPPA#%NZ"?z|%g& L'}>Ayg {4.Է)(#x?sxo>,>8OU=7D^3O8?tDd2|Gb#_ KDxC QwA܂:34^y8Hv3 a7k\>eRMj8|~І!"B3fnfqjB8klG}\U yV O~y[8g޷wo.w<ϣ0<ѵ8}Rgx{g<+Gǘ87p3Oy>s5f;tGWtFNsnaߴxk|$|a =_2yakx6~a1fK<}~7rd޵TtCg܂Ǖ_9(d8'6}u?W]\Č~'q%qx--Hhc]~o]]`g"ÎY\bO|:EW+엷X7cc<16;iٯB&>+f)? X|.;x.f2{5nƥ8tc ώy'@N<ȃo#ߍ䷙|6UN*3w *x@erE^ Nr99wrP\ʏS9q+德\8k~:v`+f{LM٧,*ʰg9-9`\̙08'+ ,}^g04M9ߗJ7 (Yv1Elftpg;% v`;3Mc04>x <^tTx63+@Θ[@~J=ח}M<,#˷by X]3+oe哨|Py7M%#?!?7a~K>?ន/Bʐ!W yC>sy!6䧆|R>=^s7~?w񯈽yWG%zҦpao1rp`<$ӈ"b|(dG}'| :pfc}%K*c/bY~1. "-d~ce\eW[e#a\ssMKc3*}"^_=U8p*e??! !?R{$d5oA<3dE:AM#+=B`n}}^= }^ [k&Xs DE gLĜwCy4D}B I@!s!*}Q=_}=۹}}5>u<,&$=D H@ -Ph, #[>ﺾN^.?.:Gt zCk'4{A7:Ǟ'G^ђ{Ft^hzJ-ǴT^KyNAM/;vWOsagpmFoz&ofmvS7C!.ivI)n:s5:暓jnj9GNk{7oC{6g{g+|6_|5OcA}=m&(qN="iU87E '"ԀoT߽Uf_nXceզRDm]}&keimFizj&im^kUߵ=`ZM탍lUYaj5ײT:OM븴ҸKz k*6:lfjoerovf}FgVf_Fd%gZ3?@˚4a-khZm-zu!ͼ.4 2&#kll1NC[h,m1m^L^l 2釡}}ǾȺBu2a}:9Ė0lv3/ҖZɫm"{#|1|aE Ll!`c#{W٤_f_: ĮZnǶzJ+'-ҲG+^  :KZŗ ˅v\hCuvp=c+-KƠŶeVیYnbwUlWsYeUwRVx+Y=TŷO5VoEX\߅]`_{նXmK\]VoZJL.on;)N{Lo.v(Jp QDTW1gUZep|Ss05Ssw6>e7*|;ioD'k{ Ϛc"(%F(ED{ :PC? 1(x{BI4yZbAk].yP: "GKV7%%ߠoP&%AP)hY ]Ph5H3O>`瓹t=vYO`=v@g<R##oD#|-Mt(S cs07gs [pxsD m>0j8VM^4Wm?̤-}H{z4<@$>Hp|4`k~2>p=/gX ct;L莏'obM_uU]OI3a9/ y*c;wM :wxʡY{8n 1MU|YGEx8nmbQ,cq="b?tF˩y.#~{K̹1G)n\ňxpv0[hly,{&v&3IK\&󉗜8,Fq]wqa3Lck'DZ9N|DŽwC6\Yj%#d3 33˘1k,e 7ֱ<&qW13a)c\'_9X]eYaF}̤90wn#0 8#K0Xp#,`c`^2_Y\<>'0 Gpa ܂35q.M{F| ߈SD! Ms!sʼvK)8^ӳze}GE /Oˇzy.ݸזFWHy(&$쟃i WV!~7OAB$=x:_WyS!I!1dd{J |HƇ܃ӧ@%#cU_T1UPTMT+R? N}L?'oKk΋<ϐcdC=#//j~g #Wi_ }O з :<P<6lϟ0^-^~0 p /,?`p·~Å^¿] ۹.Lu 6]9B=x@52)>~q׾p^_09\ePg],u%,`NZɤ|FWC'D{AC|?_ƟK`YF pc;}NϞ|1hǠ>sˑ+ǢNj o,_/B=0zC phYDUրX%щH&MwcLs6Me贓FPJCHvo~ 6۵~KI[ LH.n dC`ӚN7>-5`oRN G5_QV+]=8}^'ygwqou_uOt~.I}f/jcE'ܫp=Vz,Okvk w֞^ _._B<[uvWG`g;Y=vX˺NI뺮6 kN쨹;G.87w7K|7m,n\"q=\6"5 Iu]*5+V NlOL;N [,n0n o_,,:|Yꗥ1W n@mf"W&w(;@/қoKp$. J|1 ?6c/&a?]u_O;𪻉W<ە ѭ/l]7V߄Y~em8\iXae8KW]1sj*K:Ã>x/"{,7һdoX}^MjUU?V ~HU8 zTQ%6_S_M=$?QHe~cuV2\5~KwRULeJRÐ"<6QoB|*qɧ'tr;'$Ws|r˧2WbP:c\э?GUN=p9΂a#7鰬6>ph 1ДIc:%nY۶qcW➭ϩ>:G=y0#x˥'K 3;nQLx^ݲa쵌q9zHty~]!̯3y>&Mb8ŦXt5b_9)3NƄ۸oۭcg/[j"d\~ ~+6agb)x0[Lcۘuwaw}L9rC|#%䰡lr^Y qgGycYCȪH]frMɕG)'^ebQ o$ Nrd&7|u,з._˚1?^f pۆ`'j fz,WcyZ2eYbf1̮0L:nf +Xs</o ۃ>3# |!]2397f;p!8v n}O`-8t ְww x Nx۬+|b[T< qד"+^R{2`~/\tHPzUOeDދ(ʘO2w*?ub,[}y&.?ΕG8Gt~8QςK+ݣ>)8+Nc|'O|kVLI!y#cs<͐_A??k| oa)>6[?}@<٧2@fEGz|cn6VUF4S Ϗ }s7'Q-|%dBd^Fv&>'8 C͝l~䀳 8a14uФ=T@b/}= R~E""0.=A]. GG86iNǧ#6lQƩczj*ƫ.ZԚ9_8i?u~N/ŷ[8S[p(5CJmpTVjë3Pk7í *B"g`=_)>Yc}=?/%fA`cV"5Qj}@/f Z0k=@V{o쭿n;/퟾o~O> ,><}6 .5>u}T97#م{eMwvfzi{}6T^HyoŽsGwxotOuov.וx:_&ܑlxd+л+ܡyvkٱV6Zauok-E'Bsv录{3|Ɵ9ΟԟKr<\4tiZдOҴtiSun9;ڞs[Q8▋[ n18쮂-7q|7o[|ȇ8Ϗ8և}on צM\6 \.~}[ܞor[F`[{mvӱv[_k{c 7Z@kAUx7 Fnۈhev&MMN;MNmnltî bO1vL˻0Mo?C; o3:#\py (L.<賙>gkH.ڝ;hwZ*hw hx@;Lg{k0[Ӭo2$ \L9 /3k q1ŝ'sG,Xe>eOS4Ә|. i Z![X3.1X6d`Wp_}1Q^#yQԅu~u7s}3oͤ/| X'5x /Qxuxh\[r-[iA>%yeZwi]^RBX`ax- H_,b Ə0+W @+-ZRF*or"ϴ<ҊrŪ ,Jը,,FyPSMmG*<<B.lבr 2y)uHM.+UkQoOv|'0\a7 9qث)tѕHZ2)VL֋ך}-'o%9 N[Дy&͙0m>TJ*u~yhHDڰ$:lx:zG;1QcFgT:;(?u㫈|Օ:ktyn}^(O4c2@?!/E3> 3&'$ R g|H >1]E"xTz1츾z;+P~:(Q[r\̅c(f{~<jE[S 8_pz6Gq]V6>B[MaoX^S]8q4mxlccwn6S~<K|7$l >).s,]lb.XkƝXSyx1Bjk%SdLa- w;x L,87=3gWqA]&rEȕ'%'dᶲY޺OcC d0 Dod+K~ɩ?Q]򅭬+ײ\>ˀnfۀNXi zqM.a'c Tc+,\2`qC|ּ)Gp;3xk~Dpw4sw.{[ 2x_\)>ܺ7p|3`/Xq ܃I85N` V1/pcQ\#`1t &};` !8 ~}㾗5g5 >4/x ˂x oc7 ̘?2ck0= g&U?B^!E#5<0-|ᐷiB+Syo|Oߙٟǟc6t~ z٥GA^rEo_?`O>h< Z=kcs5w$dP:0E I,ԯrixy1@ 3%Ab⍜'ŇP</C\(7xdb{oJQL:ZaA>_ yRԷ P'cS)vO푬5ڌzskc:o<kD= E_>T9gw&ceWj~%Ll(쀇x9ȇ_|๕gPR1IV$)tVt> -%Ey08L*9~/2%?#;C^ϡG B B0t?h(Sѐ&̐^2,i Mc}:'B0uesϨMdL1\̦K6QQi ub6ۤmM4I3L2zKT{ȸw{FWTOGd}D7JkLN]N(AI ʪS)@yu&%֍YRj7ɵdj^M~% dLbEivͣ7b'!:bB/5WqK{',ji9`ҚiBi6jiefm: _G6}_vd1mhWhC-}Z9 Ȑzy@ |ig"(4X SX295BbH&+tX2c"x2jǀL930ƉQ gLJCrx\/6lۄH 'CN$E ,@TYZt̂n9CʪT'EP d N+?RHyzX ~fT ۜPfmm9.؛T&XѸZIXU'DFBBU U|02"D1@yK+V)Bdl`2kJ_J8YIqHFHrf܍&2ɸ;V3]Rĵ OF JV,$V8: V/1NP)q(#8<\9%+ Fi 2NHt>ߜ6WѹLf2YeRdVX(BXaiңucjRŌ*1c/22E0#(_ 6|xpu3X]tXŸڠWTKhB!&A=aj-;NYa* A`4 5E2&gP%р(lvx@<7.kqb$9bzbSMx2$Ċ(AJeB R_px)#`,i]1+^p9cL I"Xa :qtgÄ@=W.Lh0 IXb" Z8B"J2`謖H(]D%%AEaŇ-5<[Yq c3 =1` $i 'aw F#.!NxE()@Dg%R%().@8qBX)FZKXQzb *ZE cHP1HqZܰ;)ݱzqA,n#d(1"7Dh(ABHҁNI T)IJR)k*-7FMŐ",̔>S/H tqX0<)ݱrqA+n#6ű&DB}8aȓ!J Q(M`FYt Tx҃u H`"R 1" P"PPs䈨%FBgICM҄(Mf;DabCTR0rEj!$Z̊xRtDYQ`LdTA W\7,oeZkqwZ\mHh$G:Qb%F@,1dR e )Jb|rD W~Pղ W1,HG LD)F(LLq O0=iM\^mPC$G@$9$&Jl)JvtĆ(#1ٱKAK\vG R03H U\AXW >-Ѐ2vBH.CqVܝ+W+dq'S4 C0hS  `h  R2,`C P |uÅ (3aG qc/މ˴\Z['z8\< 7/ëp.kop-20v/HJT4 ]EkLatEREIK-1 'ZP"=EGx)pvCn*tSl Sd(W8c/`U 7O3 3C70',Rnz(y I.M(5Uۺ\J7 5vl(2N*8!S"C pU tu ,z(eqCM@VǕCH(3Ji!BB2*VW!!AuZW$Ibl`B‡f6`Q%+W>2HMdɁ/uPb2WK<[ UdGPH) RRFAB "'l@bHf;hׅ*VL<bhXU::rf/UqEK!ˊYb{xڣ􊪏*@^At&8b$,-غI:+Hre lI |fA[[Dޭ[ Xfif#t \\bbh;@lQ塚Lj*@VH!gH&2ڗ*! 8@Cؖ 4ǜxV^7 Lǥ nx*>V^mB \tņaV/9V(Nj)pr#J؛ř2e[p iG:dRB fVx-ҎK +=Rj´!ܘbLebkjiZNgkPN#B46iCJٷ*X{b&D>߄h07K75H$V0E U07V@**d*u \Ry2dHN$N|<>2; =#cN1UN|d@IcA;-ƭq)P[$W "xu2 eG%D@k/dGquqL*\Ty a%Brc,8{Y烀; pぁh\8)AXGP:JX-<\cp%1ƫ`P 7X}*#TLq4i#ʑ 9 e"/:h^ ,l@'ѸUc8.*wF ZUVz#E*H NjbȖUY$TTe4qF$hP!Ra30QnXpG#BM.JSÔE\txFL _^_`iEdj&MNaDI JWzXbqF5aߌ `\ƥPEHSY2Q RcjFi-H $J 'MT[DI"ZXb Gd^e( :(g _\wB 1 eÍ-;qB5 R1刈UcDLyL( 2U)$dq`*45!@P$JIuG'Qn:"Ó'.H8aJQ`$XUJypBId:8CXݽUh3.PKD:!ҡȓ!Iu(:BJU&O?,/gA:f$ &Zhy#w~=K.TasesnX 3N@:A!S 1Rp $v^hA<3hԴ%&TB6\<$0v裻.jՅJ7*\P_P'9\\uQB̧-4n)1o "rܚ "l-.xrAcK]DUZPgE`9ATN"*DƓN"58"1Q]x-'H )r!^h"c5T]Nwn9q"iVNИƓ Dv$ILG'+ P2#T rܚ!! AF1*nk6NǤ` s#֋\Nyr#Ax8,H&<ҦtAd̈#fD׀Tp #= 5DA._p0c)6L8A#D"hI aLqrÆT@ariD&W|قCl@7K&xܯ*ȭ) r@ U16Z!CuJ Ni 09vh9 2C$J,L!HJBYvrbnրt:&ȼqfomqܚ" W^rh c#+,\/^`}aT/ʤF5-=D )G({T¨ ]P(y5_^@]t s#&F  3-dQ,*+ F,:LI ; QH+4A=`8Ǧ~}FQ@nяe5W`p(@$Z@`B ZY̖Y}䋪"YTm$F&7 AH+=epQH}4$ԣ^Ea]=e*/8VU".pJ#<p5c̪!^T]*CSN҈ *E2c\cA͐sFk$XfQUlف HA Vd^aa5A $WV_,*I2$J /tEa JcD7#= lI6G>.YS-;L]x"!j"0NAD!^\chaEʪ,(04)% *E:]XQbˆoF{o9̖زT l"4@E1)N|e- WJ`&.$ uAY+=0YlQ肀\a FS> {Zv07>ctH |QaZK #YFHbED)(@ae j )Hq , )JDr$E"NXё( m\1A% l/2j,^*?:Ò*;BT!ʄ MdJ MJ 'OLBiB5ʒ#$y-AȪ*EJKXNQ5W Pѡ Mm2e&R`|p"'QXx%''T4)eI,$$a-QHWncUPP)b=:,=qmxQ E]8 ]u1$@7FpτQb벞5X>7FQMBhpTy} QD@jJ@$ki T5S f0K/$as xjp'x2 uI]:3Ee-/X`PM^* bA0)B\8AgO2,á5QG>|dR724B < gzpEHK-0>\x`aVUl 0a5ɪ%GB,I=b j$HB$a5RD&?0s&Ff-1#ا(@MRr\ J.œA ,b\q5Lj]Lwb:" jV>!SH%Gs2eX.)!_Dn˔%bf  ȐQJFD S25RH:ƈ08P1[Pgu\G!C$&qNt>X!Y Äܴ#I) BrԠqJ&R0v3VH!iX!G 09npIBX Hb+C`CK :xpAXve6-B̌Z@h-@5ƈPg 26jIű VFqRH$@Yq%H,.lE 7 mbBrs\16T!:@ " XC/^`}Q€,H!L*bRoʣH?<EI&)E qX8 ?eFtPXe c+4P cň\(%pm0 XqCUbToᲪHLpDSPڰ"12ʌ@U"rܜ!#̍ AB&L" Xm \[8TeJH`%Ǔ%nL1IHX^ (3:EWMmr2rs\07DSD2F(  Ȟ 7b=ʨj QO|֘bDSF!/(Qft.Gg>)hUQfl0DH@%bu )~"IL|dSTQeH :(n MNFPe!Ɔj23Vac:0!b>ʴ"fU/FHBeu' f@YAƔ#0 l!%6=9DE.@Qf* `0jLV\ca=Q#XV](ɓ2,cʑ / ɔE:$j\BSSTza# Sdh ?8 Bˆ%⥵-)\Y]DŔ#\TJ Dݨ 1Urܜ""KanF)22B!#kBĐX%VD.GHbu).8A}ESd2Š+.aAU r\#"KR`nSǘ -&)/%TtzD+,+%PiM$ '.(!m1Ut"BP<$^5 )1r0`l| &i/E05-$V`k)'@iUj(JJYL1WX( 4φl<5 \tcHt lYIPUb%u XX&RDp&,$1]Aň +B0bjXض㪁NRC"[r"B-0>$D,,J\Iaʉ%UJ22BD(#8a WRb +A0ȢQ{4.Htq~\)!WI1&҄HH`)d*G(c !P2]9 xT`CD=*qA~N7E 5p@󥊫 7ZTzB+5Pt0raFiOZ0%ËP.d7x3(L@.P"/Sijbi`Br: &#"qUjuI"KB2%dtiBL)u)(:@༲ 1bĂ2A" q;\a&gSD)*$V!RXJYeuj!PRxJɩJ< -aI,'$p V9bK S 3<Li!Si 89YƔVqJ+S*@PIduj)A\ *D!(ICL6H*7VypSf80@t!@'J (˪+>TX *@b914 )BZ@@EL#1&՝1a0d ')-X)@\\yraaeY-ZPgEd91˩WN}rAdD0"8)c*ESd2I`ΨJS@V8ѩk`D(ª􋪎]Nwr9Abl -=Z`91 R>hi9&Lv`5"HQN)IJh=\R$ b)C`ҧ#ˉ h^%UEPDy"B*h `b3R`%QF 26Z9!# j]Rw(C y$B!6 bꠚPed'#b+dcA@`* d.#9Iń4P'Ą#'$9!A0DC'`@,/J cE1^ :F4rIᣋ"VH},Ǔ%;aE ,&|e:%&X!5)I `1-F b[HE 3/Z@17tYqJKx#5YDZ4`7&&5)A(H CE Z@)!+@k fX_0`5TijcHUGX%4Ǔ%7QE ,%f*Ąd01@ 'pB+d]40E^em$B0z(7,!刦*Bc`)6{̀M!@* pABV(b@`B `W0ʂ>ʨF5-4XQOd!HU4RbmM0" U%VJLN brB,!Bր0`k$ty5ÀV=X]UY!`T_BJɒ 3QEH# ,$x 6;$J Zr. 4LW1keXQb3, 6h  ɵ"C#UF2RiB,Xg 0cX?1%YMA*#YT]$* 2,CʑUyNɰt!pAZ+3@|x%K0# ˠ!  ̥ ZlC-''F8b kR'E@8a-RdH&FL9U$i"KDuSkF,+IJVbP4wHBC P,"RqJkS*APIduV)BDDA ȉ'h $IRJY!$ҵ/](ʪ+?RX *TPĄS!*aDCY&FZ$9DCG;b3&N0[ΦjQ%+XP}^9ATNB* C`4,2#_?$& O2Y;bP2ui]TwrIYX-YN{rꃅ0!%N$0b#*^By @[;`R k/9RX.;b0Ŕ -.a)hhD&StH5"E? ^"@XoQ*&T"œ4G/:Rx1!唇-81 K, B$ #]FT..d)jdRkA cLoc0%,>~Hra*B`#eԨJDY0!,P`u3^@[ 25VpjŘ3%u,| RaH$A e,$)] C;WxZ@k WTe UPgF25bjÆT<r$O:@Qb9;e+=Ѐp]BQl0c0\pz* HQ Peu25rIѣ!V>X%;!q.MO(DWzPf^]Ěa}q, P3Hq#U<ƨEu+ x,a%:)҃ǖ''&MKJC=U.0E F0k fX]@`ŌVa @UTizcHGXC'K6uL!bJ-PN(TP|h`  Ul \[HՅ X_(`5l3Q1#VNq,"zɒSmvhT |6D"* BVȾh`Ċ P@DQ eTc :GXeƓ%pL!J8[FP00"*L&\(" J q+V[ "D@bE(l0C@ʼ WI(+R} `bG@ȼ2u0%xaEad $WV[(* RO\1H+_PB+f40 DaŎXa*G@ĨeuK"Q%2 ƄK -@y KK:\ `cWYf\]8,1|QD "\Vu"IL"Q"Cl ?Y2ڏ tp &P $ !p  |thA`XPAL0D<@ 0 $8  2d3FL0_8id$G=<9876543210/.-,+*)('&%$#"! ?>=<;:987654[HA3 |hT0@@ ( ((Cf0`t% +UL&K$9b!A1CG6jИ ,V@q"\[WUSQMCa4( >=/trV̼tld\LD<,$˻skc[KC;3#²zjbZRB:2" óscSC6o^/̇Neh!嗒/ q4ypvQ,lHK-y&_sE  6h?_~AsS@#T<(w/~,G!#iZ72 ǣrFjY-H/M_RkX?yR&4fxq?FhM_Vc^G|j=;?ax2 _H(\x ?(G_Sz0z$~O/~\4E#;/o~{!=oY=w+{o _(sO6WDC3;4i͏vC8rOZ/~0-z+J/@{-i?$֞G_ۿ}[==ߠ}k~gWG}KASi=O{ { bbGޒ~^{^o[?nj|>? ;)SCE}Gu}CAև![/]]j^T]E]>[ϣ9Xc5T4kn>j|療藐i雜i꟰ Ҿn ^ VjWx{WvbWuOWdBVc=T#9Q4QX'h m*!'Zz"/z7z/r{*\`hND clYSgEt ,.zv g! ?u&m )PE=Qe= }'g%?9㠠 :R)ݏk??Ph(z&Ghy|) Ho郞>hꅮ~~H>)0Ncv|^sO g ?tj;B CL:E-s4>;I(㣴۟@RdAZAe@vO!πFhjnb#tO ghNq1hl޲IF'l.a:{pсjz ]$@h{&`i)#cStmJGj&.eM۴ijnFHK5yjιFyG}'ާ{_~zƲ?vOPSR-SMUomuWC5Y[͵\ 1{|ǿj U[ͺro޺mkj{i[h)Lvd\vfOs6h9pc9}s6uw]'kw ע9؎=y7j=^5TJm4Am?sOs4}OC[flvα&>hwjlݶnqw-wsSMu+mwvmw|wx_;61+;u5I^q,.f.n0! 4u ^ʬu_w}&1M9%W1,6Ło-iܶq;RYi! Md\s-/)aG~1elb~s3]r7Å<86rH[n7(}e 53c֙~ <8]?` GYȳY#қ /,̫<r\&Yo2MnlZ`5xhكmSlR.ü<cq/㗍YE5q^Pj@Vg(FɃ)%wC^0B>/k ,k2>k+x ,JTyB@~GM?|'+g!|q|_O,yg z4/kzC"9? _ _ gq~?߯vW3'+.{--x#OS} =YcS}=ؽMogj-|^ 迈+鷞^̞ y{Y/{M?O{'{y{a{ߦg/y1y/,j+Dz ,Ϣ;'sGڧ}3=ڻ_}=׈mׂݼsc W-=WMWI~طC=o/}|vZ;VJ觊^*饚^*ꧨ* ^o >_69t=ev]קuW[qqSz,wnڙ(TC=?-=GI}KqK}TC~A`L^_b!]}Ջ]}GYYm٧fq|yNCdMJNP4PXPeOxTQ/@7Rob?L+N}I}v.TQ57E|uT?P;ꕴ^^)ǠHgrb#evV=YQϕF>Ws\͈" j(!"GhoRzKEOoFTH\IgHyoDDo B=[YȐ0~1s!3V OH蹠ӂ * (.J>@L5}4I]}}= U @ ZPЯ@B IhF FMڏb>Kk0=tH=O@MGs~Z dAX/C]OEg_EuOBZh=*!! p6M7p8iW;@5Ԁ7SPfSu_5Wg`=EI4[pd;9` `@iAt@#>>s\r1ZP՛cxւkpsvb{W-uMC -gkv4:hϓ=k?s?=3G\k{m!{&6[볫N;j^赅fyvGs~y{ u,͇e0L61&.t=?rOo^v췿[븫깗9z_7S84~np6۞m{pnݾmk꾡[i}rWno~mӎlɿ\ʫˑ\h%~1Y"8H/ɧ)%pOOyB;z=z9c{LGclToA×/P;󷝿1ſw󌞮᪾ξ(J3O4K6?i0y(Gk ǿgßl^~ߘmӶm>m  I|a/߬?hs ߆zۿyz7_Sz/z/#{7FߟW |g|0|&/ {{v?[ϳ}Л%=ӋU}ևً=ui}=$}ޗG}==}Mfl:g@_6bH_R^]%_i%^}k#+^ ܞBΚ5g:_c@6_Gߵ\PU[Z\d\vfo졜7>3^/~,^ Оl;O8d;o|{ =k)}cM=gmkg}a/%g>ؗo{{3*w#1Ba434\~+賊*魚*ꭨ 믺jjJq ^>EןvCz`uiWE3Q\7+qLĕ3=\=dQ+譈 驖~駤ꩴʾ*JXڭ`=,-fќ!f, n +g2,vg+~pNU4TC?eQJQQERX?Sb?Sp?Q$S870u3P gWAt9>|>kg+) ?I$PT?QB@]ePINP4OVQ_EQj%Q|tM{PLI,-]j%goz 'j&_<% Ss0Q%&e IǥS*MhBJ )#9rz#G$Wg&oJ{o_j p4GB9z 86Dt)R]|9?c}P}QQ-> ~g |35sTjӞ?{:gk?vnl/t¦I줜rAgG?jBn?$DvOD?Agpg18[/:(p6koΆ췚j9m,hnڮe[yp{{J| ʧ>!&3xcz6sO[p^{nnl~{븭;깙躃guj=Oh~~_ۃV൥l~ \xQ~o#MI x>8;nxމӽیxَ֑W-ӓ#=Д\yΗk9_WQ:+~W<OĝO7a- />9]yޗӍۙyٛma9M_hρpy?Hw)r,[_뙓Μ/wpC7[t=5Sx҉.gsN[839Hְ9|bS=;C^!&Q;uWu]7uGu6q':=f?%|/5a!t)[뭗. y얓r+~Sf-llu촎1}$wQ|?).k,;]:=XƗ83g~ Md\~2Q^B[-,O-Ũ#BនP@ alâH((@(RBJ$ŸO- &JjWENkk׮\ QQI! qw w`3x|O6s >ދ޼=0M'vcrX yzġmy,O`o Flșkq+.Fz&;̭޹UIza:ҖWmƵ+p ^ކJhNVOqN:soX e%۔?}&=A0Hv9H}ts*uOWCVmFZ^faB{kj#5BDc5XNXXmnR93ɢ*PIMsAXN^q`8#ރOK+A <4 \雳@WVÎbhwsTNrʡ6yj)8bQ/rB3]2U`n~)P7ǏߌS11L;xwh.Q]DDuꈪ;4w%j(ԃiDnKa=PhgVH|6qM.q3P81}-d#t Ju=.g6b"K$S Bg=nXEx߅ K aD @2OaAL0Y黹xsɸj!lzQ5گw Q< !讣j.DM[3{4]Ŗ Py1 ݭ `aBF^=җ?jL2?t1NA& n| 絃8Cd`Tԃ0t @TA P1!@0b'B^'q))fI_;Wr \ČKř% \5r%ֻ{.D~ IK)7Mİ X}urt'`@` &!'" uUڳVicx]N{2H/O󁹖s]Z&H2P{0h,NQmNa?ivbN"(7ɐbS٫ PC0 beO t*9$qb#?(!;ٗ9KmN.?Y?sQ]-{}~'U@b0Lk[E(x3%]ާz(B~]z Fԭ_p yep\`njPnB:dQ6,2[JP9cP7`Vt?MN`} &z,fD<9p='uD=c>V ~~AE>qq yN${;ܹs =[~'F C 1SP5<@XbUt 9vW{`k=T@.>r#9L=nG[\s c4Oer"0i!~:]"ʜ@ד>۹Ó@IvU~5Z9|[D_ѿOL?^8p & Aڑjv>u,?Tc>Mo>wnn~ZRR||)ޮ}VrUoOq-U~ wQm";?u׮k \7o7{Y07Bv S`mlbʺڊ3; >Y U80i,i::_ FC-O>ODپI&ٛdo"ٛe&ټIecW{Ji&LI۩-i+%%mm}ڱN2֖)*yh[ (nn}'n`v{[Z hrL-jHX%+jLal-Tc fl퓕ur2NGƶ2 [%e4lRϖ3m"m}.,^jn_ V !j@-؝Z&ImkxmeLRvi* ڤ_$kNmѪ[ʱ9,[=yS[45lmcgk`^w넦Z&ڥڥv] k Z%4Y%kdmE:~d -ZD6k0gl}–glyz25,kkj i6N&}PITmUĦjT-,UK!)Z#!V[j^XB?XӨ=^c<f׳5l%unM[3ݺ[vknmfzkڣi]:i6Z77@{ w2^K v7~%k^=lt+d="kYJVm-];uZj^ڮ@K } x:ߌo9Pl#RAH=46G*:4nup{{u[,knYjaڲeh߶ڙ{ >_bx$Cvꬔ;elUP"~N7gs|"qxvnͮovM6:a'>w~' oȒ߱N0fQmeQ'Xp%3E= G5qژ>yS*GaQ$!D , gB+XSUQpMQ/$ w$yW>]l^[@e[ఈ$WFsV%RE *K 4*LQh/ƞ c T^Fh3>6+z34 {2"oH rFx!!bJd#R 20[iy~t) 6 Wf2F\a'ݏ(t~ip;On'LQ, ;lXC/1 l i#G>>J0ZTg'bh1z%8BQ 5=f+VXO'0XIV#ݨsG\Sm&l/3^ Fa8 T bC%?CdhKħP)iV2Oh=jE! <`U~pit?\kZo+em% 9wG@'&#J$lg])r$2qܒVESd)8(t7Q0N{h%#[fDzS1 e4an.Abe@tT |R?ec2]kZ6ת$V'ĵV6 "yˈ$om"=iD/wcUAC$\E4p8ոhlf22X(\@3*N Aam^BZչ֧˵695pW{KKIJ[C,vKn }@FNB6qjBB܎P P`L_p@%TQVnRf Brֹ<_{֨ȵB)*pYw U0K z2q ŸlpX{Z ԪR$4!qtb@pN؉ : zEBUf# P&$ N.]U.X^"JŸV*õ^Unq۾5mGAzٳ삽 ଌfY:({>|fo/uD0xBէBI&9.Л9.O^Z!pZz{ݪ=6i'@1v-ݜ`lԅ0 ciu6 Klz[.xuBo+p[ѿ} \E-3\9I;\0½ruo]v{[;ȖN`l:7:` 7X7;`&ۂy/1ommY6B QRwĩI|[$@v=c~Ʒ/Z+Z88`غl%t \]vԶ킣\-ꢶZT6ba ;*FNP tn(n-^:va]q۵mp\fGllN([-VKNf5PYl+LVbWP V8)h"mrsFvosnymgj{z۲;W;VUUVu֊k Z4^Kk|*PNG@> h $+sczL,Ql.M1%uۨ+R]ZYVjkՒYmm[h~^cP~{aRv . KBerMA<)hBmX)Э >C\=}ca/F_L<*U;BՖ`;(0*0'M-1,* fP*9q4jMIRd sG%F`WU#DD8@dT|=Ĉmiakumj] p5k]W:`" ްLl gA:*Q 05szo0A }e$9V9'_|x3*"8 ]~ mxc[.J ̂ L0WD܅ C|\fyF@XEbbLDE`A'OUn$1OW L<,ͧp o%SWlLpS&'<3= 7{m/x#h!C1.xrrpM&>B 0!3`ʀPb 6-Y<%1x0 \'~Ko헼=9b̈{"OχbDA|K&J&,H3ZhDdPSAHM+W+ϙ_y*J~ qy|RxFIJ+CL'T}T)Q%oF< W$0`t҉6.x c=L} يe?qe #3'h\< ;MxH!$g2BAr#a %%Lm`Gēʨ[T3G* ")nˍ.?U^<ԥV*T.22ͷh;CxevdD>`s|,J(G#$2~T&IA)RO#qIِ uVQ'GpZIǃ)J5 Pif<@JDNH"`j |Pd1M2QthQ1Lp FG-#Q"qq~x,Cd$ Rc:ɩ *$mpdaF77|G:BH] $m,-ƸR!Ɨh~`Ár\0 NJ#}؏LךVm3J( FKH5A9 K:(.'(K)\TFS&R} v0C:PO.MTsdk0F;mt$p1G(a&"urR NhG;*"뀲$0*xΰLYҊI$: {`Ab R# PBf.d&dXӤ!7шs*ZDuIZBDV]4 /tx9#g I6sL1&Ǥ L48Dxsɡ '%0L[-RW[IjMǢ#9ЀǂX%*aMkU\^ 6Mȼx'SL%XdJP2qRdшDV elfLeDhQ3nl.7($ݝ3v&\@JZHڤ֨ǽVĵ쑸?OhF$09eH+f (^䂸DKa08*DWBܪgmm:ZAU!9M{ <0 QxmkԽFrS{"•bDWF2A(ۘp/pY{哷Y>s_=h+#fWUZY6oۺ/BW ~&ST>-YPJi6*iJ! ) 3hV0B]"ܯ! *\ =[uY۸ڽkZmg)oc`Ϥn֕ ejY٭*E5dh,XT{R2Ec5%uCBܸ+[R@=ZƫNZVsD /7'K:ebX%f^@+ƪi6ԅ ݹ*̵z\=(k\$E4;1/2U&Zt/ħ^x$H\` :"I@ڗҔy;A'tcDpޗg~[!b1pXE#Jt `}A +}N|m+HI'Ri-tw‘96r`P7칕㷦!Nh±8`qPVtÑ?c|^]`#9,ڙh'_0}#Cl`_5FPC(BM_5zk&:dU_S?K{bxЇ"00;=a OvDn"[8!ىHe # >*)EepII))*$70Ozdyi9HJpZmXvv@& nTBc$:.Eul4SI A40AyŞpNrJw+?a]ECY,䤀40Q 6:֣ km\J`h)B1dt΋NS dh~i$h0hnr5GS' ?$rcɏC&HHi,Ȋ)`#"|c?Vҵ>Yg!PA7q* JA1zZ QT1)DQ -UtF (^ $9jiQ')HZ'\LUg4nATGH-#"ǐȀe00!sE!XT}ACqtƎ@NC({,Y[B+H(&gТ`݊‵ RADƾtPl 8t(ӡ:dH* Lx 1D"\d0AcrC .z@g [c,`CH!aC%\h`QJZNUIZ<=;HAP RG`@uU@mjJj #,0F'@@"#*#-aΰ㋅BhPt!@iFકԋ:XR>qjr;;0=) NQjpbtR9-N(}a4F?NsxHbÐ0p2E\.H(v L0dΏ:<]5ZbP dר̵FI.,<;mP Fa`96JA%tPC0Qj0r`T=Oc.PX#X*4s%ՉFuث^. tg4>9Q <`s2FBp&_BT\ŽN9ndJ&$L>$A$ Y&Ix #9Bp`64va1J##3Hcr?} ԓM w"O QVnS\BTFRqcG;dKXlX'qE:pQ D1 ZABGX<'̩bU"^.t]nOtq((FфE4wHsa q GPN CK(<#hBG Pܻ+!rH+ 3s;a>. *\ <^{R3ئ M(tЄ8w?wGpo- }LWo \: 'bq2j|+n:[=:u{gB֭ s[a@u -crY6.ݾ)[7!y˖co^m S\Oµ4ܧågAr>kzt x%mّ9vk`nmRoRmݶ [ycoL *VH7oև-oy,B٢[cJQR ]$Uz\u\*Y۬거xjѮOvMZ9koB|T͉C.%m٥$'d(ئdRE[Et7Kp+ho m~ԖglzjOgMZC+:γ?oNoH;ug,U:WS9Ղ]O ĹbM[ԸhJˆVHnvl}֐jMKimK 8gj"D%6viY/0$ cúV,=25,u`qVK7xQ礷Kr{Hmvgk5yVԪ@kj!/'s'مݱ ,[[ve؊lڍ]CK8lY։unj-5Sx+6T¢X-ҖǁƏV|/$3r8=ٶ37; &0N<8;|nv4E8X` Y{u:.@DFˆ $R !>fV.O> ?mHx 8Gh2ݠtx=?u@tC (7\cಬpO/?N8tDbE#yM^z#or4~#Hǁ=F (c,Sc yLbB ftI; 2)M3WwpP0ʼn!d!kT!%5Qe)eohʡgS =;B^j0ߥ/ XCB$:11uJP42 D Z PQu|Ip$.h)ϓ7_5JtMZA(ƒBCA!L0"ĉ. DXĈ#VxaF7F=:Hdh'?E vdLtɜ$" Y{Z'{]kn`U 60(j a6(dՅ fx!?FC(©ڢ*:M9x$-@'H5$ mEJB?_6 ^4,OZh ؞ "sPƆ HmR@;p 4ܢaʦ G:Ŕ5*ctf 7V@D' *]d(1%hX);cP 8fyqM&` ǥ-6IPigut<3\%]ka#ko>Ł# dx].OV^0'V>9%O$xrpỲ3D6(e(v*<CreTn\\>Qѥ<]G +fa:,RXCݠ10\?$pt]Õ3n6aQǸAqf<]{\ ;2X'TdQD;5dUC{%XB%\Ax4.PښO׺\]j֟}|H@ӣ*VQYk%%cN,z2\ L+dJkiHnؖNlj y񚴔֢g3ofcY2fĴlW0ņu1.-°gYElW{p.Jopm )-#N\&b"yD x5;K%nΎdpamʖY6{bF,[u;]2Ν\:\@.-ŪjL % =U(Bx1Iʕg:X5?v̮MY"릗c/X8;Gvr~n;g(淕]wyOkJ!lIT- +$_X(f^#S :)Jp"KrBv X@W|[Wtvk :\uajmO GʄV8Ayd1V26N`?K 6`:/#M2!. z.Hk 1b:t/H=ɼK 7H'9:)D%',`٧{5XR9aGM=/{T22OSKr'U4>ʑTĔ9)'OqNi{ӠT?6@ā O1dqs&#J dAPCU8g Q0X*\,=@ᨍAO4ȹ"JF(AA XYpkN qaP媄8sFCzbB,G"E?Č"eJ9D/iWf7.sԨddʈ,P2 nqmDF Avj.آ0ayJ׺*pcE8ZÅ=``$Fd 4T`T(V$P DZ0ąO\zEbXp4ɧBD|aJj !f8-4@̜_sӱWu6։X/"5h ` b GR( DEPT$%bP,DLJP\@đ: 5iQs8W=t@9ZBENv+c]#e3b5N>#3 6yK!v*yPIrYd L<Ѕ$3G*,ݩE\ O qN9xQ3D:(if.u44ɄX¥G;C M!6(w\ s2C vT"U.dms79pd@sF 45X)GqM8 l:8&u4(EK+v,0ޡTh$Dž7$7\?{Gʹok \+巹HuCV=Z{\#``A!`AsY&e;VMxƅ,Ҹ?w1ƐArL3Iv.W"Jb.RQnV^:W^Iث(aTwit<׫ s$R)!.Y!-];[":[ }>PKȓWm识` !_PXׇRݲ-=ܴ]Úܶ { qnovmж.aOn$h;<%>&g2eࠬ[=cjlZ]cmk{!tDWs,n rp0n62l3,h֪HѪf S3fl۟uC~krz$ݡn^m_\=na&cVmgۖJm-.D#jf| G=3H]c`XD8b 7 n_"In#J .慾E[T p٢hBM @{@KU .g=>A&4#K"Y! J8|y#R._ꅻ QvaUh 'HA L}䁇n,jO$0hZ 3#4R"sGqd0(bB vj`Qs4HM:%h22 |~&N -;@(A B̚0QhƖAC`! ;(@`/vhB!nab8r1j@فf$ & su 0k ALDs4BY&'kU±+,Ҹ0*8bK, 8" ,PcL$Ռ9'r뛃C.!dm8 ՜2# YX٫%4hBIC)4(c00`M. G)Z$ICL@j(!.#hX8A \ -QdrYI@ϒ@y!“-quMQZXJ"p#yLQۣ˖4Lf e@ExPee,֎RV )Vlv`5B<9 $͓jHeG-:ZpK 0BI&!@3\j$ -dƥ$0(kVdl,OR@khd*47LA:Q"f|1D'D|8hS6HR-14piyF#,|I[@< zcʬlP&dd,O"j^IbUZ G0E2$U"2"=F$AcC(JXR$H3_۰Y $.>7plŦNUZ}BCIg@HX+f!gZ| bƅ#d`t@2Fƪ16T`ɁdH Ff68tIR`̒Er2E4ଏ cu\@zr[R3Jg="V $ AҠ(1L Ę q̊Iư$MHnPD8>"HPrK1   &e.(٩!˄cR,Ԉ"X iJD!gJ(bD#dLH2D%aPhE &JP@c !.`1†|DQj 3W" |NY;Ulڍ5;stlaMwPt 91r rOq+w3oE6um?hYXOU+i{\}Hr+t1P_h@p ?v s$@(e*J'?X4ҕp㏭!Jx– @OhPe҇/=֘yܑƍ-@P&R&sxSi#E3@" Ȅ`usXhyq!}l!@a#Ki8(TF]GǪnch!%+F|$B*i*IcF,p&Ԇ/B p²)`Q*x=9D2w.gf n.b" Y]f(0FBYLM&``.2I2Ix4̧iHw0)*Xa@!_.P#XcT6F+ U6jL*Xk=O@i.AJp1A &RTلr&!_8DS j<`E rJ&`$t* 쳀[]@ڙʶeě,S# RXE*ZzZ" $WL$K'Y08!EC/&tSDL"n#P hNZJcVkC) \Xь%F)[dƇ+XnxrEǪ+>TX1bE ,5N1$D*/znT̒iNQ@oMvtaeY4( Ɩd܂lSڍ#;e T(a% X [хʎ_GVRY@P!eJ1bD+׼qCC5 n:9*d. p?I\X+;e UPa% W@ Z8х` XX$ AT8A:.jF/$`xHC0(m!0AY7%;Gdz,ZE *[PE +YR%E -VTBEE 0RXE ,1fxBB!D8mRfDL3@C,<RSe[%Yt;ITV 0vʖ1h)2d)1#˕4T!a㋔:1KJQeȇajiS8XRqJQD|r_KpwEjX&SAZ^.#hL2-#lT"↕,"r`b+~tҡ%\A⊓, !a͍ ?`!ZDIJ* qJ(&Y>#+7d|R1eKTQE V|q!Yp8(ZhR%2MhXC$ŋt|@R!sHX" 8d«Lj2\l.xHǔ-~P!H,\ɲ P`Qb%J)B8arC".Rfh h v ش˕c`OkEbD|` ItQeKj(]2`5 ,i0A]dj#;8 Fv8q,X!Hg\9F|ܱ;֮PǦ .=ˤb*Xk]F5 0KI}K)NbiL0iT!ei$D2@dž@#E+B@D*836̬Agr 8ƪ خ=ˤbZ5i"$T(JĜVQ"41_!E 7HpdI"EPC8V !2Vp@ Tvʠ3iICXcc,ފeRk?X,֚b1bL#eJ)C H3FʄPdhD``R1%Ǐ С!480R(AA(.Ht&5c`89cg욡SLk,kZR,i!hFA!4AFdQ~,P#r Ȩ0:Dp"E >(A (8eAg 0ŮX˴bTU bChjBXƇ|,OxD5`rMFf@mƍVX x Bj>KQЙ\qeŃdk(k2kU:cv=gm: q^:q3Q>$TG000Ci0z>j(fR0j&<9OS#L803EصgbPO*o>xƛNn\lp\j@d`b u A.T/pb5>IQ[ib$Kb+Lk''ZJt cf 5 ـ-Ȩ@ (` $*A(06LxAj FCڡN$[)b&E"#Lk&'ZHl܌ekmo'Mľ]pnvrlns6r$wq7 p 6Kn֖Il9hj;PEx3Od_;u|"niؼ<#ݽܿ85|[ĶmT2ʹZka l?(E- a[8Nj%AҰc;}%Lca"Sw0t 1"j`:>$^n\ߒ2 * @<8Ly:6 ?UtPY rr@qSL/EEar%MFB oWV[ٮ56PO@x' U ?C =И@ 9qC 5G2C (OIX~ܠӓ !d,aQm˂lmQ*$ % 61`]8E[HE $UX2,Oxh6EB!TU JS)/P%]C'$&H`q6VY P:3DAFa>rS`;č$!n&wpCÆ0ıMp1 @= ]C]˿q-ϵ婴!HpfH= J!&4ސ݆$M&Di.!KO aZIEA\ck} g)^'9d]N֝d]No`i58~i8>#jTRgޠFՁ7q FT5^u!.~Eܟ;l鯆x07eu]{uSTz @ ,+Y,,XlYWYt kJ`' Xes0{JI*J&^UW Ag06JƳx xsE!"x`Qf%qff}vDM'l! @}nwOjŗx%gM|gQ|DH=A@ÙU4"i>44ƃµSuRUQUPyӀwTR{) ZCCTAG|P>煟n@+DnA\F݂9A:磁'4Xg5,mW do5zĭYwxO$oId&]n 9'@ܐN!%uB9:Rxzzh~D~^?V>O\̓NǓ8!٤L̿| ->g2,h)2Oh+XIpG2hЎK>!~A0J1}83 {(3y3־Fk]meNY9COO)M|OB 7TB_~Eb$Hr!I(BusRǒzHyQVFye[CIvn >RKI"9@XAH(Dv*h4"HhG!DA?>=Q+Fy.(ڿɠZɡ'N,hITr` @>=־X_{'Sދ|9/ 2yA,W6 |~h>ThСD؍ )# ag]һϻˆ0{w$Bю3)p, g ~ìU&wp? 1Y<%K8w1Ѣޙޙ= iAsaIXu|!p*GwO뽷[xzz٥*; w.;+5D/޹+X _e?>}}>[#" }H?Իz5j_&oRz&|&&wʝaK^g#V'qF<М8'(=Gd>o1)s9@C80<x4 ދ x$YWG=/Sbދo+ec&cx$< ^Yxo!$7_}kxГA0N귀=bRI ~Ac<2@R}yeSB2G|ؘQb'N뗢Y SQ־I'"D{~O#ƻ1EV z-f5 JСW2f0+FcY9HV( DEDgbᑨ}=?G^oO)bdɺH!0Q ,aZŒS8F%P*NmIKBVXj#YOA{pc_t> ij&~%8xVuwX]IaeuUˏ++1~dY?-tk@_͇zCl)c# hx{xCY4~ 71%<|oB0RZx /}SB / f|xŃd3xH(r8FH~3خv d\XKP1>!ݳ{ObC|>0?xA>uɃba#̂Yf@0 h 4)~PXѐ'4ekc9+:+ Rབ"zW@7ӉL>27p (~Í`G| nChWۈph#㡍̇6&. &C\Pt? |4ADػ&.g aԇÏ<$n&?Q3N?JFUBB > V: i`cnF6zF wy64I(^ yIQ0!G?DF@I .II{KyKxַJ+*T" /* AW|(=R~' hGGa7vOiN?)JB8ftR(ih碐g#"d (u/*Z(_O}~99K<'J?yBi- ֒PIK¥6KU. \*ոLD:xӱ4r3S2RQpSoZim =lst+Mӓ23s-A<ϤA>SfE4B.-F*"mbJ&.m d"I%#J7$TLBR09itJYIY)tY hhhzŻ^42X^0$4/_y(|FxHK4:2Ȕ IcR$@~(zVZ};^^SR~P >γѱ0DwnQգ :pD;E:H31TE;@w,~@\dēqU̫a aޏTw9''[x [A'BHƍ G8DkRaS18A3`"nƏj\ Ҹ=r9x,v\|X 值'G䷸#-Bخl}ua q_k+X-[^e#(5A\j9Q#D->.xXa ZڐZk⚒֚<3*cm9iL?ehm_/8krl_l}ϖ2?KÇ=  3Gؐ#cExx1bLUؒXXؔŬL< \f Wיx({2,Ώ}.!=5gl\!br=ze uɖk)V[U0XQhaҲ-fu3 ̔XbV`f4UylXꂉ:˃]\O>nVEn+*V F\ %F#j5&I,̦~d"`%S`% j`*(:'QEW~k/Z.]]s1ct(YћaT+s0bS,`" ^,? ;a3Rڣ$(jډv%*byCpLzH`<Q95r&n##lcgMh+گ(MJӎ$/M<\Lf8c9O#9 1؃M pw|g|4[@Ѿt/-MN{ys kpopsp=<^y}L8Kxw& xlUQD{.W5tw0 Opl0&F.ǻ(,޵ޯ* iJN8}=;f޷OygnȻsh`[<;ўuoQXOݟR72/E'PXx7n,>*¾aP'O?#+O <_lg8z>ocӴ}V0߉|K?Wn<708?e/UGo_ z ԫ3b?ħbMHb! x*<၇t9LJ&*_gփ!.^_9(=V k/6AO晴mhs Ϗmzd5A3h >g(<S]^K_y*z/L>yx5nAzP؎ K-a`&i/J7~Qu^^Ҵ.NW@t=r7C:H(5d< " )m"u]'RLYH2eU2Zc#\]*kL)Y/J0vQn| 'iRJ | sOſ)(@cgll.3H#]=Nx0œˆ#bO#dI#fD#gIۮmE+vm4Խב6(/N NjX#ZcC[clhoi,h(:ʂ|8j#ǃ5m<;ޮ;⮆;};a7t]!K96 _ħh(t#!ʋ~AFaSQXkGblGm^Qyf[oD!^  qC #g1` ^ @\ b)SxE㯠ObÏ0#f aɗZM3IANiA)m97%▬SR.iF ; $J# 5'tyĠ!B?;V7x EK'&*a$6Vߡȣ>yP.OfiSV5i55<V焅CSzM58-𗴡CѨ"MBA0 x y AiE qỵ>pn;P5 >>5ɏR0XӒ6-Ӗ=m~QEi9:r U)jR I:S*U!U% 5Tm۰imy؆Ы4ZRãxaȗ6O,oihB,|ZPB% !tPJ).RID t">C($(&i%d)fPOgOitZvj:iL5C1OK|(kp\tZMŧTD):T p&RyTZ¹(ŃE'OIT$OL EB@z0^0da07X{xP/E|'o^0NZB:%Z>FT\:R|jr9r2 fL̦ JHFCA>~$%JRJ II%$%J'S@&1dLn>lP dZ)iIztyyJ!S`f;M6Mi*G0c#-g)8SgC$JQhb)b*FG*E5>)D*z9,+9QC"I(/|DXR5$mZCҧ5$cH*uAaS)+QG StjpzaȁġH#>Ka&T\1ƵU1\=nJ2"_tU/B[ T]@U Tm@է5@ TOJ?8 $#p AIQP0Ht& B?5z q\wX fH($ >y*żal6xMZ>Ji@4-_ F8 W‰˞&{(P" #`mQTWcF城G@=?|CpGdr_|a~0|P ]Kw*Հ2#A)k҃)QNp5o.lr^z#sGk,7:_Ӷ]BDš! ֌ QC"EMP5&ACԚP{C-O 5)>!ԨhPCڹJŪf?2ǥYSd L|emt~ml*۱$AK$hɘ!{#gLcV|]"b\xnyy!X1T2V ahaj~ s3J/3T#0s[vHl}~ˊr]Y,='pqـ˕12ZeW\hRcJ#ˍEk ?+V` p`22')l' v"k,UR.+SmHuRyrO,)U.Uޝy{rf_CҎŴg9\N$g{0Xޣ/y/{Q=# ~v/+iZڿր}2Pu7B/!p~w{ q-EC} O߱/BQ{U$<@Dx~~9#H}Y6?J#1#<#|t>#wiXsJ'aFח@8zlMܓ/E>D^4q~ 9XbGM`L[,IL@> I/ӻz5_=U2߉ @^|>အ( ..(ZEk`Fpt\G@a. `6Q&X0jeŗrLH߈sxDxVu8Y̓Ճ3OI4P@ 4R Ջg3c5]Td4&1[| {.jwb}x6^F_d R*X>k%',Z<h⩢e -^WZY,X+l,ea1 bWƺvM?  !exI>5bhX $aC", RP&cADhb# i&m}0"y`PZb\cs)ãH(j bkƼTj=Y>@]Q}WMDYHBĠTOH$2`L,xiBDeP L< )>d)0\!EI'P$B?bLJ0OTwH6զ^ U, 4f0d! W0VF2Jrx|AiCeLEC!ɗ^y1_I#ZPȫ}ZPG-B.mB5%qkHdV Q (0\VuՔ:%|-e4ŐyZj*A^j7U0USşxTɛFy-y#xBvj7)RI.9]j8)pVrԺѩ“ TOQ(QP&SIԣE DJGH4'Lkj/5Rj/52Sk/32jsureVOV"4I4lFr:qrjX@Q!( P=r,gb%Z$P[?|(X^SSyE NN~ZN~VϬ`ҳ O,\I&1h$zAyjU2jRC%OK" 5K!nlSic.3qgw6N*/<$jIjI+h>juH5c)Gӭ%Y>U+x`uQ"B'K,iR@\Qh"HS^k掿2Y K&)3%T `SJUzv15EU-5hyIܐDW5CTX$1jc 'RGK.*!c5F*GVJ#KM2(RO^y.?YD[50CzZCB0@a,1#*D),qET. YH8tFi %PEH8 y!6sr{Db-{lǼ_4 -x;_QhBjMPm SudL٣1fT=QKt@dGS > K%$u+BQ_!L@&AP| \X%8f5 p ?Z{unۦiv<ݱ$SuBiRgHvId-ARqׄ'FQ̳CGqՏO5bzm3`; ғ!UpDYZ8$J-IRIҘ7(HUޤPPFț#pX`e!BeC\80@dʕyKs%k-pV;i0&k-U{jPV- UBaժH5.DiҼiI3$M1B=H;B-͏ 5>&rkZI8qnۑvm/ZۓΖKъu)1/D;fh0V(X7bm~gI=Cf@όxX-3"́0b\5,>a``2V;+ajmgZۏv#lg"ۭ5Ah,:[JzW2Z\hIpu5HVY\odŹ"kC YXA>Vjѱ&n`v2=kl"Kۗ( @>K@R=S\LM`#窀( [6F$(;BҀcm5T*+i Vͨ:NUji_]{UC}U].ɹҜ*e`U$$`UT+;&H&X Zq 5ZS Z3 iJk=\ZQYV{UNWmYY{5 E"XHKafh3V,Ck2$Ck2VCk1VCk-1աhFk;Zk\n{AZV;V= kZ;yw^{^ֽubݧX&݈u@kq]ZS\ VZ;y jGsQ]U;n;qxO;$C{kZ5:Z _kZ݈v3=iv1}`}-E[K_ڇWߠ$=8.OÝ8c\gاܟ?ڿWZ<&C[T^ @mLs)^ ~;/-|;vXߺ3MmWD=X@}[<WrЀ \|sVAШ}Јom]s40`g ƾMۧ2tWR?@uHJo(PZKi4mӗ<}@@g HŀR LC\׷){4 އ.~/$5*]cճlxJpsuM*\xeu-zn,\&56ajIkRcۃ{.lGⅸ:o1hx5d08a,c!hI0 a~N)Q s&Jf v`fwؗНn.xe8>gRG<|/.Nۤi13!a0 4$'T(옲vJ[*oCT2@R9wg>5e)E\~ Od3ކMXy eQm&Dܠ((Gޞ|srqd S$yGH( 1&LRأdz-%3%<I x5t<""{nja91Was]AW! Qv]qW}Pz6Tax#GcH; ׶ V A6A;>O++J=X,׺^ךz]Sbm %#`240@CIFd H 6(o\y51zVӕpdjcix2_qKDy\TcJ0ص"Dג`*' pDD,*JW@Nu|lqB O"??t1LydZdp)Ƈ`x*W!M0U>5̃xjr@aPc|0PE 7@0~A$K+PR%: M*%5 LtBC 0#Zq"+?KP~U˗MQE}ZGhaS6U2T˲6Q}ˡA ^!V1R9pVI$R( X#+_p12^D1C0yT>]*vlI)ÖtRlK'>(4+TlKdYJa/N7FzriE:jթz*UAqCaE}J́Pq= 4N=xHWbMOM ̱ , !LP̳.fKB7zz 3aèiiiOS S 3PBL-aAP 54\r̴c E~W\$!|zn~-秅[NO28=P>qDd\%d#P+_Ag6ܳ)ft |c'yvkEp]$$,Q,06(°$D]4cl`%цS<:#0UJ Hidhr&Z2dI4XJWw M*pZC^'ʠW2rRhKH!-:^2Cb)TE"OG2Ptt2d)gOm_dfdDҁg~b 56PW<В$ =\4RB0%P4BQaYJۂ$ (30`AB5W7&pB n*3:AO.t#ɭ;BvJ֎I9;*RmDunӑ:.N>4ٰNR;2H!kD@"=?m~loW"#E}nD+]X)P 7j߬Dە}oZuiɀ2Kc,YyKMMҐ76H;PƈE# @Ш?s8UX+ZvioHp{2U ӴidV ӪYZ54J&ЁF `&̀".i EI[IZC@ʡP PUa >lLusU노Yu{ݮ'Lm{ڎboVLŠpc cM06P18B_Q "b?-:U0juSkMZkZ֮ZBG{%h9ګj9ګ.9k8ګ8+hH{-LZ+ZݠjfְmLkcW߾~ھjvZ=:h~Nˉz8Y?A;}`x+`;k=^ݞ#oF_4*-L/Gste@"V:|ODF Ⱥt9sd]R H#$Y1Q҇"4s!DaTQ`ԁBp=~' 5_򽄾oxD| g-ށ#u@Ե S"T]c\`uC,%lgAtxV(j${ȁ{ XMߟI1%l&'Ho<1V-Kh 7 xHQG|X$ֳOeάY Pho$>S4|oD8,O&iň5혱-C3 1`%ّ4hF1Ш@6tB'I4s=hxYv3~GL_mSku-8U%iiq2e ]MN8ћj `$I(GP8EqbiyΏ|Oቨ5^[\dő'<Ķ] mgHBR0d!U'E"LF$)$C#- $u#?:x|#Cʛ<.6Ɵ3[~ftj5pˍ?$7r[CE.TX;"0yTFAdBIS V0! ! %Gϑk M>ŕ1OxM9%!ڪ(Lj# \{ᚗkKZhh㘈RaLvʸҜY5Ij T#=t|(p7j< #ɳ$*WrDڊX\muF(I@BnMH0(ÄcPd\ZE锣 N0V)!$7 ֬1NaeFœ?q? nSP,=DX0IC<[woǖ.0Eg(mgQ&A439Ҫ$8Kأ̇"BX4xdkQ Vid*(OU1i4ctsmVf.9g}wio[dkl {)Q0HB"eZ5)D"6PL`>B%"+SZ2$9F!Dcg .%rHu6܉ ĽpO[aaX@"ahDlT6X'L /$V|bٲ!%`_825,ԉiR ">{ \ 'Zd;EIN4Luۇkx݋{Sv W{YeF0YM`+sL 4S+Ds/% a.0X_hayǖljgkVnl,,G{uh2ahNsCXV;Dڡ.V+DjX a@7ȅZU=YSobPK vݺfN٭>v+z[/m&[hMzjvZ6ꢥg+j^'{ހocN?ˈw"c] ؠ١%nsd\%k2OmcKu^}Y?-HІ}d_C0 =&ǘ~QggKX;K=!C2P&( &H]=MJޱѲ֕euYH jFHW@|1CqGx<$D+jhT9X@d IZ@;8IA z@7CeW άwsԬns&sa-9p?a t0 >FÔ~#A|*_N6`Ai@cGL0vE2#ՈDiByBC [r<]9WzC5w| P |0GYJn! k՘]횳e6eǍoؠMۓ7T[fiMݑ<"7{Ao|PhEq-_rxylᥤ%5~Hihyj\ھ[ _޴0UFL0rY]AFh2P_BUDrh [_$4bB!C6u4y6w ȝB-1$b+)XЫb ;2dhڀK D'4O dD|m"4m:%o aaA8,p9Oh aC{-eL!+斒ER3 xl$ieaDq6*}\ha&UM$cKVQH* I .y-I"Rl0V9kш "tNYiFjs!gCz Jp c }isG܍µ>-E ¶D6q'm!Ja [B,kZ9!F0d3DŽH4B1*c+.% Pbl9M9I@*i %m1f)󇆌ʟ1%l‚!NPT@u.NLjQ;Iz(KUUrS;Z ֪B~VʶoK)[=3df̎ k@3z xi[^\FA *j_) rD7X,(P89kܫWZd'J魵B܆xc, [u xݩk` @kM].udM. B4qA;PAO'S>6=`n^ ךE)Nn[vMеq p\V־2+\Qo \y+'o$TCΎ;Q-Nx!LtjFx+t?{|CZh\Sut5Cfa_6dfOdl :LÇر,j Ƞ^Su2yQԲ9˰~$Ӛlk=ȼVzl 1 _ %6vEaf󄡽aqfb{/b/Faox^ /XXڇWXZ=-tikC0¦ ll:{ m&/j{{{0@働W窱[\/pi Tu W, $؈\emmѶZKm,.c=o=w{Aw>WV{+Xx e XbCE+]Y! j^wvՅ׮&*nV +W}|Z|-JW>ީA*L=d SiZ{tmL 7JM(^tQtE}ezһ#'?*ߪ.K+px+OEmj['EpZ;4]Moh۴inM&!>do)%V[Lo/+$48RCItNSOEU_eT"~jG)y^$7In:Ir7$7 mHxgw'')xzG vDQaFo!NU8SFE=~/E?i>Gix(㙢l [>2w!K *$B/NxPw@-xُ_>cD3yRE?a#qR/_zJB)3 nauStVg@*>y+s3?Pǜ:蟋~菓郟7x.ްXj'MbO|/)K^t+NdkG_WaA%r_lu6Z?ƃ ADp,%x%nCU,:7w10~<ƉϘ}cs7AZSYi$R wW<)x7qScuͅ\9/G&<~3o'崭la,WXOwbŽCމ,D^`#sKNrc'2Tʜcr-GŽ\x.i~f pL/EW9D`ل|Z>3"9cvK9m;` Xrf `5xla w3mo.3Y6`O0+p.g3 ܃aI;ϽW+]_4ùq` .󜩼G`+a׹vN݄I?3%/d?#8 ƒፈ*Bx:%~HZ*H>aGo߱wX+x^ : x dm G ~Cݣ~B˫IY~+P~'? ?Я8O!SɞCs=$ y$#ɯ*y%/,%O䗐"ǫ.?eh<O^"opMj^orIx5$^Ia3xJ/xN|ى2S%i#"y:??yҡEzΦǬzʮ, p #?~g/^껩kTgB}$7* 1?YH_UctO_/t&>%?_>_><)p~m^3+~2!0>0?GWLυP>O|O|ʇ|ʇ|%?YhR+7&#R}a =_=9>Y| ? #a|ߡ=5X[@S584R#CG$*R} y=}^}~W!l:Bo,QC) 64$zhB +0V!QI/*}T4РRu^ߡ݅:ů|ҟ#|Fo_o(E3ѨCz/鿿fk.~:oꢾy>,|]ϱ\ݽ\m~\éx;. i*yISzEҪyMs=`xySQg*bCgM>^M_ML?ğxѴ4W-k:jX+ick^cXr‹jbV6iig͕Nhl#-tfq_Vfyghgozd_o#u݀Y[̼6b 3 0ԌlL3,K[L.#kXdoG$&7݇}ދ=Y~YU~m1/&&6 bb? v1]CagcۅanYp/X L0n#{B)eyx 0˕]h:[xK^kj;Cׅ9ގK?.[mn)o? +8DWZfm'm  ܪXtbMYn x V[sW|]}f7YUKU De8W%g9sa~Ww>׉ZP^Pmcn2UUL:ߗR}7J](x_wQAe\OW9)ĸQ'ȗ*ʿ|*~&IIo3M{?i-8)KC.xRzJ)1Y`Ic!ID9˃2)6_FJH|Ռ2S]Ip$xpG8RhΣ'_4(>(+|r2"̻4Is#!}D /S?x㟜|әwhq3x1q @\ď?Tns=lyYCbXkҥ>= h*:<hҐOH߹r \͖Os!g>cK̹ꝋyK..KQw;u5 FZBkjD4=1O_Fƥ^z=W C&~=bKLNq,fF?[_55,fջ%wv1E,bIo~Q_1-x8mcqq@7? 'P(sͼ(X-Qcuno\y߱=&~!md nrQ>>sЙ^#qyd/r!$o>Y(>徭lwr`zS95 ]w%Ka&Gd(GYSqY|gy-eѿy-se^̈́ofvڂ%̤/9m3˜384f<nV\/x ag;g05X^2Yq<0s,;s3{_?uK+ K@—!uޔo|q>s8 Pfo}:~Fݳ<~ ދ/D/wFC1G|4f`~.S''b>F!)~wX|d^30f~52E<җ=[P<9soQ&o{T/|&yB'j͠|rIKH!y!_GG"ק}܇'{j9~u|;^͎geO}|X<j+#?d}j3_oE [=^3/ r1;GH x*+^=Y"km,>c()x"S$szգ?]Qzΰ\{ʺHƏ|IW/a'[:ÛzI?<|:|_Z O}4=o:GR^q 5;>}$7f/~_ÕY{CE߉L)XfxP?p;?Gh~ͷ!8_#98-Ξ8}n3h͢y0%> ٞp~pIP B^a>߅;/'E]=#R_գW[UX/?`~_g-ga3{=s{Cȯ:ԧ3s;^;‹w)ȳI*lC/zZqucׂy ;.Ol5+l;)ةdt~.^_n(_4|܊ur]w趶nk1u\/G%F {nG: ;cK(9&{8{7{7o‹w+򺉽7 fa8p`p8uT8;ЖSm7~{pڅ.u!n*6j-^ m$M-ijx6%ڮ}[ml'mghC[EekŠloŝ&lktvUNizS@߬_*2ZWiN^Mm F64{ElnƳ ; o73PwVQd'.4Fb/e&h&h-şx)b;ӌe,crXݣcwg(~b}߅ ,68XK_y%=~Y>Y痕~/kd@~ W~ѭlew/a| kR7<+*p-ĿZ)#,{$.l-~Xꋍ0V-ty,WKovwd-# Xb U8%'x_j<0+ȵ$ށ@ 3Ld'9HqY7y'Rr+Ceŷ[ g _ FW`A!9-v6ς~5#NpgPa#p\vQl"W1sS3 U`£1ᡘ8L;OiJx:E'Ca7frJ~Ӂq,x3|8ΏZO6[IJ|34/V(B$L=ϑ2៘|׼kށ5eX /\rj^,[y24kX*N=5E٣~W7)S^*'/_PՔoHP4J$߇u } +h yM)y:A> OE!'W$yD~m y ~Q<;~/] 7!*< t/ x b|?DƳ3rx6dUI/ xϓ(zIU+׏=hs}cR~kn?AfFOw(%xoVHI"H`J Wfy/R')1KWbqϘ,&d%sh#E4ڃѠDP3d,iL2NʄSj˫zȰwDEGC74|C7|C7 5^G}\HH&=F2%KېdZdӤO[JB()uTEVMyWd"~ RkǮo {ųGd{ø'AL|A| /xyT/ԛ8*Jbfn8uEM,Zx"֎Y.nubi^)~6U6B ~ ,/V߯ y^w0%zNab 0k[(Z¯# -|Xv_udmFhцLIpm?{K=={2~}~_zk)_0 @W,l?Som5?0UK}qmݳ{{c|<|<#x7 ٛGmƦG6=]a/m {k_ݸޅޞӃ-/« }. BNס_'S{:;C;ܩ-nmvmsL6ۯn:F]Yt{G$wP\5mu9?宼opno\ȵ-W.LDmrqTnN9#F0NX^{o7.tp"7oK|o p`<ܐɽnqp!n.ȝE.M$ޔn|/=^tm nFךWk7%n$?6MTߍĻx7$E{޴W>@n-jxQۆ&VM4 p4/D+;O;Sz1_N6^S)*-o;]0nLjt8E!:P ~CH*Va 8(ē@#f!Ꮙ}Nד8]] f4^E<<~ $%T|Aucp?\y9.qM,bk8Ťs__x 5αN{y#\eVr :6ap!WOC3޳(Tfʾ} u꼑@ LϏ9/Q맨=/R?(_P|9ASb~<[߂O<^.P .?8GBB~`|OG S>ͧ[n>j'y#3Td`~,?71z$M?}XxlY '1Z-?qly\^n-已ԕ_GU~HP'??_}Q3ڣ9t>s-@]$"ɛ*I%|YBTF#!3{X_8LSt} {:l n|$t|_|:'[9 7x$_dj3zѷ@M_Cp=Wuӻ|7Ń PTV|ŗxJ_X|cZ+~j3x *zI }P>ؗ({=goZ&sjxN~;>/@ z# z-: Q VAt{:$Ozӭ д{M$|Ƿ|Kҷ2^ҢhN*4ԠdFEd(C)3}{ː4Z2(4*IM>m%zѭ {Nϼw? 9%MsTФׄ& %TKe5iЧC t TVjuϫ>+􉵐oZx̳lɹ{otomB^Jꭜz-Sj29LWdZ!jL#36Ԛ9̮0&K 7Y}d9OK=d7+&~b'n~b'mzG2k"@2k+Z d$R2H#Ƌ$.4ȳ ΂L[Qǵݾ߰ + SxC(=`]D M*-"0DÖ8vƲKdDq1ŵ٦[lM2 {)mwSFt} B` bx\>^uͬKXfSXf]̺jJ0kivۈpƍW87\!iuڝExw`H{=`~Q_߇{C/u֬ fp@K I`F@S 2@{Po6o%0Ro#źyp{ޛ7+}o/}pV%}qV}sV}uV }wmɧw#ޅ|}m=J<Í 8?o6މ׻zKukGg;:k<:xt0xuV&xwVxxVxzVx|c;G<Cb k=~jUfjhmeSFd2<곅/$7/[h=Lъ٢5YuJUH3"/Q621YI$Ydq7ynbet)0ӑRa~,ğלK|-DـQn"`<ŗ8/J/D?0`x\qa0- [eqX‡*\<ٍ7#gU,wEbeeZoγdy X0 V(t+#RWRj}UUucS"Β*TZ<n׏úvБ7JJMމKy #!"'/?*~b!>-=1#c <4&zcL+#:^DŽo =d vnRqy1'IG 0bg4|"8;~qM9B;/FɊ+Y&d|vr^0f=t2 Cr|d'K^qGաzE2Uʏg-vN3mn~߄Oޝ;PsX\<,X1rb0,z9?iv͇o[v`%x8'A˻/`bn`2TF3c6/~3830\7^_6 Yw6zϼy}&3yN~`"G0'b88!z{ 8//]w, c,f +:p|q7\§||7|߫@( G84d> x.Żw/;+1+S$~H~@?cUI<>K߃sn|o!,x ߁?7??[GY%x_T7gQ$3x4 0*o)y5Ge~1="L_ct o0_'S 淈 b |_w a_Cqy-o僲ˏO)$J_Dh=qc= Ga%Ju/1%?yH"?/(2ľۧ{A{8 x+@GK*g.Ɨ-`dcs4ޑ96fX0:ԌfL4chӫzY/{@ qaM 41x>%6F$@g`g5'|~r(.BRC[ E{(CqP }I#HZӏzY5='{d|}\?VUՐnHZHV< C,<(Ӝ~`:@}Cf08X Y f?t)Y ^Sou1LS/#JiZN&r:uӪ=Z-zZSM3ݺΔ6ӮL*ڪR ݃AپS7S+)?2+M|elb]ͭM-rjYxjg^@F5 ЮG~m &h؎b~IgkgyTJkqf_}O~dCj>CrHXw2̺v.-X)`fU)fIf5YfM<3mIL1l ] v wK,>%r=C|cw,Wۈf]G:]zjAuٕdU5$u5TmKI%&{/H"&ě 2ǽr{_e߆,2>p^y3{|nO~{ax P;}Zn]uȣ O@*z몭EZ-lW$Q^U^Y^waZ[iZCqZ3uZC>\ =rMtŵHS3}. 4|e2 xdێKr[m͓MPi]LfV8_:M<>@1D$FH-3J 7nH῱:|Aea!ֲ@/yx?6ro{na- Ժ`DZŀZ|Z|N+u_S/zuÑ] V L,4-axBll]r1dU1(c9x)AMEg-.*o!̃y ^J(oT@8S8؊S4xGa4;0tiơK4\ҍr\c$!9ed#)D埰|jx) ~΋ŋ;H/s6F3bT%F6QcE:VQD=@R" shȑBEV :`P~d2)cYtzƼљOx iGZoPHu~v|AOXLr|"%Na&Ο|y!2Uusy/=fӜ^{&x!9/vFKr"GyS1UlcZF}2_<̎3|oOG3ghXZ$b1_lf9i^}ͨYt\)/`xp{ݶ`|5+D=[E~tNsYlֲ"0 >q^Yr9'0"6 smgw ?BF@>{ ,vn9?0Gp%d89:˹| 1o؃MaEs~΅һѻ3789~rY<9&aי%l;؄5wW=+%3q1xߠ pf W!z[C9C3}%2$QjŌP ?! `B"!~oPw(ރ}gk:~fc><7>SCbDYM4`' cޏF|>'z446 ^3 VDGDky568#V+-RxoWbg\Zo apdbD? $?8}R p@,Ev<x6pg}vPGAA  %,1x賂b)!#O'㵒X(i5,Z~Gx7Jt#@ g}>x_ <`+gyg{g5/hɟώ?F> {P%y>)GK+"`5p<?GS}#~zޏ?X<_[gzVg94уbog^eO%^Uso*i]η':kz[C />K|1ޏ*?-/@u=k'Sngɟ1v?O,}aȰ)aɳiz`u#ڙgw]4LFz֜B<WT|݋dzz@yy:7fg2{J NL!p=5ByPaGz=+~ƝZ12X#Tq[]qp >{1[RlbvFۍ7Lu@yaI6S)5Tkkk8X7ܸbõNki*Ck1Yl|YOnC>my84[lN'e1K)pRzERảFk2UVSOc8Lʼnc,sq/̂͘t㟮!/fϘ<[wko᫃uq9HOs|.MQ ]pHXr@SLbB|s'ˆaY+'tX!_Q扈lб "i|@rF&bbȘE -UH6qcRN Ch.30e_kM,3]e4KTY! "#aK(paB3 E8It`yΠ$CbSn6]n-VTə&M0ـ -dvpF0V^(D 6BH Р))*908Ye_SNoA=T66˕S&]>!d&]I¥1Vp|(EF 5GZE␚. QVNQʎ> xHR@B_3d ,\t#L ! 1Ppc X!@`@5䧒+ܢ l[EJB_2JڥP1STM$l IuSj@t|   p(dT BG@D -I6qɎ * yC n"ċ''_D,2&ɓR7FR$?h(@ а0NH0Bŋ8"lll`sDcG+/>X5+c{f=b/XDb:"ۆ]d˰kg+YbŷVmI4m # iX22}16= Wb#Bd۰vkY7r6lkj淫`v[mc@#e4B 8sPh8IF'L.8RZ&XLDia7 SStC>tr5%n ,ogt l-%9h&zs ~sF/<\ 43I BOKtB)SI,-Kpq] fMF25UUaFN1TvAj 07`S~@tuN)T R?I;B I:.w['ay$l tـGP!YTqe d8  |h ApЀ9-:-=XB R ;>]NPS K)d ѦSBotƆPcd(`Ei"J:'6H- da b! _;@caLv H`2, udy`,sDM%"x"PC gL͇oP5`F N~ZqECb@DA҅0v̤lЗ [d\KfY:J(dh2$`P,8H,Y4 3;4!,mY3'+c{(dfD"gS0xPu WPREE dj]#DgRB\F F1Ё8dtp7+{d|lՏE 5:iDT &g:AQM@1e,Rڐk 8A,ƛ@lI1ćH| U,9bfNt k%f,T\EJ CvlUm XQ0Ёƛ;xYcșCʕ(_jYcT&>X0ƃH4uɜe,TEJCvIGV(`ȋ#6i#ə?aD˕N00MR. %c%Si]vKB-٤lɉ Ac/!#I6QJB``LU'np^Q(8b@xc"M[zS*N&XdD6ID ȈYMP̐åZˆ flHFnK_NcjZvKOF!$;dA\4K.$+5dl,Q؍ұ):ڍG; Ya#6\+]K8żkz&aya+nQP%-"dPEp,Ħ 4T&pZKA'z[Z7Dު@6ޖJD 02 \cie2)I9 Nu'hAOI@'H5? XIR d{*$#&las4A6)&u6J+Px2`CBH%3B8BAج9J/ Ѐ`e’l֑RK]@pBDCT%cV dT ȈY0Ae"o $Y{o4xc%+=XF*]BX+Z4LAI)IFɸ& \J8g09h 0l S\EZDbDȞACPJtEJ-WHX)ղHBIMRV@&#Fx#5e֚,ؐ"mX()eSb MVhx""L))ZQet&b\SƦf8TN#p Ƙ05weؖ,0AG4"ɇ5KLI}"YHK!i$"؄l@" f説[- >XCD8bD!A *]L2څJ3bft@*ؔ4`EDLsRQ%6$RBAJ pbȊ@f54N9 )-GDͤfٜ BF6$9RI%#[4$ =V0\Q"=HMIrŊ1GF0TM6`IF4v ɬ-KEeڊl :l, GV1'i@4fC/VP1R Q2t\3g2fN+lUݓej#Ec@F=dX " !UԀ %$_>9C10|y9e>8C0kV9Y!I*,C\Ԉč*h@҅0Q4$I#$`jy`SLbJ(| q^6V5Y6![4c}BFY# @d,A 7hc"k~:!6v 3K ^6ˆUY:&d~02 S@qŌ[ƊfxҦdž$8",djn)0j0?*kUd܀PdÓ7A&c:7aCIs3Zn=5!.V"zc1$ Njtv@ i%%|X<\Dy0ȋYldY%( ,4LQl0OONXZґhM"U@(SBu1P'44BFh&P;/j:@:b,q$׀Eli<=ZPLJ6;8)šMXaZ8ʀ@f#V 됱 ܠ < [;DeQg*0 eNd8!ȩ=H۔ 8@U :r/Ho ԀeʘKiU/NPpPR:fQ3) $q)G 4r@  A]:KnW֨Ll-Y.fQŲjt (^N0 ˆ>>IC#":؟œt%Y-%;CU0S1#3#Åd0DE`CiRs*IR,W`3(Tp @BƄ30geޖ8GD!@#ChX„L(U^g HنRL7@Fhfl}zKGfF$xC&~ )&n8(RF -R41†hP|43g%Y4.7eސcT`BRxĕ#^2!uJ[8:R262*" ݡYªPʪjJJ** ɩiiI)) ##""!! jX==(?|9}+W "l< xmѐǬ^,%|9oi$>?2^9>GA>9[~~(̞b?sc[!}WAWX]EXn_!z;`_o/t#Ȟ^Ȟx}_WA_ETF?RJ/SQOUVYoWgoU}/zծz4zw?z"=U>P}"e 'zz)z {*(Kz^۸ YװcU T*Q]'Jh'{ipOS>MK[*TK}TCὓu"8sN"MԏMSM'IY?g!'b2h@KH,)ôMNNUeOoLҏ 4}!齜_9 6bzhh!%RBH)KLLRtM[Ni$L~&I@WQzJ?bCq-3XJAK) ݤ4tFIE%MtAII$)͓JNKTL]3R+ >R{ }G_@HGxDyg;ِCz *(D裣AbIP4IVJ_TJnHD]Er0nZNo4P5R=5Tw]uj+4κȚU@[B_?DfODrOCD>TSyj7OiLuǯxc}[o͵]Gu_' ` 69ygh>g{4w8k*kcG5kýk^dY]uPHm4B3ڟm{[%A-簹vNا9ً v}V[쵻f붩o+ 7.njx_S[>H^l޶nC~;n{:ov>zt>7N7 ql&o^|on{ w:7q3oYp bU/#Gq_ovю\lɽk˧ƼȋƜh{71]D9|O|g|!OsJƓr-37|;Aw;1<6^5RδV]h [_1Ce2tʣtaCOr ;z]O\vf1/J,wky.I8$^q[9-4FƂo}v.IvrM&֣tY3;*Y)BvqAӕy-2QV|ʄ[o,ev6lz~,; /`r|/x6i|͗9^ O`X mϙZaI9 Z!f:0'8,c 61o9O.%Ţޓ.s\"xjtx #gZ/2k6b^ɘGq7ˡTD'FOb_V ay)AT@| zD>;o hei;/"sʼn6# =]ω&dT>υx_UL<@>}5=_6^>ekt~χ=W}. 5HO>f%dorGai@0?q/6&$ju߄Ӕ,*<ؗڣͽ}O߯"B?__ hy?oy5?3z{z Uoa~~~gb.!//佘c='=Ƚw~5|Ћjzz/ { ho޵ }qU߾~ =/y1@_Gߕ[LZV\^5^kE\/)̞@{hikio!>*Ҟ} .:z-Z O_걮>K뵾~^+ ߯ #C/=ؿ=s};}RYxه+Vc}Zz+z+++u=Dׇvcu\ףuWW(_zFZ>e Ut*RzZ{+I>KQO`(Kb aևau <;U9GWBY TutS'y h$c hP$%.DB:) HJ!14(+e/}=3=3'E>[dEFGPd"=È5R:hiB񌜾PYQ`ERgMMUDQJDWUk #{I둸>^^I>(Q+QHԴ A 0H ߀F: - _`5H@\u=4OV#?#PHit)(QexzzRY%K]AXZ-iЊ5k9jo=6^wT֯`+Y-LvQA?c=Q=~Q qxzn3OYXѷĽknfma&겥6鳓>hN>kB~H0|w ``(t3q/oF[V뵵f궣0Fmn:]{!Ii0mvw CWpUw=?y'$.Xӽ[6ǖq5Nc>~!3S\f+8~1cacƚxr3c}8Ej3y'sM媫tZ~0 p݄,#X$nOư72Wv<ˈ}1}f,kR/p Lt[#.v\,f./;sb5r>0 >~o [` p ]BAA$WLg`rmgzt΋۹{{{7zgz?;g&x# 8y ^J/ڠhpKޓ/~S13|Otp<D 𳁢?/=!^ &R~ʛYy+,兪0 2cEF~P.!0x7>4~<ҏV*H|WK;%y>^/Տ| >UKfN`D<χïeE{Rtd?wŀ>5GJ+~|״)E+z8^'D>WAXzv^?uKDDƟW_0^.ߞ׺^־-CÏq?dQGOD@}ޏ\[ү9}՟i?34O7}{_q={C{{w?{"r?ϻ =ѧ%YӗIX1/C _eo߉{ uYuWꞬ~i~޾l@6gF_cLl-|}؋܋w}l클 ]x4r nJmO^Hg4fDOvbIo鿠ދ껰ދ-{;e_w1!gMў ^ Ξ>z&*gkу%s=o]su}Ww}w=WkQ`_ "aBSS^OLĬ_] y)磰, u 鷖^鳤곰>k뵾^~K>++`[_+z0]axE/~^ IT_$O@Rz@J ?Vt*ӛHQ7"MT*|5(l]Ek6:;y,R/=7};ɽ; ~/y~;FO)zzMRa$u #3YiTF#+k"Ê"QDb3 4T6?Ff[)='R//+M~x>j |fS(?5 3m Ե#׃@`X8'l~);l*zVQg {ި.Q]xݾ`/,? 7|noF[J:pz[{,y4*xw{jx8}ޛ ߇wx .m~b5X+>M/4E7c%%YʃaCG_pnㆼϞ\ʻ˜GҙЛ[EC -c_~ȐXOnz囊k>}w~qvhcXlҹ6ӡF= ~*y|Ν'U,ctO<É>0n]bQ\5N1*_5 }7ݰ3GzA?1)|Ŋ eL7/lcs6A^ZF#xx~1c-u|:?1@\ȋ#余\ߖ2U2]NC@_] ۙ |,'LS,W+̹<3c|n6_8N%j nzr=E$ly C~gww(x*< 4'bor^K' 80*!?aT=I?6Ol~4?+M$@lh zҥz˰l['/*g/|wFVG!=j1~ QLʩzE?oo~Y LBo@!BÏf $7B=} ?f)"L'Lq*>lW%otH zubF +1}?R+T_)1%~~^ʿSX̧_dPGOT? A$>kz;8[{7MH| `|lWIDt=?/]>n/`}ط[_ |?w obE?iy~:C7w ick~_E~ޟ=ޯ}g3=gczGz/{3?S{4G7|ބ =1s9s7os_6]so5Os$Gq׉~zvkHgP_VcY?cal_6e}Obڛr +im;ݾ~Bg]j㙩g6چR;fKObTV-p0bv5;f/ќ} >#Ѭ|sdhAw muFYJgFdlŞ]{yW=ڃ=saY#jNbgұ+ǂ>zoh1Ǝ;:K )1ĒFS( N^Uϕ\\]cu]mE^}߅[Uv}Bxg)^@Ш_Eg~ut_Iy)-4V^M?u9QW@\SUZ^k뵾^+~K~_k2*2z U4-YhA iNbNi˲ʊ+kj),eP _+dW >>+8jJz X !VIRziO+!:j7+ѪPuVUv:j)}~ ¾jJ.| *@_ F1M'ji4aH=A*jP%6z/rQYAi=uzz #މ^Ԟ (_jFLR/S$4A@\64?4]-TaJcꚎ붥͖, v֒TJc7)t6=JiϤNkQp/URz?%ROtE"]=B Yj$HX@:֣H]["}da@$~C*b:BHm6:r;…I^⾉ 8IXF^Q"q@\6R" ( {hlˢ,B(m8׈&#jk S $7ECuQ#ٽR/=3)>/~G@cC>ds)ȏ3~6ڗ>l3=sa=znG0xp[z5{?{GB$O|$?"F96嶑v/x}_~7rggs.\nuO4&g_or|Ε%_O9Aen5`[:xr>L?y"91C/DFЫHUKͳaL 7 m4Eʒ 5P< \yt߉Hy+UGOX/˗iy+-剪<"(8|Xo9qx1>>ȏ5_D^'"O&SZ#+= &~&/Q-V(oly.>oxR<Љ{nG.}##v= } #TT߰/7˭>PF^}L3KK+. j}q8KlWIC˿!GWã\z@z={<[|䋉|"BK'_r'_n(&I DH~/*|W=>]kkO0_510-_+ ߨ ߇AW|HvQOwq\&.<\=\]~~ρ߯&_=~'3_>~E~<m3;[z9 Zlf϶޻~[uwwݻ{(x#EhG`=[ޏVW]w9)g#<쌾!ӿA[ճeק}ګܫZI>}r?E{7{9s< 7\`rBc %G\GrF !}}[zg faoVgjvN_6gڮfJu n"6|WJ)/)HeCjR]J})eaG ދ"[.lˠJ; j'>r0CɽS}[_-U[>6M_orFw4Y8EتMdw6t!S}Lj1}am%ÖZJ)'&RtMxR|?TOT?UQ# NjT&Icg4 Hho@RI$Dq^;2{h#>I j&R /x^M0Z En(z{QܠDrSѭHTzCx &aB}s}I@ STGJLτ:ҋG*̽?r[NixCwYܽʃ(ޖ<{3^&w8U/p poZq 3>U h)`>#ex$d㋚|) d]]  q[ /vc;5XG.KSn@c>uP~ч'rB@"leÉ|3y?8GxS.wn_6bkVwI^=j+Q<9޺Gn+8/KdoL9+csx7x.7>]kԩNiՏp~. wfϡCw =7rOqp߫a=61%QXa76? 5<ª'"^ëxs^M8W,xXnMouֱ< #PxO+vr{X3V9|ǏXq俅7>2J&\Cj)/Me̳8?i1X}|e Y7& $d 7'[ȧ 2[rY3 &+=Ȼ&P6a)Wle'c[qQ 92~Ռ9%wsl8y;06Vkl4Wɻ.h`50 H0aN}I9O>7[M]KH˻')8 򜇼8`aVΥt|,|{{zoz?zzIGp<_eO߁7kx< :^ӧgax( o$ >?}ЈoXذ ? ^5I/~8hY=\_uM>47޲%oKx0~U_x7fՌwH.+_'=(w&=&Sf/}#~1_<|_ăEBWNQ3??YI > ~P~ @`x$bOIIJH '}[;A/iJWOew,;|!73?12-2_+0,ߨ(gyǡ_慨nM/pc!z=.D JT(]C]k"%Wٮ. vC%:bpBI9!`}YkX~˛.jJo2vՁuO/D{7REcBW88 <\$k!D ߻Ob *V`q be(n4%nSPyև7a\nZnŁəS(cT?.?34LEl1[Fx!̰y&Z `aC/@ÁIE#8Ї 27v~OaT}"8W9>P9` `jWZ S娯(a@[>Rx g ?|eTaaLVm81y'Dr~l?|Iu!p"$@LW6Ďnj(CѰ]VU3`V{!@|0L/gRW!T6Ay fU0[@@SHċ-SSH@bG// :@">T);n>A0 /v̗lz"}ݚZ`sģ3j3 AY!^턏.LB!%c /-?D9Ca& '(L5_̇M^2P9Dز41<ͿvwJ0nbXhqۇ!{r8f[0 :AubrLKbD"nBG-^ 0ghQgu[Qf.ЌѸ ڢ~l0GDP`MK `9@<Hc.j8Ar7WciYY"j1,'!_N ^ lՃ 3MO C0XrԾޟ|NfJ .PVy53jI;d?/.loViBC&u ?]u pC#FS%%?ԃa?J9d[|G9hr=dk$`n@#1Zj@B(Ww<Ѣ8S+>^t0bAxR?<݀/=w(4D¶ǽJ:PN0BBu:h !u< <qGо ،פS/2{8 p!?AHL @w} ޜ_{_x58hf| k!Ph@5{5|VO_M}ߌ?o_¾NM | <1X^rAr|3AՄPA R0p(مpw }~ F/w=2}~aWߥҷ91`#ʬf2\_J$4!zzdgQe }A!A `14 ht 0m3+!Qaùs1qܝ]~W~J|O~yZp4U14Q{)@6tn^ n (9 l{15Hjo?hDw : }Wkt5Ϡ#u|9@=%=:=oIq/3FOnɱ+c^!_%Z|!B?*B.M7fw.YB{~}_Co=E??O uΑvYۥ8XtyYO 2y@&I68A$Y Gmq@f:o"E'<3??MԅmѬ# W-IWv92'vR;G8;N|F9]p#ߜ wq-7?_נ-=SmJ=C$9-lln!wul>n!wpxqh6FK"搓ٹEof5ĶFlthh>wq9*1t%8ct,Ѓ3]3tL,.бYLL\H1 geV=7olr')q6NS00 n].3Jp*]  r%@_/iaws&~H(# WŢ͢•6Yp{xA"kkMq-a=l; RuN-,n08xV͢tg,9VUEVsIU"ZYWD+RP)P)YAY,J̽@,na;xV%"h?O?9'''N(+jeAPPPV@Md],e~=nogf9 4߬?)7))9r~܃ .ZZ;9 845ħ;xdQbPmeږi[tؤ]m}j`SRw59[mOj6m-6MFaQ mPX)>%Q['!k۔cm^ԶMt[H0(AutV< VNjns6kmYjM{N{O('B!&:9['-g4l-eځb E ][ɶ%*|۠;a[n!mYicxf,!B;;vHotVIGnr)lr6CN]a9\ ^V@+*OA#gNS6 H&X{'.&̅y\ =+T~6Dn1\LWk)vXڱ./%bH;vfE{fpՈMHnԖglv<_^VъBOgN+pN*) ވMTƤ@! fZpuzBT5'L7nLn%`wdgB7L &Q' UCˑx#30 TeV.8uκx5. i-K8,YyrPGUF7k`0ID i8qPU"6= 8GW9L0oe,Nlem`3-̈8FvÁ8Ѕp K:|P_ Dj 3D;[Z$@+!`9dx#%< 4qq'prSA3v]*< H948r\C)Gf\E!(ӕ k0JظC")hh 1OsG Ij@XՇ"Ҡ8ŚJ <8*h4֢+4|²ITDJbQ!DEc2Xp"hJxbOz4PUXes!GBTQd ؓ.))gr@IH pP>$V#[+vIAjZL`J.1'cT&THtZwdf9|x0r'IS,BaK\“&2D;LNdb'\+8)2p :y@idO&ALG4(Qʼn@|*`e& Ǚ"!`9`6$gr%䃏p٨^tV kMjܽ%QD)a "N. bPD2Dc0H0ehz OD ;(W18{:<CDVd H`PĂzhnT"͵*-nފ8"N 0" &$,!)UR H#)yP!p$l(8FW gK2̂U+ Q9XbI)z4V#̵.%.ނ[p@Wx@S5*I6]4XÓa P(Dy Zj$uId(b,hEg> ;@gתpQ+t$е&MթpFb[o.u,n0qMd.;HP,2 GBGYd*;s,j$h4QkPE x/@bAAx:hbRթtJkWJv[co)0)G! 4YXD$J @2! >+w%4ȅzY-3YDR{+"B!3lzXR){ͽ295*p6F"H%}q $Q&g fQbD a+<xB݉J2qh:;׌M&ЕX RPjOX8;['6tQ{eܫ^3m:B[G)tX%oxB,k ~128LC1AP /Fu* +1Zcp5f@HS]NU;]s5bs2tirQ{[U¶]6 "{S823#8A5}P ;Bx@Cn6 B6 Ӓ AM\"4й;ZCE97GJpUx;g+BO9*`8#[آ$P $ ,A,(p`Tac)G}ܤ%R\ \͡:ܜ*V-Q_f۷ {Az"9W=̼PV&~oČG /qXnZ&Iu"|\%ȵ&@dqw6(n`n;^y"G˜ x&1 T[˲^Qփҷ[:C J\p7iOq-1nx)pxZ{'ޑaLZإփv:s\]v j9,#z^[}o6qpχ\-ѪUnX:8, ZKҭtj@ZmVt%@rS-VJrVA J[ {ɷL~7Hmp}pm^j` V.Ά +kł@n2US斊 N[u!F[м][ApAlC<_|ns:Q- U V)U pkVMU-SUVIj0"zmԌ}JQM"B" j[2:^uWű~jlO"\.nS>-S;jkcX;ba;dWNՂFm]m@ n߂_@ģQ}.7&Ddnl0Tb5 V9.Vr \-[>o 棶b=d[h;[˦SrW&h YiR<Oșź f.L ,%$cR it;q~0b\fDx&$kAkSs=_~9SKxmERC0x25oEP~e`2dY L6  T,S9}Y19.ۅuS8$3< >Vqj#Pi=w6(T|5Kt9NV8eZ`5 fZYe- jfvUi pT)IY@17zN}o"A`# <Pf@j‰wXݚƃ T-d@mlذY#@12"MaHoa8B"/ uB&|Y a)0H!<@D:AS @*`l J#qr+D ՈWE/7_ ;c"/wH70}a:4Yt{,yvx YxQY9xRK!@;bs;Md1Ht UhX5r02͔=+RHs1%pŔBDT I8y^ԻĒI- iĴ+sYA~: >iE=T̓sxH>$o"0BIZ9hH`%#;F@ؘ1JB&O>$-R1FheCXt^|4AUFUÙ.1ϖb"Y攌I:{+'X5!cK1֚tbH{ezCJMO(5},Դ2S JI!%J 3J\Euh QU,|](BXI*`@T9䀞'd%JčC6XZ:ԸoFQ0±( J6@z&ħTO%*6`ice 4QȡXj!h "}J"aDQK 1WvC\PKX vsUC:^/liq S]a&t "Z:[t2鄱)se0DxT%X”H$~)M(K 3Xx? Xj+ z<AS2 bK$רTa 0tʀ$s4: Ȝ'LE4w\19Sj#BDbgʅ1Bi6@ ~xÄ-CbQ{e&˽b@4oѼBf2uTc<6PФR 3F$oh*y|D1ŧBSWBKeB0g#(`v ձb9L)tN{`q,}`%E7B$G,)d3H0@*lلSg`ZI\Bhߨ(;NM0D`\aIj4BO9LWȽj(\4ҷGH&9D!c'0B\8 cJ)W$ws}mszׂ:UqWh#?wh^x,٩!Kc`Qb.2\"`q,.Q߆ PܚmPٶm\Tm_^J,G`x1:J/Ue;@)OAӍrb`M$bA0i.]`3%UK[6}39 k{7D|@^+aw6ʴ֢5OśZ*cX%zeWDJǞU4*6El:-Cap9(õsXiX[v ɶ} Y7/ סE.qFeur+jЃc5qi M!/DlP5J cx|%sd+) 1-Dh&A3mج9$ԡ .* ze#0 sA/GX7$UQm#x^0Ԟ,`\H?9^|哨@n 6@ `0:Ѹ@n}s%.| -ijH 1 3 /)7 L6PA<8aM? ,y ut9/ās^,4v'`Bu (@%:&%A p|* 4E`4fʸbhEQ4ccJDM.^BØQ wFF;xh`[sH):V th3-$؉XHR!r 8.KE0QR(c%*@^pR9) eqSJaeR?G,u[^~7f'Idd@@ (2 JBՏF+S.Jr)ٔ(AS NOIPpzzBSSXLOQ(* T.PLUY#2EDMH%s@[mȘ&jܦaBaZ$)9Dg\-i$ijE)1=f#># h[bR@ql9R,6=b)NY.]xzLXJ"Ok,UoluC(AflVݩ::D7GC`p(4D2)¤$f)EVDy"jR (.C&h$c9#L4 ^ W5P# y6L' M X.cu:>WGJrs04H'n wru7'-b LB7(@H(N`C $0E'm<B Z\IC1@)b7 zGbBnhm.p9n @8  ȁ`l-(4/7}fS㢨4ƙ#o;"wM嚥ܰQ\,'bD-t:קrywGps4$RP@F!A, Líp ŅmHW ㆰc fj\{ȹ>s8WH,s}[ l%c p.1+epä. b]6WkEb.B!>=Y%npnΔ FN.UZY+ f v 魇!z۰ %*?`泵p } UMnb%>f֎9L j|-V1LZL`2X4veBDY93#;Wcd'Vb>nzA.5Vx !n-3 DCZA5i40d4Mقk3?ā6'ݕp(p]꘸ &O=&0!Ԅ4OCTQkl e Xr&jX3t̄D_&2͑}Θ,\)IxcXttPwxv^:3qpb@?nx%nXr)hל&>hd "LJ}pKyz"GwxRwQҀ"Y4hB(r:HʼF: #E]..t!/ hA͙}p ڱVLun`f/D}_>mȹ0"9LT)HCL&^9 t6 \1X0 a` F"k=*H@ 6ė-%h 4 eD-#V,vҒ 'dPϬ PL [r4q:k OX$XP \HPvk=rW+D pJa/OSaqceJ :֊bL;iTmj"P0BQFI-\8sMJ`fBI99!z\qewFK4g H '+ W/ X5HQ EdL6bM;jJTrP`Ǥ 1;"]H!قȎ<2U_4RS!ER77db1>D+IPp'[i,ykL:YXHV5k {u%Խ6u06M@ Cs54aq ʌFfpAS #PQ#|BE%E<%,K*7203OXvɹJ9z^LĹ#,X  @ VXHBԅ4`H=5Q()3g+9.,iٔ~ dunlNԊ́WU6`D5&PcpA5 j !&T@"2PUWq)bKNL+}P t$epId}SB[ʞB~O׍r:PWGCD=>Bڂ (^2Iyd1%wHpF(d#"HAa) Zc:Xݑ2]́\M2b)"(`dMV|ʥJ0DN.1BÀ5yzQ4. a:iC5C&5[4c<؞ 3Ebu0A4rA *`iQR8 y%I-Y&xBfF((Hp$E@su2<8KA(T&6kDFc\lS5bw,7r)H: R hVH+pb0'81)LD!By8ep`rh~0wɂ1NXc*;6c`׊B]ݹܜ(ˍ%[89`"R 0u`13 ja|`>HR I00)=6SQ0\ZاX-͉6_$EP$s B"N׈^`@|ỲLV =l2 BdK6=2G0oU)}P6b;X+=bRPrs0Pn 8F!>$vPYeKxنQD%( |cS=qP,OU_CIi&HtDG sw6<phbBE B 101"0h"@)"3bz>2澩\4RݮFӵC](UZ3]" s{*w`7(43@􀰃 E0 (1 |R$7O ܸbsCܰ*҅#Jlka,t6Krw84nNKaЂ@5AЃEoH\o+Ӹx^J5ܸ"۶r``.ى\-Э"Px"V-t-ynxxܝzo9k:4!7x¹Cp$.ݙŝӸqX !ܴkFrR\Ͳ\CЅ\εBs*ǫqwQ@@ Up0\95‰Y\,&qՔ@Y%1a**)r\*΅\%͵pAb;Yr], ႉR"[J˥]I! r p?ߎmYQܞYX5"}[:MQkzZ{#Pkn<#D+pc?`xaQ+UbǠD,T=]nEsZ\&ecܷS:3"?5 ZǞq+ҞY}n"[L!lGM`c65m2]7+х8ʝr: o n vIl%vkGw2lhaAi|"姄)nc_ ĔSqaAV. }u_{Ub84<`qPPMIBh`ex!&F՚;F0"E @ʨYy,'yd$"_>wD#BM-M#h @1sLCI @+(@UM/`rjRm}ʴ(` 1ldG<֊bIP0Ưt3}& PӇ>S(D4%\1$B͢5Xb^ 顢| pダGXH.r,/=[`  KXV^8֤{p& 2"XWE/.xlA */*\|aDqL)i1eF-%8Ѹ8Br!Pd̮)VnxI2 ϖX5`U6֨(ku 0d(QTDHA!F!(Rg(.HrJJg P,#DdnAݜ( vief"@K t΀sEԒB+^JWEA"W9À#8 a!H#"G m829E)x0m2SSTxV^Aa"#yfw e @46=^`J0eslbL$V9.Y,F>,w1ɃG|XCӆ 2TiHTKx.z %e$!Zɢw̖T`?`ZNz:VC6'Ć:W&U O A af$6:wFQ;N_BiElf*9"c)|\kPng28:"BPXz cuN4*h!.D 全%/FD2PkF*+Jw0LjM"D2)8at 6/t+#A0(i)aHLJ.$cu`STMWPaJ<XQˊNVℎ,T؀E U4VOm81BGJ4-LidJm\Ƃ)8.,YqCt(<vT@bM#6'N`@c(~|9=Xձ# 3VЈBǪ@N82&,I*BQ0.Taaä&Mؒ0d{6؝ՑݫM}  X!UiJS)/z)!CT6j*,(ф4NM]'\lr  >eJ NkOT0"CA^@ݙZ;S#6IĢ H_@452|ʨ JFsB %Āq LPB:bNNb + lL! |5ń'%qʌlɢ4H+@^)ˉ4Yc.`<AJ N`^@:S` £ T MS"qkߍ gcdlԅb}D,'^<7h2‚2( "`QT5܀KO0AD)&"op@&T Mm,"Rs2Iؠ*džgU,*uӨJԈ]\6u0o7ET9ɭߒ&bڰ>RKBiU\V7PU 'dݼ웇Au`s](tDVeY:?+[D ntJaikMvuimXNJDdH/5 8 >^"3%?#1Aai G[T(zHOR ,3=/@cq`C΃ _q ,bcc Dav0HX´6=YR*H@ `3ɨR`#Q (QԀH`LrFT!8 ipe1F 0HcA)4\ĹF!k^P1bX/:ʌ!1EL#a sxF)H(%"Ԍ HI)Ht\ $ۋ \c4aX xrl!cK)Nu S(1BȎ*=`Xԑ f ,0DrefpL* @(r| x@$`- ΰe,&#cuZu)uo!2C#D,4BE d#J$PR)DƒL*|8}2:c,:BCTK1K \+Xhc%< Cdut{eB8G1A.X b$ #=l!4*Q& URvRb)% !.v 0vwVrhдVf-*}Eٞ"Cucw4+Ӊ͡46>ѹKN%Kr|&C$8J!zGM&AH+,|rE3婙`92'h Y#ubL&>52&&[dXD!3La*~xD2DMhFRᲨ%*P&5RS!KLXHHzQ @Zء !3Jq$YCf-M46V1`i|a:R[8Μ,Q=V/tsP,v ʂHKXR5$49qȍ{,R#hxEwUP{LhIHLu؞+SU9N;À8Ԍ䌈H„HED$PF 9bdJ8}Ej\VMe9 ͑SSJ*SZ@dҎl؏ 겱>X+NJDBy ԇ4@$ #T@QdHS~ qEG.3VB5c Yl+ UqJ)Ah"O!3 $cM$Bd.12FSJ,V8'=DǍ84F4Au`)=G#%&yd䎬Z=۱Bd"'efd.ɏ* )Se0Add !J@  `\ qzANX;$Ok>h yx?Y&jtsJWEOZ(`)FWv ș1@TŒO.q@D!P1PhR#vp7C dێE"C^-GtyF7tSI-1scc8$lll@FH)O Nwj ,TID|yT'L`DNk)cm,`ŲӱPm56 }.P\؜'k:A#TPm``l@G RD^`d%Xn@IfG8A *62JATEĊth2bX(7dbP,Ps%9P;jb  8  0V`Qg";eP+!xBaS& D/H,Ӆ E!*U"sQqtL7syB7*B:18`D 4X SM# K #i¤"T3E$px1v&u.SKQbn*X H7sy6nNԈbfR)LtCh 3 s2hZ]mQupP+紷uMsĶ}Je;xDY; DT#(y/`!Y>9 ch,ۋ tSm't B7msCP#N-$6a?|6ɕ(*s5<0 ~KT$'P 8XC?ؗ{%1oBub1ng S]42p$ 5 e!{I%?&l+!d#gآ&8. 8M% pBᐘb- rpnh  M`8` cJ&5Y+B_ I@pԇ#p\p(*#EP4vrY!lJvhIBiac }%k# CNpA-D^FQ`X쵉^hl\PQ[Yp*b4b'N@0YtQQ >?95 IG^zo"iultAKe,( V`\5<{ csI3C= p,OiZ#b`p% +Wd^yR Ut@!EFMT숁 CL&*Wd6~Ichx&ri4wf,9/)0#ȓKB&c&<ޘ6ZGdKVC@cyRlNӊ52&*/)T`IUBe+IVcU.n8qG5)cI@D%4Y8Lr]0e֢,Y?f,OU"dLXń *YN]\bƕ%(lT CJ>`1$ɊJڈGDƜf2YY%\4"BlۓM";C^=CJݜ&; %`LgLBƕ+989Q*)'Hc%j E.S[a$&f?KJX3[bȇcf,ދ5[x(6tFqs!2wMr1oG|ӭ]:L vP>Qje g5"%:iCبCnaIL\=a1F IU]Os7&W.qM6E}9ԖH Q8劤JW֪$dEd  Dz E8f~yy%%$t2j p)` +Vf8F6 ɬ.?6@cErkT7ק&{c4 ")r`q~hfueHm !GhkX'[K\0F 1EnG /3tвCF@r8)-"Ĉ%;8XiaE H1P*oPl|hxՅ3eB@Y lV*YXB 1N\9"cE2\xQc7pTII G0g-.0) //KveΜl"c{^lSEu*+8Q]E0@ZE-0fF'<!b0/4)e Lj/;.KVeΘlQ ڲ=VSxbDG!*vxѱc EX)GJl!j̋'VbK_Jp؝LgK(, q؞+RŊ)0PJmyr"(xȖ+Xq" 4)#LCxdU,`òUpQ -Y!.sbs`,-$p\2K<@)-%i.@L4т#UX刉gz(EF5l0)iE@+'^gel5 B]3œ."|T" !_X )\VUK)T!J h8  @A#(L{`Z#7L֭~sWrI)M!.jus\,0PtRKe+W.TyY& 3M pd 5NB/$8EšA"8jI2ɺ.|KJ>Y$8vZݜ[%`*@2) K4KdD "D> c8p2C TWW- F:H`Oȶ}Gk>X$6vk`$#c"cI,y& 0⹀ 1 %*8}BQU X0!S {?-Q~#XtBjm&X/R9P,RLCL8X2`c 3d> ',xNCT$4aU#H80]XEM"D`(g<ёfRb*S.$H6%X1ēTAlCwc{@UcnlRً%pb}6nխpb:ػ$kunWmee1 Be̠Nc۩aK-p՟͑SCwtA|g0luB? hVbH \Bk6`o!mA5mksHm+(e0jrl]u,Nu P&Ƒ"\2q#N5ev.EJQd lh#:P3QwPxx㍡ ]HBT8+8Ɓ|kwu+ GiyBLnal !  Tx2 s\4ၡ1ex"Fl)SEDA2JGm2;x^^mmLPL19, k6 Y:"0D!i MYPHBHH82TLOQ<59?Ai)j *Ӌ#.H kr 9RMdk0 e@`j.Rd  `@'g"HhVPR `,4A O` 7 l { =_uewlaƐH 3B\Yч $y1fF0:(U@ d6jcP&|$l k$p>yeyl1ʍ.P\tat#S0A>ݱ\l@ `vp3sx6g@ CaYkX؜ȕF.;,nQQK +XNTV!͢%K(N0E4˨q9@ Au@ `7 `.Opܚ¼ VeUyұ]P S$ʤP(ôr.TBR:sK*M01\ac&(@ ӳ< MSZ8FbQ6\؜ JN2Mp ⤳ 'cZ@*K*VFxeGM1FrTⱀ08(u}t0vf82oҲ'+A\h&BB92"GHu %^PB" (VD,+"8 d`2l “ڕ=d:Y,c(_߂@p 8U O> 7"l6 Y <yb?7aGcOAC }I_@ԧz4^_}N:x| | ~CW;#x =`끜ɁYwz^ l1_}L6h8p? lfL|c &Fh5CN ejVi~JcQ@zLӓz0Y쵠O_I!>V</#u$Ywu(=0fi_&Ҵ@%8/>a.".R+uzK9Z SA(s(2yJd&r։H]"u"ej$ ՕF*(WV(X(.^p0(ւ-$웄%p :L Cǯy(?8,=C Bx" ӅO"[<7p AGD"`"aW" yEȬqEF+E"v"j#a { wp ӧ0ljGG`4W 6$%W`A:p‡Yfh:9rt]tYtYCKX:vҁy[sįq3F|wW#OBs~!RAfaWY+ŘQdFLZhBhC-Ԃ:]LxQ|#NG] =b.C$8[*WqQyS&z+m 5-viH')U$DT&ؐ^p3:5gĜ:Fx>z<~}:w)sR^CZTRU)EC) ?SC?5Se _ȋ]Qъ(\Ҋ7 F1dҏrbK21jg$:gto/DO` ^ibZ F+oCʍL1v~?e7Tȏ.y*_)8Z;SZN7uNp9siiFgޅN>~F$?A C}BwDsDH^DTZ;jI">1EQ4Ɠ"3|7 Z%%Z1`eTzPS,X5Ѱ[R T"$<4,L$j\ 2tیԁ뼼ujZ<=}ɓTP/ A< H)62IH " E$RF$! I HFDB@=z;r9iְLs$S&C)>wxO"1dO$2@äm4Lp :`$JHeD&NB!0%-D>V)B^ FjIvMQUUib ).l4kyL(Oɼa&2"m&.2bb(,PHt :6@\28$"HS"gP!_(ϝ㨎ۣ CR-652S y%O9#b?pHkKq B |21J !$A'M0-Pb&juvU~9"L$ѓxxzƨ*G'5☇CyI'y ^4Z?NTھ&6|dϳ)(S > |#NH3-;%3! dOŎ 6Bֱg؉BJ0'^/o.y< VOhD6O4iD:Ip'i݈&)wLDJrܕPri^jg*ѥު^ D>>}3vn Lek 'd^pAu|m] [WhJ-] N.;e:#gb HRxȩ&ɘ[qbQ :Dڬ!DiW!ގ<9tzBi{Gc򔞔򲬣ߗYq$-*©(Z8G)PmUIM[ %-m/@ &"n6,xADM(L8?qAz[rm{X]趜4'f9Sy<^?mЮZ5D64F 8J 4FQvAahAV 2FhTȏZ˵%!}GlTvvnY-,`Gݿq~.·X=+8_PT![Wv~S`x6<`FBRD|*y7m6ooR QIKސ#H$| ?Eu'tF*g~!-z _@5a/ڛ{0e~dF > t%2R59/2Bsa04D;]@EUPJPi>_}¿"P>u0Ⱥ]EG@[(FѤ ]@҇ (X ; M;഑OaL3`ؾ g!|*~Et@+ d` su Vf4XSLYAP-ij2W:̆pLhaGcaWCy%o􁬾OCȷxRzvUϛ*X;\ٰS'W8𸒵zeKJWNW+_V+zWĎb+dR6+fcryLI})>](VtZ#R8AH$( E%_A/|)O2%f;Kr]YᖸYϖ Dn%t;-h+#O>/|(?;ZbG);ڠ|@cH2>̊L@#d[!> t:u@mAdA 'p|m "ȫ`WҾ@/ЖRqA&42Κ@c"Cmi 6%3ܐD̈+ʤAGINF <;q7snZp O8'7,0d ȣFy.Ҧ1m nPh@-[UrM)T-K\w$gaqPh _ž::l.d/]0&qыu\|~ oWq !ɓZ1Ӗ5miӖA)AT9)+taaSi%S@JF O XC*sTEIAS2>E?qM| #hP ðByTkB󨮄M ݓ!ڗaU&K# GIIDR̙br^NAPGwB5}ğ *M*'<M)贛P!&T~VlYeB8csPC B OL$?!A'C-D5Dt!#%r8 [ ]1Ǘ'3<OA^:!^fXB*6WX^m$RRiHQꅥ"P#0!61%/PfV&5jbnnqzy}ꘅZ;.%(7^DC<'< |M+贒P+a%FLZ 2 *S1"!3JJBNV:uR^.YRj&9Vv!Z^$yƙz\3I6FwZ//9'B}"0cd:F>#$u|"Fǫ/VN!0NNh̠xUȹ  z(9japTg2uG s c,M/e'So*(+#oXMhQX)&*ɫ0VP- bN@̰u$(ʤ8BiccGB~)~$Ww&a\ATְfYAG_Zy./yA >o=dM=܄ZM n 5( ̴"CY%B - ED0u5,e<,qcbj⢊@F@x0xkErIJCM^Ojy08'6^Х mЧ pB hUgC:8*DP)QJO0^$T$Yh DbЖ9ڲ'D[ł dR>R,ƌi`e 1/CC  #DclL 2@((CYҌ\ҡM` B턛v#}mQۃ"Y0-@ h4— ?R$Zī^Jgk_쌂A 6AN r 4}`u# Yg^DdQ֙ g=x4R,vd`JV;M&)E, tnda#+լ tJhuB9\m](XVKl۟v$~a ltj@gYtPM9 LmSf,SD!'!S""!T 9-LA1uB)՛N*8Ȫ^5oZ۴*ֵgy5xkg 5zYBYkYkY٫^UQh}Ck}+Z+Z=45+Z9h,JuWa_ji?ihhg_g~~پ}} kiuNڞhg>1hz gjތw|*$~ p+ : 9љNT9, mh (F'$t…0=Q3 5o/ߓI|.Oe!7 lV#SD WPthEG`4G}tJupa0A8xZi6NRGטpo$˟"◤!40Y5JF&+ 9$ j0, !Z/S=@T{pj+njj \:|xL܋{/?D6xࠩ a+5jt KF9iiix u` a0@'4 .B36ϙb kA*!/ǎa_L!Z,[Cek(.tKJADxD>]x*,v)rvff=6XE!.s[×"M<I{CƓGeɇe!)NIl%%, $#O@ xBfPІ2=Fi9En.J]=)}SI;)J>Q߉+|#$>QoxJ^'%ô?B438@̞1{:`T!! 9s1$I $%q3,BGXNhЈϞ>#:jGbHZHB8ё?BCD#2<(?@-!N 6 llM!pcmJ 6)$74a)&xK:uAtǢGS;>>`6n !߂ɣcS JaW禖\,hUsV:ZQp~O0 c*!84 8GH$O ˜ 7>o!oϘ1z| $*KpO4?A57&D&p.ˉ,%'68w_ R"R 1h8|TU\ZTјD"P] L6:c/D[0Ht"Ż,&oʏ#4pjI }\qy!bB4C W(J FM5p` ~fMܐ"ъ㔧I #*G1Il&~陊;-A3a" UP8GAO4U~'l4,'#r.^b $< FME Z569،2夤̀  ٤ 4(:ˆoD R՜.Nit@D7YTg ~ЍŌKȫrc؁JFd*;H|RuÒêe&(&ϨIM&F G~&BUDa"ͱy&?ZE|</հS;iiNp3@Rvɡ$畉IOQP #*-I89^XB<JSNY +DyHtɋJy, 5I|rSj!7Qs̮ 1;a`Q#֎O0CRGTx$! iS)rŒ"A0ci9q) I&{vyN2!rTrX(@ Ӹ9<"/>s˴{4vȰy4LqUrXaQU;R8hx'C<"P%8%Yc4HpP;.:ic!RpСz _֮h:O[ s@6 2E.rE`D. sKj5yjA E]&vT.YARE/4ְ `&6UP7Oe>}yn?kmU;4pIE\}g@ |4x&/,Ճ&=V-c5? d-BzqۢEl48MO F# Uﴹz ^w5loXpH8it]E(]eYd,H“UFRlbTf A"@P~7^4 wEp"dY 2P.Р)*L*V9\]7-p} JXU*Q5_?I" i^cK[!}'*ވ!q8Z5v 8d`֏lyA`' \?8Q=Cψv0xXdW=̬qY H `{p{GeLo|*$@~|%`L40$0c ;1F̈Ř1 c̆8ǜIQIHmL\;2zS;iL=bva|,?E7AGb<Ϡa=m2Bf UȢT)bR G3'UҘN]Ti% Qᕻ+z>uXvذ^ǰZ eq",o>7DƓ_hX"jR[DmjQ1f%4+0HԬ Yꁤm7 &E&sTd/ q <&?pMwΜ8^H^'8|%_dxAƒaOVGZpxӒVo[Nxe!!F=FӠ/J`b A4 Y+bˆ5OFF,d82 .OkFp06, qeB ; byQD 2dJ᷁FgB)Jo,:xcn?JE?RC2<+oB̋lX?귵}ܨ}@U/l(|`ATyPإPtp' ER.$aT6LқI$GC S/(U9VV6]L\w2pX¬E-AN,(L @RJ 9%`kbW Z' 2S)$S\fXT|"\y#:Ak0PXn7cyO^ŕ7GK];(" Fa86e!"#p2~ }JBrKD%ML+RQ'F00=KqBm 10Z.n@.< &ʣeÆFjm(dׂ>~ms "Y1#b:>%\Vz85*)rTttbiS`Iy*P1KQڥQ+<"ɫFT, G|/NKJasedF]ɔ$CI ^]$:A>=E`a~REȠ( =.,ȕs# kH=2 '[OXيKɼ|=ݰYɵ^p.3AxPyY, %&JM>*;?ȰR2 ,+atSIŚ%P2|dzs6 O-&DQzhc*FaUW? !l2Vkh@ı4QbK QD# M:sڵ)'T`Mֹ`d_kR[j cX-a ;`oX?_Llx G-=R *A(J԰!D&MP EnQsD3K.`Jds <fR js\ퟎtt`ozxC \Fl+E~IN1"Dç`K*LY  -qsyU$F&L)?;nnOaPd3-2vJ1Zrd;(X9XȣBBϊ^ #mњ`fGkҖ1"P5d$! HN:@2Bя2p)a6ULC0yB$ ?jۣ׶;no3&WRͭ nLmkuJ[+`(p>h`X, ` X `A  JN LJj|3 B'6HaXF39 W/^5Z֊lSa ]mABv 5BP6Ap BhyZ*\kpmP @2HEvOZs':jd{`Wfk2[TZ@kM n|v >q6qk`ɂ?Rg ڟXck}Z` a{*qkw:}+WZ@JlV`kе*٩xvj{N1@j{H">NڞRcj{RLmdVmX8)ګ^ֺZ6jwD- jyZ4Pjy@mOQ=CGЈZQc<vQjuVgjs85k]֪յV][n-ݿGۏ?]yjڡIkt&-Ko_9 >᧑0%UjfV٭FjW+ͱzq{ d[!}E6i{am:j^Z -A{u g}^Ob@ˉw2C%arX/Kd:誺FW=Rnp/XZOֶ`5[ XpvmaQ!1{ ށG|1?F|Np!N)8Iigo0@!r,OY<熃5΄-k ]Vmja~*l#BC{ _YPʋ?  ~V H HKtFZ>)8Av7C qY40evVkc# 9 =Fx'A) t@<axy(_E@`aD m > 6׀[p 큁y!?#`v 3&@uK£@HwHnYL_ʊ_bLFW=CVΪʙ,gWh9 @-hWBU6B[-rQ[$-)rCga`Þ4/9`_ W_(W9B⥨>_H8|0_MkqYA&lhaJ!M޸j4RmR<'{RO04Bt*bxOݔǠ6*"!+$^J3Fx<##h^\kyt_o̅iDDAĨ# x^*u$"uIHb#OH"/%,S t$Tǫ 9Q&.H2y4yX/Qsfam=766 TlGvF @8=@ܱ!cb ?0t& 1FĔ'++$P8<."m/AZ_b0l[~jS/H-Z0BLD G2"(gTb5М2CĒӢ(|~2c 9{ P~Fg!Q</צHT2R :@AȖ kHP@x"*بɯ],6]|Z!!0iS Gp6Vrڑc <,p5 /̗"~)$؞>m 28%>0Ut#4-Kb[R5h1JH8A5N(B"Sb)0\=bE Bh3nxaQdL֠5Ɩ_'>_ܯ%QʲF%BfQH%l>DTBX RϰRQ/MSRM*LUէBnƐ <`1Xf,hsbd9g-O^+bYSo]:+ݚt^Tb 5tDQ_#N*IQpp,UQux"Č v:V kzH/:+|E1H=+ɝp4l;?ɖ3mMη05`n=d vCSYMRb_]+ EJ$!"7EzT0nX . N 4<.#SZ nP uB)%ZJӳe(J3UB7W;dvȒa#LzCNrJe&U1`k1ME%6[`[YR0AHR2mT KDM" I2b.D\YCR3Ucg BD 9#'ĩ^vBqf SwFy9qY܅k'Std Lf'8XElWS)aTE/X<;Y$;0-\AbBDN Pd:Ui>0hpgrI܉:.؛i XSsBmዴ&1΄ 0ˑhc b1UF`%t}ik2Ԣ'AM*HwZB耍 >`"Q6 ys܁ w |66ц"Sᰆ5eYAfF{(,|H<ҁTQ'c(RV +$ar @G U(;n0 iCP.45>W Da_2Cʬ[xD$a9tA!)Y"\m&jd8&O;e\6/NmIS,}(q'ܑjl@$Z kS=& s"eJ=)`F|F_.[Rl( SJHFåaOj @9_X7Tfܗ$#BnԷV%Z>)U7kd.xq` -^bxb0]Cl+E#z\K35WN$H:4h/1Ev\X Ÿ+!FkE[+Sbػ.2v_8tbȕْ@.-4rhqS%EI d+t Tɨk`j]2IW%_HX h 9_{ݐ ke\kZ/حmV ahX85Sq * + pU@[ĴEPum*'nLq@7 2܀ EOr0 Gw& 9[{\Z$ jl 0-[NӮAGM m*ȰAH4%E-'%jlc&lmXQBuCIM#K%R۠3+b[+Z1ul =†`iClf013`ֆI37K_PXbfɉY Qb!$8ۣgkDm-l# OͭrTZ5UJmVd[hApb# X H'AA ap[1a%l=""F=֨-bb-( M--OpۜbJmV!ֺ!lݜ@^(XX7K oڢ :egmYi{p dkڠZAhC#l[; Cav(Eڡ%[;DdkxlZA\ alyV'|(k^%W.8,n 6lP 6 ZBdVh QZ&X+ddذA76lP ta<rk{;,O}1{0W.{k5_yWuzV:VkXNJ@V;dZ"}GKQ4B}&Z{w?{ ؾXDzBTٟ$ crV7(gtKtUܢ+]6 id'brk ;H[gA !>@@|,̈עOnpH< lMyJoFwI)3> Ҥh=2!lE *ƃ!|l31!JƀA('t>xy0gn >pgD_E/D<,|pzA9@ L0dc%P" iS5#I ,$J'(vj |0ze@JTC@:gFCH<(˯Xõ\ֺZy˚`ּ|YB򥭃/o\,i  3̞d(D1Lc0)+ses fD23zGJ'c8 %N 82 ,2$QtHk! uʞ)~[T9DV1Raʣʤ9-Ci[@yZ-.~ g"0VWA#["x!KHFش[KRNm3FL1 !fJDZ6dJTPdUa1TN%d0x e0S-{qЋӲc/NɎ7;^LC0Ĩű*LvpruH Ј(R%8%(̘rBdAN|$?$5/1lg CdƱX@ -1!.Ķ4HԀ i&̯H\ԸV [c;9#FX[QFv ,ư 8Nb#7z4pc$CH)}Z*|I4(#8z@0"1S>݋YP&=c$tup\kZ$z֤:ݭ ^Z+6 eO{\$rF/L-zc̠);wf Hb8Ҁ#j\ l4|`2q(F = $pܫɵ65UpK~k[ah-+ygܡ.͘vcgؕ;š-VyPC/{p8w(4=xz4d4Ƃc @k#:[{j\+Zꤷ qKxk+u tcx5 7 :l+[I4Rv\͸jE{$DD Ezv=e!qsapP}k`שͰshعvNj*؀>xۀCol 8`$*#qm~ȹ1wBtoRTpI{[ǷBs&;`!pR}{ՐW)ZӺmnDm[ 66S! Uh Ö-lp2Ha[Ò8QD>xۦU]*Jr{IV.o}Gou6 oTt{z놳eiHpZPM @-pOuNNжH2I[&jBmP-5t["D ړ[![uÁnXp{Z۫*pa,mY K[Ie*l8 [&v) .!ۥ,e$l/,) DV&0[$!ֈi[kV^bVl $&& JmVU l [2aElmE:"H?5bbJβz AhCaK*Bc DX>v!g mu6KmsEW. ֜k͉ל]Qܟ+ ] EW5m^v&kBY6UjNujMd'={ >7@˘Z$Y <=ю"H2SFm1[knzX+`Z0ӂ8i&ZwM >?@ϔ>e:'( 4dj,GJ\.UO^ڹ[`9d "kˮYf6kmw,k 8>䈛2"a(X,3eDe7YE6V˂""ݮ!\  ܱ8f=rqxVX![- tW,8xV pF38 +>շy"Rv8#x5˩ʘ X6aL4J1Qʒ4I8;$!(q6܄FM0rDj!$6`0uOSbV<dy>TmdT)X@VdUdPV%*%ځ 2^zqL]8= u]03vbV PntA> |ēY-6>OlyֵuX4$ Ҽ(Ik~$%7-qEj3)2(&$Ep 4``RV |NԀ>sfx*@ɒ'ruU¼X1od;ͬ2n!$("LNȼ G& Xj;02z8mQxƐ0Ũ>4KtXZX',͈땚ӓԤ#sx,@?ByjDgKmԭm6̽ApE9&V~Xcq%/-ŕX<~ԒqKc!*.Qx_<Œ 1Ef22!s /%/C|M׶I(ћ#A ]݁QrwDI%{t3ťX"V0G1H(df NҫB$wDљ/.4xKDzDd~:[ t $|= ?h!SQT[H"fG4v.!kbݓIjRf}ZueFWl嫒, Ub?ͅ[q{#no`0aj BA'a_ z7NsHʉqɃG(Y#/!YETH!$'Ar6DQr)8ɲ0્~EOgˍr na8QpcZ$Fi(8E |-^P晈Y7: f9PyrxA'FӛDeIh1R,, 33Ps>A*,vݮs*9hAca)ER's{b "@]hÄz9CULц9hXHQ"LvT)ɗ}pO48xDŽ{>!ȍr{nOl`sJJTV1$6(F$5Vz|ff!! }fH0 ؃2ZoKJnII-)F v*j4P˧%@BKk :o%d]Q+qŐ,J2͐ĆCχdS?Lぇph B؅.jn+&VD tI˦)SA!'qt\K }QW*b\(5MSfASh p!6_~rpJ:&Xa &v"ѝrG۶nbn=U&3P_,S \ \ۑ/Ʀ<5N?s9"YeMSHn X 5׫ e`0ݨns ֤]m"n5a->,[@IT&a"磲_ x,0yԝGs'"=ee/8hb ECTO,|~Db/\kZWm/mA6H`68[/d=@`LS<Ws S$Ep%Bz.Bg-+ 6V )T65IZ2zԸ֥=i;m'm䏻}eO"?.B,+h$Gc;LXD>;19eJ-a`CT PdZ zT;Jms2V(k]]Zfh$>Q[G$nۈd.E$z"KP _C&{T )9r$q ]E+r1pv LLX%.HN Cp#@d"x(&tDsIk'E(d%*JQD)o+Na VrEPݓA.tF( f 3ip\3.J5)x)N7=`#V'KtS{}\ǵ6j -dk%vQA/o&ˠO\~enCװ, !!V CE 01rJSM7Dk+.ͱk^W w뵃fv̇ 1ۿPKnC-ض5e/jH"@7AY? @LpC6x*l,0<,Y.Px\ɽfp+^1핊nZ?@ B3/2v%xbצ;7]JN&k^ fRgo1pn*n)nl!nPnk>ذt d-F"A͖r'h5 B`nՑaT7Vf0O{DٷI[BnCF;Wg\knVjJZ6U gվ*RUcJ RI۩%q+5$n3[ 2JnHv)n O[`9Yp{尶MmKAö l+HR&l+Uj0m5a ]6Cm-}MpdJr{HVho}',ou:=9)$yg=)< r_YF}eb>1Ilxs<͏9f`!m 7P{# *X޽93Ѭe5?|;V`3|b.]C`+ ~<?o6]d7O 7{4 [pK wp?8 b6XK'aKs*wӰޟ:=ﻚ">`|G~Sp 8'6)ɂރРX޼7D Ox873G"|o@?@'\>Wz)I g@@_P((J oW"x x$~I`T>ʅRL{<e>30#<3!ʼV0<9Y~,W*d}?BF~H{oc“'C@y Q^5gFy(/Ǣ<(_pEM^,y_ɯW#Dz^YT8uoceD_57<o/3|菇~@ ȇy?>Ï_F>~ǿ8,16ϐ{W9_ğP}W? hS\<kC`3x&^ϋρ񿸾oQ,Գzd+^US1蟞x/_ߦ!0}-/ķ8}M%5T`I& ԰v';~$OnK{M˷t 7Чt cF_$:/>> iXmOj{SwУz^nһ(k!|_㾐 q=߱U?Q#o.ka7w/-w6rw.a'ՌvUYmWwu[ftZN몹Vh=ta{uoXxOqWog rUO GY]#jq#\փpZm [/? E<ltNش޶ߴe]eMm[`5yjiK +M,4SV6ZQ{|;?Lϗ|\4oSHi%i΁u 4C>Y]˙9?g|[eҎv`iv/}xۥc 뒛k6.Cp-uwΰBOh߆^[o#iP~ vAp= [qВܝYuGf}?xqnj&׆p+}>T_iGlPw^*Xv{U&ڛ*ޣ Jp7*Ϧ :TWRGI5r! \#ďx3ȗ-y1,!{)F4mC)2." w`aQE).fP=E4LM1u%N_K{;^ȇ=y.f"'p2KI*Ks.9>y ^vJ)]X:BHGF$AB'?7x. AwU1_&5q sy#{Mr"C0,hsD Z|(:"߈F|FDzP[CO x)Hu _fSÞ{P9qOn;Y,1pαΏ"HJ)Ci(3#u#xwFlBΈ)NfG!ƨ~ꚯxr7_x15nek_䧍l}>V#j7XUG#x0S1m :3;nLJ8 =dv^rOzKch/\O; |#[xqk7>`sYMql x'.2G<ə/yr&;dġXNa9k1gH9 FKȻM2/yM>yOyV61I2[ּ˔0#NϜý l7MCDyne2wy_nrL9b6qI_3W7 ۂ;xk#Uxi.5GpInG0'Xl &qw0x.}' VDwx_3A _g7.26lb7CGaSxr8/Aww @>Bo+xv\^ yw/H!=S_x/"`pToL؛B/D#<0'0c8} _e'Ҹ<< H" 1x >̂"Q`?0[zKWqz)XE엸;4w9Hk|G7)6 Uh!1`]W~*RM~'F^H|?c8U+cXyW^3^y+v=\*D哦)wO&'#F_\=>נ"pק o(E| OCm">`G}=IHˑ|F)H{"g#RH!5zR'zҬ'$JK=Ey1Lh|&V6ysMGl=)txDm=Yt͢6Πy&EHi^M ^ip,>Gx4z!>! $"ia#Eː4ThLI%\hLE6QP+AFm2>Tb_v)0^2+y^D$.](.DM*>uc"3l6DB: j*DF:a9^hp_Ioi+>r3xB$op `0b3dP8NSHA  p\  ԺX\gz E^`=/*C~l61zw}W黺I:6aQ>'>ok=y]Zz )Ol̶ ~!'j3?D^{ w[sq;kE;>|W'[#+:1̝}am9wO ;UPx#oG[}h澎2 TNlL+;8=[֞rcm#7^_OA_ zI~6f]7}a u7)ocq#[٦mfo؆6![{ljϭͶvZ`jno]7Hiv =-~k~7~/17-M;S aݵBX׬VQ;[1>MS3H ;{ nq[,2;YJf{}=#|LJ|C{/^/ 'k=[pf-lj%Hgmϲ'N$nVl,/zg{ L /#DM>[Ǖ~;Ogߤ? }ӏ[?ȟEy2/Ea9 ~'Px 7:.?ZӊJ/Jr<*_*sͱ< H>R%~wa8~,ȓ)1+ao`Oa /#8  @ kb<2 s(67ўD,4SSM_`BҊn֏:`Pދs Wp*ߠr4LSaʜ68_q {*n$GHL*}/uI! )#cEl㡀<Ց䳰|֘B1l}G`$c)wX(QO>ěD(JqC+bސ`&4㱠wz<䋘RRa^F( 'uNLGu<+bT'ƙ93̩qc΍ 9Xc6rJư2U yy#2G<kw cqg 7qw|σ%'rF6ɀ+9& 峧޲`.;yH|cqxA\dYI^QoO<ʙOy*7~eó7c.]  -ٝ Qr* 82Qa|̙yn\ W\aK0- 6{ns].]޽/ ,0ygF2}<6V XF=Ig`8s\`1xoC,.a|zϼN@`,evyl5`kzplos' sxyxo  p+f%Kxm]|ª0o]?q|?|{")xݼp 0)e8?9Grxo}D;{;^7O< CG&|?y2F&h}2L| |b72>&Ř֒RNJ)%y$Gy"D/Ddއ`{yiP> g.^9M@ǛLx 1{aj=>k)x<q@PƆk 1/ @}_ƻ*X<-Wx_?_/ ^Bg#< :煅w-ճzg:)270^ה5yKL©5";0s -M|h"h=BLeh &`"F&=*ԣV=jף=ۣ=Ri蛢>gY~Z8P3%z(KD-|ChFs(4htJ. "ЦBP-F:Z/i/Gn(H}I"o%[Z2 ri0/l&N2֑t LA1ČzÔrTo]V]ui5IXYr8c}o=ɘĺRm:j8Σ=0 GpȩW!Yu%ՉX=n1kGr䚃cRnjQ?as?">cxJ( Xpue!4X)aYHkF"zkï;paW 2;ɰ_>%7)SdE>Pu+ee3e%`Ze*dכ~ P{cX6_!<[ 0m?{74|Do4^&y>+|:B?0땾V"}aV} }cwg!_жKmO{kͥ׶Pzqg|~(&~' X̺wGf%!@5K^ 3+]ڔwj'ޱ x6pvo=V7ѝn%:~]!/CuIuЮ%С]CnKSژ[αYEܶ{{Q.nA9枋{-8ւ{ no&8-gt/2><)ͺpmWڮkvs&{xYGY1ntn6ncm0b mp5}>__zIʻY. 565:5;+US-oH5K.oC hc8K<3W7_w\ލ wL^˫9.+hw0hxW&gyVgz!!!4\,pA3++e(S\)Ǘ$|W#p).x;"?ݸ|5}%.YMY2등?1@1A+*A+(+ABcN4AE!lr;}ȼs0ojW|j`~ 6ḣBk0CkCD+/E|-8^zE59Wm.799tCWG\#*T ƫ6!׮|2vmE tX hh%h-IlMιU9Zji9VqYe"K\H`, a>׋d?ȟEy3./6wQAҺ& l G0qyO: tȪD"gZVr,d'0T>ʣcs-ceɽ8 3f[^l3=Ckpm>gghHy OWs#{`]L]=O3k܀O`8~`;8!|t 缟[v{Kg`,~Zx>i[PA??_{-D[)\==:q| b2惌19|ޘW_a cQ=\X_=w2L^I#{"c>P goDS)o) G_Q~C噠ѓz=O~';D=7BD~{}CoX @^ /B? [@DDkc C~i 4竐r>cq}×AU]<*wƷx/5Iaz 9cz7|v3 ;Q7V14 yl=!2^Ƈ0xW-(V)J N;P!t+~b+NjբW\=nk+BZt T4MU U^5ī} ObQQ9K^ )UzSL/tiM0ujDӨL1,gRJ*ڪj(U'zĆ$:cxK  嘀!9]r ]o @$"Y-CXDT[qmC&;| qg0;nB?{EgH?'a|doNޏKY X3`YΗA%!rr]U]GլlVGA7k}q &8w@?ڽx`L{8o$Qe<#G4R^LmZ ~i:Saⷖ-gJg웳g]p|x o||[={XW_#$WlޛΛ<7 t8Ƀ"ojGwijJYxz-ڝew{ p w#%tx& \t9[7BݜNus:etyAsJw۳RYtN7x! 0w\Rs_{|Hn4oR?0 @"<\Q!OG8<3qy:QtĀ1w ZZp~v8[ioxcț*'jth_;zR˳pٶt8hA3Og[` 6T6k&ۡr ъxzkϭ=ζ_koPJmsyFۉsy7^)y*7.us |FP ZaNgWgWgVγF䙣u r3&;rBps f'Svi*Mx;"_|ۙ7e9[BbNe9Ys9{~QZ/,c#e8scNB@^%fR>>On@>Iy.* &+%``W``V_V/O_~k/}~u:׉7xPblc>ȯ9+6x 0ymӵy JcKԪ5Z5/L*qZ'JNKu6Dz\!6 k=|#/˨' x-6 z֨,2WV)W%DN8L&RQ:g+Vwj!ՋbG5㸩MKW̄BQL sYy/.f- Tp4A+Q'jqˀ*MhTcͧO:IdžNʁ` (7N Q,Y&pu2?YQ\_Y~U_q=ẻ pkf`!xYQg_hh~f9[a.Kp f,d&w49k.q;Wq>|7l0> D>;=uh1Ѽ .nNsy'9K0)8u .1X;?&6aQ*=J'gڇ,, \f Ʋ;?`NMoݼ9 OŠ;] ދ~v}@xfpKm|CFU8+zQc罳xJzGzGٕ}ݼ pYL; ;mx_n}=]]j}ɺ7aOgܿ/Go^Iீ 4K@Փ/kE )()(єVJx!%j I#u#|k/lE|SO|E_TߢU# "n#3H<$x<OEd<?oRx"!_c]^c*i9PBB& + iyh~84_w2E|ј:b02X O(^=<xZp<+r\U,dXÕQW^[@V[M~+z$goF,~y7H$&+I5TJ^Ou7%ǔܥd*%۸|BW7u9t[: Zѫ #^]b]nͲղrY)G;8ѯFv٨` 1FM$"9"~ݏ>|O#fZsKMֽ4ydO}_Zs>gEzhBOf%Aƅ!_ C1cg$N>pY:WO:*~u>DzhyZy~V9?SU~7INg~H*^+x#O(|];dzzz=^=+zz>Ezll D^ z=D+mz='=ʡ0/\'/NxC _S`g|`]`:Au;u:v9vWF+zg9hv]eyΏB#xul}X̻=#Gs<;G3Or>[ }CiS"nQ412y=QZ,srm>&ySq"V#qP[|t:떸n]{5hsycMRGDdNf[5ؖilӴqZ>ws =ۈH|V&xO󄼞ϣv_? Mg wV DŽȞ"{M8AV3R'Zf)fN'j=L楑.VzZai'BS=E!blEn?~=,/Gx.s.u s2L 2yȐmD!BcSg,4k(وk&UԺHVJHj#;YTLF.2KI,Τ$UL8ed)$I-LÓ2Py c \|+jW[úlX ba-!J@ ԏ<Ӑ}8Y8!DSIdL&'G2AYiʁKU[ʲۅ.P3n2*`i KMU<ajJC^"td ,Q=IYGcYyEѕQ-dy–=|IeH0#otC&T^A <TJDYҳe(<]6 ֟1o,>M N5'<>IMxHͯy/;G<`_WSG;+Gf6Y^+!%`` `Z.x" >Ԃo* !5B <?GCf^ Ṅ}O ;Tyּ~ ߃OAsޚ_[aj^k#4?{gtLЇ)z/KEcz+a?7B@D_!c.Ax^ė_˯yy1/姼|SG]rsbVG'ad=>x.p<ٙ  s(:jEh(AEZ-aQ֊BT](GL}?FԞ|ck~ ϢƧ.7(1"E)b4"G(i5(XD#tň WQCIUQauUV',TPnCE U/Ե9mӷJޟ䣂)h8^lY蓦Q/w(g/|'e@j4̀h <󙡙sBs'TmRMq3.psڷYgۼqbSoM7l =ZCWwK%i˰lFZPjBL ]\ *"Tc'uGĤ$#!e^WgM\Rp.J¥( WLDGAG랣hx#rdq"wGѷT4S(C=CE(:"-vCHr8)EDD YL!L,K4%&Z!S|P%f$>OxI RK|#h"{4y$"u&s*}|W>M[_!>UL}%KO/^^OYx?xBn(S$sHĎq]c Gv>w=w:R+]1;oթ8]d|zA/Ey-wzg,RxRP6 #m@9:Z}p6BҍT4gŠj}Sq8{\t|׳y_.ͿIf#tgϪyQ HMZ(4џ$h#~x@#ѓ͔Dg 49j)rT\V{k.sk1N[5_x)MAhIU:.OWx5 D5#Y3Ҟ8e$|B(3Aԇ jO2=@P KJbl29d.r*`\d\hXnXtf㱲 kjlD l5k]m̻|N~HC<[b'ʗ"_0tt|Ё5#'WӯPWM0I0VFVB yg`Ke)U6ט';!_v FvY9%C.9l9#NJ%ZB`C 9C8DCLGhDl`fi/zځG.$e^!3oЙ#y5)-;eG00ֱ3t"TdpNCꄰNxPgǂ,=Ѯ)AK^aVxG'L frY}7!|b&1 n–0qDv*ih7%FL;b0kp#9,Pa$c U¨y{{ۻ}IhW2/(LK+$0 p>a4pLef2@w.LA-޹ޥޡ|v'hJTxE8y;ڻww)Ўh`s>>}}(An~~6? I@/@ {3r<O RxRx1)~G "|( ?JxC|Oo\5^7CbWi&_ < || ?+7 ~N/. O-D@|N~v_c4'aȾ g{)$~YKX'X,Gx <? .s | :1 5>/џAz2OtT'!CۨeNgS0V<3jzfr4󜙝U e|Pg2@ gLуQ.Qt*e/7R@Dԯ!aBV!Z+Dkeh2YfY,l"KћYfr %Wκra+K/]a•y`)W;!{&nD9L<"ǫ(bG+ i~i4e=<$jK0LDÈK[2ӔT-%WO))Ɇ-$7≰2=_0}ƫS&9tVTNT9!AuZV\Z|x0`ozVicsE)EWG^tJ϶YZjgX;Ìd $#c= !.>h)n<5`?GT0 ReIH)@ U5۹T=iTU!TvSfSgeS!w=JNxmM)?BƟx#)'( 2-lh2"-$ʆ!tYjih)f)Ρ\ΟiDb}k[CL1=|/Mo<]Wьv mzY(SO".jEI>,l#Z&DSGԮG14oG0tOgOJ' xe!h-AW&$PV珦>iE~Or4CkGqΨ%Q0ߐ`-3ځ9W材gPe&OOf`fzƩ7pB)MCm<(LS0wDiga#LK@. d];xF/$,J N]N:H+[d,SEz)OdG/<%w|_CHExt" *A9 Lr(x4(h1Qh3qh2gQP1roRnƘi W#W#fj,]3䍧Ir/zA5g-a!,T`B%)IBh &2Z A8ITY(XHchhhwyMK%!O'&BT0qCy&h<o.m".ZX@ţ4K$"H7VEZ0 I ͡#q]+$a+<} W\D m|gDP>Q>~>H ic|JA*YًL27ycҨޥT!Q ;ɾOFPB;~9wW7@Vw\]*G5D#Ry.o!mYme xHY&Lr.fN"wCxAx"~,F}JyJK񩰢/U}6VqLP 5o7tCKia8BD:`Тrfȍ R1G*d:WJW'΄d ź;v+tZ\`Xh, x=fr]׃~V<^.G3gF-܈1o#,6jiҢ'mp3! E @o*~:V{,Z<½AG'69 oJNDpQ]y<.7<]̓>j@f"hL I;A4AZ*4ա)h* @kj 4=i4x6lPa\ۑS:Fd6${5YjmFiZY qenK,x~l{֌Ϛٲ@'€,Fd3Z2fc9zZAAփُв :eCr"ˊ()pr[)]JceSsNϦIh~7zttƣǀ=^@Sώ";3JckIO0'K0(G)VC0+־Yؘ"','87[BtknىeDrSEI(9AȢre" 5+uDkgVke+c֘̌5k (u8a7_;<ɓUSm\-ruT듩/V#Sa>XmH2cU z*%"Ҽa͒ 8Qex` \&\T!ꄕ8D[מhBZP] źO`(‡**1?WƟ11#5Se_}gbOr܋"vzxuy"~*Hgu}F}_lO)of ۿh{P_t{W{{z{O{ p/>r3v~;|aG{wd_|DOxx>_R2~?Ax/B}}|]x; |byXH/8}&^ Mؓ>}>ӇO~H@_gCmPgs ?-Hd}Y??[_+~뽸1p7z=r~Xѓ-}Wz[{;={=};ӗS}X=ρXXiXv^ꛮꉮ^˺[C՛uxoc{8ÿ|̯}߶b8 FHG1S(yR&bR4SOEig*UsXz. ^$_ND|꣱Ӄk~ QPnO?\N_d=&xjikzzUكh{3O|o6g_b/`0WLϸ}K.\vAJ<_)!祤&BGӅǀ᧬j>|Wr{' 3OG#} HVHUAt~Dg#Ce"5 T44*2ii t*~=}-~Yy~YP}0olvNhg,E< )E"鈆N#"Ð2J BH %M$4Q;E 뙺>J*5~勁郭5!G ]DhhBD =T4awAG! H鮟 c,i'z.z'?BTq#0Oja> B;xO$@O[z.s4I{买!y;M8]WNY9jUH֪rס_dImT7@sxtץM.-Kw#7-cK0{jꮯ0Ss8꬙鮋Z}P=3~TOMcԿS=3 _ì$K]OZs ם5Z5]oal?Gm6nSC|oD3|Kx 5*ް9q5;=mqmQa՗fq0u,";f9A_:}utQċ^Xn.Ve4Vj'd?J ~IMO~)F`Wyu!x0N]#7<njplwE6VNZN[h-ef5 \xq?^rwLdu$̙<R}rky. /|D70<&P&a)T\-˦91g^87mr+L{AgH.gVa4OwYK&#p8\:.S08'sw-i9lr,CIgP-$~5?+OB<,eZP˷iy.-凲O% _HG8RoH{w <!/Dȧ y!7ˆՐ 䅄|Na00 oW^S%/aůY&,Fl.x\N +~)>EԧT?!&KtBK VD_:G!HHH'I$/I$~=OI<}/{nOITG8o^$?Re V>Y;] ]_1?77{;?sAC+9Goqw"_JHguƿ#~r)޲_LM|Ч,}M K^ߋ{Ǿ{6X~I~_B:?<8JC$W*eSr~o<}~WOކܗν}ݓO}=Q ٽy B~="/ֽC:7v"xC< g_GIikOtk^kQkEl?@nVzct{COFlJ6} B`_Svx<|__m_뷼럼>닾~xiTq ~O6o7I[[߷oZO*1d7ʲN39]K0<֟y=[s{ |<ϣο8VO7pY^ճЫVee}Cu U:7Q|D3@UCl8z0,C}۳=1>]yK}5$HREH !RrӠ32) @;E%tQAQ֑Jzϲ {1'_|w351ʙ@]eHQ:B< 3vO@9$VTLDC1}aFKE-2<cu{@{kj%lŏx~6 z3&g0lІλ@4ǒJ2* HG3!!}t4ё@IM0ix /@|w߃(?3d,>Ƌ4t IB[ mhT$3N<2z7#ˌ*RCх~*bZBE*LjjR;y}b t_/cO6N|BQmh'EF(:Dқ^$pqDM9a魡0A(MBR# MmAꠡy3({J|/!`<wo`Hi3Bn"t!Q9)4cPAҰ ~S5|Rz&(k?JZAfO(DAOfz0':x43'Տ;V \}6d!gk+XOY= ez Ulzξ }Q+S:`㩄VUZVj(c 5ZGu]ḽN?[j._mm{GJ|ȏ|_u^pk6wx}5+;qMvf;m֚Nǭ4E-<> 9~+}~C㗘mc70X.{f8ĥyڍ=xz-7lŮvkygxoN:p t9?Cot\RO*FR؆3F5rNv#zw}ـ-׃k]Շg8Gq'&އ ! ?䁶yN{|\p /;_;bȽ\ɭØ3џ =Km?V2p lr1^t=r|pޜm|lп]WMMN1,v,#>IyW˓95Wqy蒋X'qߦ}ܨ}bS[W53FCx ? BGaLb)Ȩ'N]tgz-VńoMck1<EfF3h)asbvØ2F2s|c;ǘ8 />‹ FJj?yl){Xg]V:Dyg5D`#GAV򌛬KTʒc,e 7>3;` !Tޱ|Yn-kI c38I,px }?v`9% 'd6ˬQv`!C'| &=?9xrvg#w mml8wxz{ $< s"x+qG 0S} ?7_ K<#``>×/=]Ly<>X{z"#ɯ&$Ւ<BEHLj?#50Ҿݯ0|UxbcƓxoGh<ohόeĈU_+^EOQ(߄䣂3 ]J?駖x<6B}* ;&~?ӣ?ZJ%KGG`>wvo[8:󯝿 =чB({m/bE$ }O/⏐j`t>osP _} ?')ʟ4zKǴ̯LM$N|Moy}?~? J|/|*|9D^q>'/-=Qto3PUݣL?O{ y!h}_}fxC׾! _2#11MPe{>m큸|{E߃6[p?NpEq?$?sCuBwzOg> Q5?Q~M0B?Y=(;á}w@+B hoDiZз=i {>/T/|B9Ğk&GnAL ?YңA=տu{{=?|#z_&xx <cx=Ozy4V:ԁg~ X1 ë=]^]~xލ[a>zS-Z9j錗3[V 8h AGm†`,zzz3kqr_vo'tjWhNдVULMc`jzi%04CO}=Vo{O#}7b<&h6MFi2z >V4҅tהu0,a'$ +!M]D;K\CX7|/S}3Ǜ*-=҅ Hp$1(ӒHQaHmFRMeDuVQUEVc(šhGEYmc)55>勁>/j| (jN+EQ )ʰ" ZՆDU] GÆzSC]C uFB` Sھ3}sx_&wYD!wjF@+BV ІI8l5"}@]txk| GlzV1 u(?*$F+`|֎Bl>[гu<[czNaxtkLu.g+hWAXCeE[봭V[l;vq[ ăU _,_WU?^e<>lC8ڄsͶ![=,t[mwnZo&:p0>q>ӈ:\|7IyO{GӮിv׾q(vt';{m[=wրW-8 gpq+?gD|# 4~_PGYLv;{y7|;w-݄]ڇ؊{x%EkGGxoIy0~ 80.=O|o|pn|C|ؘ{XssNO4Dsky;\ުf_<ƹ8!S>gs.|~r&1M>)nzGX!x9?sOǢxĨz闗q?:*{[|AgXKh!|wcI;;n>)6o]\:?4Ɖߘc{L2C]'j&CdsLe_Y}˞w/Gf|87h;npkc؂`Az DYlbsAyuF݀GO`<7O7ӽl{"uFal"!,W`$_03Xb>݃GaٜONp{Ѣ/ # 1 ^˂g&8?F%ʒ!/d{}#0\̼3K3OfʼR-WR~(J#Cd\=5>l&pN9y0'/䑜Q'9Rs = m-( n#Ǜ:Oǧx.A#!s_q}C¿z^"WܽR &̉?[~gx?TJT(x?G(ңz]"ě{T?O8M?O k%V$HO$A#V1#~4Ón=)ٓ=ߏ2%^7x*#MHNQ>OQ~U>QU~_-LL׌z lZIK@Y >'"~!>o@| GOAϳVƟQ!^'=|[=fk>fc~GyW]߷оw>{2{*{/! /dCOEz??#'q_ `àQ u>a?z³?od|oE.޵W4ھ+۾^^ھ-gy܆眾~ a#^!x˘=#ğxB|[cf׻t2Kf>ߊY#}bDs b:+3\fa@{}z5 64D`pӗM=ֳ=ͽ>_m~߈3w-^g8zx3/U=FBuZg]DuY8h*.VBh+i! xV?^>o:P܋wj [Cl ckU-C*:0:Gq8Zl\ڠt 3a1}4?4<׆z/k{4_{8ş|:ϧ#r)-ƫx^,l(z,`-WGBZ*A+.VU0OSP+IaHJAST\e{~CY>Ù_!|T^*MJ*P%=dJSbRiC)4@!j a- :*J(ºGW={1!_BVƏy1<vi20CE08# Ҩ-2a'㞦Fs:̩j&5PƞJ7=>73V? y4NF)8aKS4ixF%dZqj%.lXǔR:+i몤:CIa,}T{E操~'{b:ȫt^: V[T5%$]]# kT$o"exuwG]yF_sUa"@TD"B?彔c){y?x5zNhk S"qu‚=-x I6!чĎ#qcCe 5b+!3$PCBpX$w2| ouYU;~䱚C! }b>$(A N46(Pْ@fvm>k-㶇xK;v-w->c|:\<U䧤=!ޞb vBLF&:hQ[E"m[^^h;{m;=w|SwԀ_xY /|w*TԌUnqCYnIlx?| ڂ=x؅wp#>O;tDW /);y&(eכzU[~o|p _[q7;%0\̝Ї}g_}^Ѝ/ FL(y/4͆÷17>GW.|gnqsfv]k5l:&.Zu[4Y>Vg(>.. Or'_]靃?.bG|~wF8Uz]+]cu3d #,b$&5>]uŝr;:-}oMmbx[ ?]䣓W$_I}W^Ye3=1icw\yq@&\9"}䶕lvrS޲󙟀~71iqxL!8>2:GdΡly!U.ʀcY-e\6=W>zg䄧 Ts^^1Oܹ/4f XoՆ`S'pXu.1X;`-X f< `=8o{6ߌ ?VȼP%| ]&sp= b6xKL#n&%GaSm]]}W䃿zc-d;  *wb@}/R@~y1iI_UkPͫI{c~0]~,J$:o8cwS^)OeTޑUy2*E噦)NK~%I~$;hXCm,47b||kA ?* ||P M/c|lBW }U<f;Y,~;x;)ZPa9,~-WaU0Wiz( 䣆i7M}DcO%x9OFJ_C/Q>P=_jKtKtxo:j|(bB.=1_12_-4K1hi>mDy=5<փ}ۇ(gx? (Ň#?s Ȟzɤ  ii9/Q_*=_&> /'|O|0RDHo ǐ ~;cGdW.$Lݫp=w8ބg=}ޏ}[}_{#=C{;}_-}°?T{Ew|LJo,*P*=q&ONg>{3^r9oYCMq=L%Y^+/{WT&~xծYš֞Xb-g5x\ng45?&4PiD+]Vt9lL&qY_Оo^ % I_x<k!|V DXB,$-̀aЃfCەVfadEw}Evt2ƒ>Sh>ePVk]6slogyߧߗb܎W+F!14C#:I]fayvt.l/Vz ꠯ a}ק}1^Su3z! e4gx!m #zXzp.0 3mihKꡭy:d;3W{7s|;̿;ԯ[jzNk .IәZO+jE헅Ṭ qа6Lj)Nֆ6l=W=74w8͟s&>ŷx! /Uu%&2̉%uԀWTYUfe5Y^TXgQmjQSY_hk!*s~k^?-8mP|ۋOeF*jJ2UR7:(q kA)IeAk3 [5W0tX7a D`Vnu?,&HZO!zXհ8@g4`$Æwd*A1luaTzP{kN`{p0ĎjBSEM!rR qoY߁?T>(!V_L^(?xZ'8ué+<,kJ&[.]vJfs)Kl Ii7%}P:Lv ?0苡q?'{!\[҇(#II &E"ţ}whFk0b˨m*BE1 TH+``9PyB#m@VB,m6%R[mG" {nHnC -&dw9=SPH@ c! Bg-9(,Fܶe}3wOݑ>w#ף7q<}mxNtߠSTMčr:GDPԋVC~ G}ٝEй+XyMcQw[8Nx߆}8܉ٌZ S4Esd ҷZ|SQJ_0.xsy$cU8ox'=ݔmژ7sA.h{V9/>#$叾ǝy'1[zk8{8|n}mm:اo}S~4yuO@iʟ$&qW~~bS;w8nq0ZYxj3㢃+h>x/-OsJNǪx&f]9)4Fƃ߸c{E01̤:s!*Y<3X7rm}|:EDȏ9$d厲T]oj/;-f ~~pxB6#yIV2}dGY)ʢ[s,[eȹ bƻq0vgv`%xh ߄?N(WޱZ`.RsǬ2)F,9'` 8r^| o^lv>aW{5,l efm,.-t wwpx.}pnf;Agw BoxQ)؃S#pI%bFMGaΑ`{ww ?2#*Gy~ < @QIgᏜQx <~/#Mxˀ`A|_{\-?3y%F$lA9F$d,$ˣ[O˫i,!#]*3GO~'OC|>zYσs} .:OA{0y&D^F-Eȗ,#U䓈<ѐ:A ?y;?o@ _x{}bWh~ŗVb<:+g|b4~J3H oe2л0O[*ۯ{UO#[oJR≜> D}EgN׉:P'^!csN(zFoMX7vJo $L_D*h>Ot '|/.Ч'W"}п9 F/"~Da#~GTExP\}@!97ja&_(?-P~PT^-r15|k='wRsԧ 0|)S >'/&~_%~YwHǐKoC9("3*}c?b=K=[>fgf>f[x8~O=_п@{w=/?xFG/To ~>'%r%G{H362nh8,Wٛ^oFsx;z 駷ܔQ}} 2#a'T3^ǫV gMh&kV4њkG+!ݧ\hiKc i+41=ZChQ6Wuo{?(Qoj|BB$fΐn"!FavaN3͚,s 50a,4<5&I &aY֟ٿm=ㆼ ^O5XO@8)@@)G i.LS} j-0/}i-UL>m$?H?/`o3~I`^ 0,=V5uՒ^Uz:p6|ցa]Kk \\7qu!y:'3gC8ʧ|9Ӓ]W:TCSu*"6L|Z[iq5{au>/Kf1Nj!yU*n~-O!6t6 M4ɴv$SۇKn-m. B^Jvg)MSHI~%nmB| ZuV?^K^K^h mKԶ$$} Hr]yT݁Fx-g7fE}U8V_`xQ=Fd4x-.`p ,:܁-G<ts!6 gf 0pz?{xo6?0Cp%8ld 1XY(?Xt͖0$x?aS6{NyGC pKms|2~)|b.΢Ǚn;Իѻϙ6oimi$| 7[yN0_|ؗL]hey,OKQ~'?}pσq > IM/@OQ:|%ڒZTK)!#sF"_;2Q6~p|[Ox.7~_913ߥrSGD8>o8zI/#c[f~*GExP|(L/I4TTKx@V֊ZN*I[PB*Gſ_yաO)>W l(D壊P~gkD?1OG}WρHHϓ=)ғB=/R/R/}|?JEQ|I,qo2*3dilosRg{'܃}^~Mo9]e6oi|"G ?cI:[xQh+C2}Of}e[=kfsrcJx~>B% s,ž .+CaXBk(h#B)TèGzEXooY_Yw|A/_*e|n3O 74c~}+|Fo]Ňt_.0Vz/L{D_|Ƈ|<}?>^;+:; #:]!/ ]Mk䒞waqKcZiI { !4M-ܨV6ڮj8;{<{B7$/t]w ûA==] j lQX5 SI}65riU]  hހa)δF:z;jbrtoyGh*O9$o=4@BΨ~qV5V_:l~f+zLk2ȸjQc^+}ma>cclvsg||G~ےw;%p2 : D6c,kSZgؚQk@:k.0>Ll&0v >ޫ a3x- 5'Ƶ a[?|zƇyxkǯ0<-:͡S]f?uԕP:^r/fo?7t(x7"zz>#&:%6d؅\dm-eVɴB{++m1d}!m Ѳ{K|3W|7bW;eUޭ1 [ ` _@DkZdf !ē>܀߆vbm+;\*n)6;jY觕xF|Âun8; g[uv\*mUET]Yն"ہTpM7%eSuIٽ%TQNU2?LėvC&eچ3vSmrۉ<4!n scbC݄QwE]gP|zɩ&{9x~!Ńc:D,+@Sgd']4mʤ7'ߑK}#. x. vp3%?(ΥUPI#!ETN/5L<_?+wY f |$=T#M jTxF%W]QpE=NVɳ*"|ihs C>gPU2"ZO~RSTZ0" !ne(q"Cby"(n xO *^%nq/<Ɠϸncq̷>q?L䬏%+ 塭|Oi>灿r;Xele;1u:K1@\ȓq##~dvrQ.^_-?e'c2 Ev򑃜/MFIeЧy)XN\˃w/- bZSXcaB  0@@0dCȢHmm8m\CÓ@݃:2~ t!4C8|46 Y0J  |!. 0n#j]Yl," %ywE2t? D9BxiFOO R hirt&q!QIP5$š0el'JJ|aCmb0Dx _DP)OٛP΋ c<9DtFEg}ƁsnWe$=Œf8ȱۂ7˿@@f.YB֫ga0(+0H-B͜x4(LpYKnDWHHGƴ89^=6h*(y~$8}Q{ `}Ϛw0.c!!"c䟐+ҬDZ?<]he,(815ԢԽi)hec-:`- w>F qj-^t,'_(ش2`Z(&" x罸Rz!VŁ2ڍHڗh6ag_J9tiA%E8|&˕]jq2]}19EIzџMJ!gφtE~P]2KdոC.xp\(. _'/D6(l\rȥLCZO.q 8ù0j@ P,s v0%a r9jq1 t/L> c=,DVt(;]N1H`24~5ډm0BFC콀ax@^{5j<&&"hg-qE*Hw"t ߷u]\-_3׍sz[z:4^{ :yNpIu#ء&(H ]hNӳ( D8!e !L_ 3q=x܇ŜR0u^bnCv+<)6@nozdS4WX>TڋUG&a<ѺgG^yަ?It|T,x`YjC_A Gw]vK<>2 x%|e<Kd'1m$\5B[n+>ٮdk|g|q O˸.qp'kOeΕh[f*y6#eL Z 5:pfp,}$B44t)l@QUk4gރsI$^]#GZEzAʶkz ؠci<3͙, '%GGzAZAG>=" Q=SS x!»-x>`'l-XOY 6a޼nd u6ao7 d:~VVTr)w3(p͝;時g@K-ۑz/riv0 D.-V0xS&p n(#–*/hs^$8 $Ƭ 0aюL3v!3@)Sfj@c9x8 20* Č%jL!8>H>h8B,47Ps q \9"q4:R;( &nl$ysgD/S!*(,U~ /0iA'ˢ2!X`g" АDÀ=Lv%h&f#tE`_Yˆ!z!Ш6H #n@A=/(J5FG@H#mdCA t'l rECV0`#JaD!Y)aY.2XԨRb & ,Xc("dA |Y1E/M!P^%E ~xТp$94Q"( 1Lv4@HEZhS1N! 4Pbц 3PM<%vP$lJ7Eqem[%`ݠTPD"RyZU[N(-eCIcl#  (ɉUeHPP"cAohn)Dpc)Z&h @lt :88 E@&!!F<0A ~L]a҉i{!EXE79Ê$vx߯- " HT7^ 0IS Q& HQ #mD&A/'R̐jX88y!nڈ6dFp3A8c'  D> "pG{ŎtarCۚ1hx@cmCkAH0/3 C`l"'/ #`E)%ih!PhmXmkgm@G6+wL@ ,=0Xj$f"G AOjR! b +͛;0 &0Um [%Imh[ 0.qFs$>Sw+`p 4zA#`a@ PF y!GNw?M۶mfPPL#(K(@Ѷ[`z` eC挤<@mҲ &uyA/BEPJ: `L^āEitѺ=>7e0!fQYF?iҶn .C ~ H  |6x/iQ L(P (B/ȐM۶mjA$"|m۶Wh-r:xCp1591g%A uҐ@3h`Urx@40&tqLIж6XED!0iFR{!v ~آmy<1fH/*00)pmB) hoWÆmٿ-'۶m?Ӷm!=ږ#$\ ҡqwC`1:IasqJ3z4?7%U\ zT8]!ǎ6)\ 48c<XAA #6Ȑׇ@či†f -MCJVÏ49V(# 3pX=% :pHZB#1{(JQJ0`YDF q`A6qj=5|t9{A|⏤4%JHc&tcsF= pz359Wc{#>$ov#~~2=-ʲgPǞ%htAʲgi*A]P_*hUu)臑CyߥF349%Q 95y"s"ʀȀdCBC@ '(*U.DsB?j&? UРICAgtCG40Ȓ5 \`[NV1dNAL M NEO@M(Lɷ L 5U&[庸9`\PI7P@tM(`.|0WDJ%CIʊ/x|a'䑔7[¿8)2wd" |pNNRx4;EZ;;ҭFĠDq2ęQ&2Qb2%Lƣ?xOPߥy]=ԧgTRcUYKG~(;Ru\% EK `C&T]W_Y]Yz;_ky1QƓ>P1B'ّҞpMذa拘201"2L%E.ܑ aME2t`XIQ;YrdJ¿zSI߃brYO&~o'vp?oX%>HߙM˕6䈒"(S5meu#+W?}sLYL:{?KɿI5bdo:H(]msJ]Q%m{C׹+0%v?JjGQ\4xR:5IQ=;|pzIW  xkwh?/t2(9qYvLV!A"CG2eʔiE64hРI3Ƅ<7,Ϙy˾CĮɳ>dQoy~<}AgP&]|?[ tOlA4EWϗ;>ɞb)r Xfeʔ)ScYZpG 70יC)81LWDsZ¤d`f SpG ~1LW5_%`,_޿J87Q֧&k%h~x$ڈڕ[%r2gT 5kؒcv$엪"K&=?KɉZ5ПgdU^ۚ4cWb,O^O'ߣ4v-ʷ1X({@My?ٙ)8l5z3Sp|?(6X%v{d=JW_YRɪɊI?zwnQQ#28QTC E e4#5 /~=%gYW8Y񭊎j,Ҏ/JhM3Y'A3X5ՙ2*!9Hk12h1(Wm4յ-_F36PwkOP,ËƓ=k&,5f·?K4%GÿH23U'mO/=Z&ʓ,J_l'+>D5L244&4 zV"/4J\+qW {QrrzzD_gG~EQ`/_O^׋GDyDqW!կ  czvD_/qG~K`T~`0؋jqzV;tDEum4QG&qjQ]e044ZtRrO `0؋j1wk6\(R->IrThxmHښB1 OA8D]`FCCC0wQܑ. ྋBp=^T;5p?GY ^jgggggHM܇Ì_4 ݯ3,m<.Ec]mH:+|տcm>wDӘ++g=i>s~x]ۭE] ]ZKX'r_?+c6l"BMƖ,V3͈{/${Ŗ}  q:k-ƮN %C ZLV`ᩞZDHT9q 4hФEqk#ZkBܚ$/M/IFq[4ܡJ:{O4#Y:Dk&4חVh;Sֿ\տKqxT]]TQn^eOE꿱ԗe5bk&-yJ2thO# *ҡ#o1iGQlӘ"ZwK|gdy,_fG1!11ŶHJXHq' 0!g/'O4܁v;bAg g \j dEn )uc"Tr%OaG1?wU ( <&S{n 7;jdE.0ܱfbw wTJuU5Z_ZРr>ϖhaKpo+am 44B.r 3>Wy +@ 'XX;72M`ϩ g*n2,|7 kgPilo hpg&NhL4ޚ&J fN{&[FR,{,oKH.))0N=[|cT_IqZtR(\O'%R&=i¿$.KnK93쪦\HtП&9di:˙&&!`(&?l8^Oȿd~|ON&dW`zѝKpf ^|,hd[Gdd$!LiLi 1fQ(·MaU!JД)ASxN S\+=yZ% F;T$ʓL=/өS4hPNj0A? b"O/uN&ؓjZV$Tب)eS[܊VU%Hd}hS^PP(QĠ'ɗ/_Y* N%ǛNjXU1i¤I UVi˒Jؓ"JthO꓂:tg*RRV_ړj,HMaU!J&Kc*$:ڄlMPZI$T|'ԩŢ]r'Phʇb+ǟb8Lɺ&̕st 5 zj#Jd E#;Ru'*UAsk_\pW5R({Rg֪[u`#)" $LZlfBFXьV3hKP,DsRo}k$e{5s˔]]/)+=e BJ),Eugɲ5X5J״٬--FJ)P Iz`VvOe{ÿf+JqYJb|1o+{/v/k$fQjD-BaVE4GlX/jKԩoo>\>ۚI%뗦"*b,5dEt@9Zɟ (^ԑF\FtJ@r]qkԋ\F=L&t溬~5A|/OB1(Nƙ, l2ŀf!'c#d@KykjR5g_Lܕ_pO>e vKv+g>&{U9 ]YץjJugfUm۶4Nj,J5w>;\ScE3d}xkҹui>$+JLUI}5`P*} ? A`xᾊ(V`4:2&cs9 * &98#51 8)*;[<Z}%a0" Joy?Uoyh@,/u:[*2Wy?gUC|ko]Q]ˇ(jH bwE_irNR9^~NC܁|pDG qE<考 di"t߅78G SoCJ+@'}g9N#}dӨCnXJ$[ ϕOU{F@+Z3mKNm(O䷦*搣WzQNgy}#f ΒCYZˇ_X$?DM0jau)TxSϗ_j=^y s9;?u7 2 R[3m@&U<{Y[Cz'p LZδ_& 4hL(P8irndb[3٠(toD/Xy#{7q??>/M]@ Oʚ&B $_ۺXZZZb@t&%8Buup#Xy B ~sEh?pAg*4B¿=ߝN7 ߆w@ ? G}?G7 <Nw_G ??G 9?#8&h#5_O#h7#2o 1?#6Ew\EgXoEW 5?E# ("&‹h1pG Sݺ"9Z5hʊP/?ggBL6VLgA{d ={&pK8tJle*\fL(ם&uL`:8)+;8)&GL؛˄"]3:e:e2E\)kGQ<-UrTg3םȞb58H%S1i,iKeIcfKL34) {-\ߙe/RUϧ\xNuNuYjWɴ]ҶvyϧBp8)=F>+E/Kd5Dk>: IuMO䥺UCbQ_!%yn53X5=/%ynsmF9İ[c/ϭlL4k;7K(*5CQioHhxApAD,?Dsq|܋֬=E<;e:地wҎP 9€J:s(H)GHU1|p7ҍFFFFFJ?[~5Uom\sKQ]n$zjF4eG4 9 2vȰ3ƕJpwE_N򢧋{۶-;[hfqWXW *$N4fBX!G (vy"tbx&LgF4eߣ83qn]?єMתє7K\ܘN3OJJ(Ljұ LYWQRKHI^fRXEꚟX*qI^GTχ町I?v[T ~G ]q>OxIowAI$Tuq]3 (s]3 xs>?d}M7ǮSG$/o9v;5WV?ފeYѦq޸H$/M|+'52MK[ǝ XnuuLXSt 4hФVVIPd?,$yi*As/DX3`deUPa8b +S`FrV[ *) +9&$I )b1ba1In "(75']} 5K(o=޳MfMo7j|*7jRjua9Bl$# - І+fAb5J,ʹt be"vqbw i@Mt $~]hݢͳ'jWqcBy0d0d30nS mBox ZEFp\bUDY%VtJc'+6&aj~T zEߖjǢ6*#&`[` @,p1~%}ZGX l#* ZJ&X󹴌5v3G|-qY¥o6oMoF=}3̉Hb,K!XK 3+$*x[A%֧ĝ % h${b;5cΚiU(CWzFDp~U&8g%%9ǁy|9 DwW(qy]&aV>Ekz?>^PE4S#:z[kq3=kRD#ۧRoqTG!1qrG)ban7Ӟ˫yc& _?v0dO38pޣ!2 M9FL&GzY/`]ӕ:+fPm[j+VY˭~tCĿݍi"j61g6`ih3"יUHh?K/׾VQ,2ɋ81%nk:Gb;BWI86mGpK3~܂-mqZV)!B{zK#Ș w 5!Ϲg Yu0U!EI2/brdof>j7R۟WLџ~I9KўaFyшN&/Hб!hh2ey3B67u{:םZY TYVט;{VOrDF,4(V }=&B;tQTL7jD`0п)}s.vC}LyY%ed/B5NKBϿlDpgO_wpM[UOk %H$荌y{ i{Ғmmxx!61Jds=Z10xSC !q14l#|KB+Jd}6W;PCZxBM.9@^#;@Ȧt0#!@B#l'l \v:u%r&S:f=Qa5ؑ^!`9acZctJc2 ni׊L-ڥPC_%8+֤ES3;jU[J7d!A- b~!nX5^MW2`QɷEsʲFp_bx tU8{cԵ /W3U.yP x&bc, p-ClIT\s{t p#R O( -děd:=A Q(ZRiYu.y()H5Fje_PEʘuߜ0.kL|Bi}ǪlN5#b*=L ȱde=XxvS6#FilKSkclB)kniLeziS-ataDPNG  IHDRա IDATx$W't-eFKyz!F .[^`{2ӽhqt eB`[6HƒE/ނiQy%rŮ_ǓOVf|dDdFd^!=ʏ8q뜌|׽ޞ xǎ,{׎ @ @_nmuk @ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @c @ @\I @ @`o" @ @\@ps;k @ @uo-lL/#K;  @ @~^Ox׎Zjs  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&-IG/x8hK.~sgo\4twW߸K毚?>};;v,ǏwvӉ7ռG @I HNMH2uv/rrA`y\.رwϓI~Gϓ 4 @ @`Sl51 @ŋ^/t.Y7E$cgH2ĉ>u<)8vI @!$PU&l,<7^bN5߳`٧nb5 @ s 7 @ Ɣ[21'O~d_Gp:'@ @$w%o 0?_?N#7y\/𑇾6  @iTF,|_|_R~iR/ @ @`n? @`R%A>%!y+3e  @؅.m&k㈿f,>twuO @A$aU(D 78տ NN~Yw8| @% 8-&#S|o_̾g&Ё&xۧ⠓C+ @k$5t(rʯ^iH @ HoD@~F5kh@If^"@ @`s I yOBkiF8Ml @ &@鬮YOrO2{/nV @ P!``Y P-kzvNM!u`Y @VF0Wf-^z'>?ގY= 7.]K.͟LW\^p=vݳcǮ]?<6_>mgwӉcǎn:A @F/ 8& '$ʩ ,hw L=n~xqD/Sr:eR0 @`/^O yE`5s%!$ Ɩ\D_<1$EӅC:{ t;v}OW`~u2uF6H 0  i(  $2Bi(('DPc> Wg}o| 7M}OxS?GOkYvB2 @g AG $6Y?%%3rĉwT$-W7%$ZƥKŵ 6*9Swntz~~nu{髛L]ut2`bu,y2 @ + 8  0s{w"sz#u볜 B=gǒ벁$jJ# y.I$+6M,n#eLnޔowyz,7HeW,+h|I @ 0zZc}$WN 6b_Әy{,x$iX0ɿu#<N.]OY_t& @\WD pXjK#56#>oXFnk_Iuh\t\n>BK/-ѕ`$`ݗ`G @=U5T 9l7wF5]+0K7kh'wiuM ԓ)yX @`m; @`c4G8##\2]C_ܻ7xw^'^ߔ @d4.Fzm%6k#.Eϟ[dI03O~m^|2$ĉQ!: @* 8֖ȼo〵fIRΞ}Vॗ:O %!4ivwMw19 @fN  p`85?阛v'Oܴr\^C ԝZ @A xͮ@N zʵn:73;6?uh1˵ʩ}VS8e @`+^ry~]W0KfT)S6{+H6B HF LC4$hHb y\c[0(] Ч@~/ ډ_tL?i,N@p8[% @`tCށT6wb55H4#291LC{7H;5r7^C~ަO]Q{Ggaj4MH`keMS [::5`\w`Aۛ$quդLQKUqoر'v5yw} $UN~iGHLHog7>~7{ѯΜL"pUO, H.xM=::k_= `䝾ЇKCkhIMeTŋ uo2?1"$)-oM IDATr>ڤL>_1#vZFa= @HO[ j<7ŖQC] p[++f“Xs?ﳸ/+)~ߙ6~l(}nN >g/:CF @2@$@ =k'O~d-ɔ!cH>箘~r_{ɿ~8B`'B4 @e#E&@ $EOB닱Ly.TػiÍ@> /#'f"0@>?moTiLW\tU $ f$`WL @H s0\i3ɬ]3*] 8#$TV@*JVa?u*#@p 4* @ C[F\`ȄN$MM Z וWG]ص[  %0aztb,S}~b]O}@2ak+'@@+W6 ǵNwrM=C @#}q="  B 0~!ot?ڇٽ曕Ǩǿ?g>a|nοv҅{Tr*g?ǻ8(Ts,,EipcxM!G6i :1ӢuU&@Og~},DN _FQk>NnEK @`#:ݧ'V_|/IOy0)| @਀Q @` ЖVa^mrMz/g"/ʑ~MX>}'$`F! @0\0]  ЫWsJ3tSv^&@`;9wim$^xM%@=ܳUz@ܼq V?yCu{PE1 @@C$AȐ3 e1ءm"ƥKUvV 82cvV/&@ǎ_]!: @`6oQ`9})ݺPw~LT9Twu>r=>!h3Ne @0ø*2/GW)4YcHc>hﻮ#@`81_'?i6?\?R2 .Sh%1 @@I`CXӡh#N?M svK/d&Is`IOkj6;?}mf[Z@p-`ؒ7:%lfhO)I>Ow &`M96Y:'?q jM'Nԕ+%˨ 8x-/?>h2Ϡr,Q}B죯Ө,E$gLǏ' @6:Jo #,ҏ~_Dm }m6 @`NG;[4$%5^cHE`F n׊&@ 0n q Ы@&8dtTs=)hXCۃX Q: Hv:$pĉ5 96H6qױ6=٤^!@ 0 )  ГGOYO%UsW4֡!FmsE]wy& @1 HUDr*Qb[j3ݶjSx/m$nj~S#P3u[ @$՝ԭz'k:/\;cwNn^sEUKjE[2 @`ɭȣ_&[LOzx&w)fkPO N)8\D) x@Fa}-uo\A@?ٶV9 @Jn:St= @^c_ {٬*EI }j%Ent۶tv휡A)^[ @`xoum  0J Q6 0][>5$9xӟj/gI6ukPئ,K! l C6VwU@p-#. (SC}p5 [t龷~/f+6lzlSNK>տke  0 |w5__!@4$n&@@gs5?[f|֌9wɓ&oosLDRwΏC&8\ m{5'@vyhxѯ̓dtZpɏ2eGۻ3'~f˃)@OW\$, ŗ؉P @0mO|{g;oO=)'IreD`;/1#J={ ڲӦǏ]n <2`2}]vmU%x6M'>qV$@ kUPT $NٷO+*xWߙ  R`b z8Q-xjm1$jls?12EZॗ_u]7G7*B=p =o`#@@@X\p]ՁICDKrQ&%Y=njS,֝LM:w N"iuItegI2>;v}o5ƴ7Νe$v^ȵLwߛ0(z0GLE @`mF:T[?^`O0{Ӡ)+(<&闿-L뮉uAG'{~+tbb> $)% 2b;M~HO|[aJOumcˏ}N'z1*0pX_ @`mGJ% Xxt௕<`5ImMl+$D79.q1ɏU}nS~Og]7ڬSr${U%iZNWUuI|Ō:us5mƷ~/zbQMf9حuFc;?Em@9Y(WGyWm_\ɏo79tL?,5}6O4=r'3g>H%I+eTf9%2o:'\`|2z[>7>;l-מ+my?ȝuVZ됖Z"{1񚾟9~$۶  צjDr`iG<{G{@?y}җvp4m.Y75~):SK܇x~!kr]ha&0i̍xkmGU$o"@p a @dRSrk7yyN`v1)ѫڷH>R=yrl+5KD #-M/ @`oC5 @@oe4&OF7$q5 Sv5 9JKzkS$1u|6Be,QGD2iJP`~(ڰV#@  O  @`6ߦ n$_gwF̻~Si]lԈ+%;G[҈f}j wQu:m;U;LO@pzm&b .n`.# Ͽy"nQZzӻ.^}pHX\O/r*pȴ$z7V`\Cf(۪9]l;6aue76іc "x6_ה9HݷpzF|x[ȢѾywygu5]1C@p Vc ~0smXFE(սMz&23Ok6Nq"Uܴ97& ( 9Yێ9տ⿯,ϛr0DˡN9}؛Ss$s_'ߤI`~L*0O?7i.ϥ*L/`Hع@rlsM\,71m.PvMkٚ9E1vڔ_F5Nu.\~ ciw_ג?ǫ_/>  @`j @@(^-I_Ï~By8Y2'_ج$&2j0würk^ry<UΤ%/}*wʳ}޿vuҧo1>ݬ6y˝yk\IΦM:w>kٜ˪rZrǔ6?>O}dm^R|$YJ&?׺oL׵՛漷N;V-vͼ$ϖC_/g3?%Q׬ڎ @`{o?&/#v8$ O9tMKugNL0 !@ @ MH,wh\tU @ܖ @@|: ͈H 8 @ H  p6'6;ӂ龩QՒ @$*b"@ ӂz7,ukJi_V- @SRk{"p;ȸndZ;v}Zرwgꔻ& @A@p @f?suw 3$d\w3ĕs-l @6xצ+Z%P~q=٥K$[_oR7;v~0{f9SM @uoz ۑ^ @ @+z/vN1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7 /t IDAT @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T z @Sxfoj{kAy晵տg7I@pL! 0H 70nMxg?.ƌ $' D8C #eF˴2 Weث:Ko]OC;p;Fa\%< @!  e @`r-s^.#<Ɉoh^V gξM-`F/r ?>>t鷯 @`[n-i!@(\O(۽=Ԣ9:Ň~xLxF5HMNYOsZi/S̩Їzq9 /0??n%RWߘ&SW1I˗/Ξ=j2 kKg'}]OAOoҟWV;c~Y.)ovc}b+oJ^kV>~c>>\ɩC^h^o:mRlI~ 3~񻢏شnm[O\JRN1OTV琺f>\uOxo o/m"@`/N>l6[wvٳgז_o7|oFV|vl],s3w 7TamÕ/GyG̼)_|jP^g[m_pa1󺘮Ya$oOR|>܊c}h~Xs=k_\짪JyMwܱ~M=smON)M,fybLה(Z5Ŵj8iղދEtn$1tNggz=#Pw{}A2m&|sMڲiml>j*t_XS> i쥟:K]ĖeJ[V~v&},eģ*mR6Zil[X' NXiSmD0m[jO!]Z.N9Ȯ:m,@v:+-[nq{7G2V$R@G ϥ|FIRK]P>WIfc,3MXO4iRnbݤYXNu>PIe_P>XvKڭ~Q>}w}Sإ.m= "}NIjlzYGdDrdzr]Cڭfy?ۮ|ZJrSuy=:.uS>_}5_}ƙܴ߷v}sXI籏>$]E>C|l򼯺,o++?yC~l)6l*  Qϑ9/rúf!9 q@ T*}D긋ܬj¹AǺ&#_ӟ1fH<}%bJ賮}{r_P}Ӯ}392ϤRm7[eC|c>*.Sm9q 0y& &wAEuSFfN)3I9KfYϺ2?ɈDI%69pB;p!ULٖJ{dW~ls@|̙E{ĭ'{ fBڤ?M,%z7uNM&ll?vԵgiU^ܚ Sm@>-vF }-O/4uSNZ׷J9%e[%6ͺIDeX祼!Km#m؊iw{ԽIo+~:>>_0 J@pWK $V5Oo]9Ν[yg ?$R޺).uW(',00N/bel);gOdj22/uM\IwJKJIl]+%.(s?/$鵪-.[se~Uɵ[˗/GF/oyL3X.2k+I2/ugfM,c]5ٯKfS~ }n=S^[,7$&}j9R^P[Rždb<떫zվ,e7!5x]X>Ӌ*ˤ}~>1Z5{[n92+T}pd?XrI0WMuĸO=[pvMӻ@of{ok.m{olɔ|ǸMMVʾ~Wꊜpel)3曵;w6v?xmykchS+cM1_.cC=Ԉ2Ɵ}ҴWY.ƜcO /T^p&MMB-)~M?d_V7cRV>MLgΜiRM)wO6XlV=6mϪ}HXک֪X{Mc*۫5_XW>_Mr^ 0k1o9kN.e+`._~OK}Tu B,W_5`]vrnQ"%ӢH,uvnB~Z5R1$VMuU=/;rzW6IuU٤?*Ʋګ'mtgDM-c>-|fҎUHK;WMO63u#plnW5R*uR׺mu쏳lFok6,_՞UV}v2bjjʪWU @`Sm9q 0IsZuR[K 岭,_u4Xmzr@.ICմ]AYWw0T!K$nf_ʫ yԵ_Y7Ho3]bj).[/H1uK<\ުuul]0˧7֝bZ֯Kf<6I8.6Jf|nkI~F]X/t)۪s@>6ZF˱ĺ)| @`SoA K"/?%R&0'yl.7:9x]V\RV]/.uru^SCQlgʽj4oKܤ.MUq&xǛG51o~PJB*嘲loMbrܺDlǫTeMu?mڮ]7}׭}!: л@2/_F~%fJI59Jmz?Uv}su1:TY|w.y1-zz]K֓MFpI]bzz~վ .ѪZ}$oPV_}"Ե{rc-foDQ¨KբKUKE]r5D@pOR5S$WUkEwq[ꮑ;n:%yZwJS~gaDT'yU5KW>u.UU#E]ٙ_U~Q6_]yhZߪӦu?ww{ՏSA~kܥ>u-I2uQUUXw?ۈUUJ5I1FUҡ&~köI{6O_OdQUI]OH.FC{L*XUcUMuu5Y..wu1cn]u]]>_b] I HNM`.is^AKunMzc~ϺK}VWF] %s`6)nT|<M2P׺>Qߛ1rI&k:净CIuuN]?( }XNg3}"_פUw\z[uuB @`@k{@q4*m]WZw=uU!^] ,]oðWv3me#yMDHAyreCOSiîɿԳnbHl3wMl$u{Si#@`n? 0q\eU&>)3ɏ)pGO0wq*, ݘ_n &О @$FmC@@w̙*eg?er +T.c&6u1'6%@@O}?%\ΠnMkc%U @`7qUD 'WuMF=~7"pBeԭ{禱xL+w]7C1ǘTڡQU_U2.eF>_}|n prеWylAHf(i9zp]wPW5B`$ƪluEmX IŪA٪un^fkmo՝V7rYQê}Gk߯~hRd?CٯS/zgWk/ ؿ  pD %dwsdSM]EY꠮@+tJ;V%btI uM,-o.XSiESu@Ky=WK> UJo8XCJwh^UoRۺy.0T|g?mmj.U.A@ZY ع@E9怳%u]:omk5r0Uu@UuPԕSrIky;M^7~YFO~ڄbu)5U#+G~ u彺}A~ h(v1Jpls_Pbcu݈74H&>GķIz;"G<ȁd]ȸ$ AB躊~xaJ$ѦU7b'uK2Ŭ`2=I"n; [&ןA7ܺ%a_R\X^|گ*j]Wo[ VOL`9_ ʾp8X{\ cuɼV\ަnԩŦ̪|V}nҮe;|" x(-\ u&6)/$WJ\Ibu+}(ŕmd4GGne!kbe8C6)+9mQڽI[cԫ0Qw#$ǫe+,7iQکCڲ|&ۈn_~6DU}lSFIkgIbmѝm.]8j2U&,/S߉y@F i6;eW= @Z kMCl~]yg9ar'n$XMJms0|`>pqA 21I&ɿlh&ݪD`9(JuI%ژY6guS["%uJ~{'i4=pMd֍Ɖw[5#q&Iдԙl{~>:-QO+LZɾ nEթl{$p3/dl|ӷc>)MKI6* VkRu"s{; uYLK:LLEືS v qxǎqok/]L?^_)?'dnF$Ӓr}\L|1VU믿^{P j IDATTP9p[&&5gƒT]&dr.$>K1%ƺ,Ae9L[Դ+U6[\6VU>}aq]OR~ >4wNm/i/Hw>i]$i6qRWKU=)… e+OywjZ55+Mk)wbe~Xڤ/ns/z$/vnRc.zȨ$?9Xhʬx6?]h$ 4id{9p;[.'uy'^Um\~ye/q%aQۜPhK\cMgW72m&enӥj[i6骲ʼ&Q,cH6I4v/(Lys뻬iLMSV_iY..IBWn[Y.r`Y1?I|s$vU @`NZ ɪ&5t?C6S&ͺr?Emk^&MM\U)cUB!푾T›>i7)I>?}5rOﭪ[F6m:j|Uy^<|}QuHY}~)/CMC];r  Ї` @@K$GmG%l9p"w]8_tʺ=Xm$vmO]ֶۜ,_̦a6cuN|]]b{Itu_[]vu_ɣ|?]=>}S9}.Y>_|ߵ}KB@pUe @F Mڌ&v_$r5"]/딲(R֦ u[?x>T_{%ص$ 󱪮_tM.*{ K6~ӒNZ-}7/h'ۥKh\[Jl? MY'>J<ˏe~^짻NcZ? @` !TIG@ ^'h';W5=A5kI'总]R׌,{^{- d%g4ZV)3M[,imy\":166}AI6/le:}A}쏋&DxqQsmJhLɏI6R\sl/ dUI;,eh_*  ts`'@`x.\&1p#1u0ofrguSrnJ3.p#F֕q&O믿~z&9f\q=rP1~e-~?i˶bu6J[; 7O]ӧnZ?/.S+>[,wSC7X5DR&pn*O% 88$$l^>o[R6C#궺  @}ȨƩl׶Q/ @m\e  @ @LL@pb &\ @ @m$hY @ @$'`%@ @ F@e  @ @LL@pb &\ @ @m$hY @ @$'`%@ @ F]m,fΝ[؍7޸v p8gΜ[++ П`J"@l{w;*I',D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @F @xyo=ܳO?={뭷f7pܨ)vv}|ܹه?)i~6{WUw귮^xٯs1h#3?c[C;hNw|?XlS߿u/#!;ڋ DA"oOMncz"&Пu]7/W^٨|\y7*cJ+ _Ûo9'k9H/Ar-զSw3wIMC, ӧOϫo1$Ӷ9j1wUoY~<”}gtHRgYHoڑ^T XcWr v׎)oɁM/C%O]$" y4l6HgqJI,7eW//?|}0+#'֭8?# M[ B.\6`oݫֲok@u3"%i2*<.ajTK ?t" - 8tiq4_Yn{e^F.xNi$y]$X&k%QS|WNx*q~8ZhJ$@[Gl[(92X A\IV-A\_uз^m /unb+۬^vYrJ٥~Dz\ueRN2m*鶪({QFSL}%6xGm]wmS/#[|M>.wl,W\a&lolW>q鳼RVz6z쳼RVʼl_(v},$m:2VU)j^Vߦ4]CN)Sץ/W}XwU8 CiiX< Isy|$JҰ|-/MG`=ok2%RnIXl'f̪)Wb[uߋ6?ϾcΪKRcyޤoYOZ_:g[e*/.ibZ]|Q^_UN?T깸ĕ-U__2T~%X^{iǺx֕Ӫ_S#ur1).~excii딓i]{.[N{6]gy\o66~^loU[emG&(5Y7,ݒ$cyywϲKrg{{"Z@XY@!tKZpxVzϗ[>OE'~{NmVN6jhбgG9>jؕ6?]SV{)SWdyvm9\,`CL-'D/F҆zmʉ\'_delnoeD)m׸6ʡ,~~Kl|_Ԙl/e/:^imCʶ8l\KKb?zcs vK}ysa͹oa;)7Ie;s?J'׿=YgP?W={_tm/^xn1?7<?|xeAFmKul5KWw}>OGl~xo;pKG}@,nLgVol^gK^t2PGF߹)b;J[>%|mOJok=׫/Rw]a{RlJ1]Oyxz<}Vov;bw<5>{}Y뽔S':h/_M\6ʩmeږ#c tUV#1[Rw}cC~9jSGydOM5.~ugoΣ\m+5O<= F>bߖcVQlY+kh[q6U&w׈k>w+:Q鷿Y[xu-1[t<=vyB&w{Ŕ|u:dʩFrwWYӟ^zɓy!??Av)f~Gza/Oؽ%qU`;r~瞘cpBwN 㣀M$tb-w}tMEؖvK> RM6.ߩ+ x=<Ƨ|߹ ?:6̟UمMU^U×|xd+Ws3YpLBJJL^lW_OjĄ~|c[gU:z;Fo}WWo-,%^$tCկ~z?u9S]o[]%Rʶ2N!篕60l~z'38)q~߲3=YI ?ԅ/WM䝳eDŖ1H83F|`!:ҟ#Rl^XtZYy܌%c'mիWVP>B| o]cWG^ u-q=dzpܑBVLtt@}w>wԔnf/g}!wzuR(u k/*.H?˻믿H}5_nf=GNˈ^=Y>|]jd{Go_S_{=ʡw'ڗ'zr#y>ޑ* >GYȫmv,R~V9zWgS|vf6:zD̟UӣÎx?ף'0^2c;Wr񎼚r%U{{p/b~l2çW_W`d^۠GU.#;bgY\O<QO6x}JoLrkĤ\oq'6{Ąa<7y{j%_FIOA>s7hX'jp\-\-kEM[6y2 vL_g` 攭m@zQAW{\'[*7F&uU{: ^'G|U^\ՋǙ9 V~A'1{zӋVR|WfKϿNȨ/qc|ǤT/?ןγmwQmڧ;N=%_VٽH(k{Xձx>ͷVu۲ueD|fMՎ5^#sk{6V9l1C-&qOiSRo:6jCo+k~;_ŖH1fxzm;#Vuf}J>HW)7Z;m3#DzKd=+a2>WF6ZǸVˣתUO _}wQO$y=P^aH-5ZgR-cɏ s%tzUQ5QΣ䟥ӭ6{Eʅiy5oo5J; 9J, (+xm~|2Jԟ@Y^{=(_cׅjBp/3jWޯyԋm~pS ^<ֶHYzWlN֟?b [uv<Y9^ϊ{^'Wu\;zRk[乭i|nZefZ"7p.߫o;8f:ڗ~M8F>s=AdT{mX%3[^hTχ,sNվ^};Wokܫ+76|g7uu[{a\>3Fsm[z~{[7y! T/: 2Yx `k>f *=zA*GW8ؙID[oض{:l//z+ezyV嫋mzak9'ۭgoqѹP:;Uft&_Oٯ^K-ٳa1 ).ޣV٭|mwm%LNIܳ7ZYUʸCcak+LZ]n}q#j7:>)u:^lAme&R6lk޿/E/dkLm+X}Eo5nj̵m{|s=tN <%-u:[܁ jDz$d 2i'mRkn+c)[NaU^eRUW?Wʷx5?z>W9ߋge$fS.gu }G7Y[wgu\\@J\\ҟp>Dʶ?~Oj#3[o5^/h՞UڇQGm+U[k*sf7eoU.7\;6\m橌k_Ry^k85vaCбygyZHrs86[+eW`1,zV+gdQVo_;} %QLK, H` یÒ FݥX.y'^E@Lٯ߷pG|8n:L<b#=ޥu&^߯mԃ<1v o#{`OέrdAs?3Ǟzj?_fOt朼{[++`5QfXsolv.kV_qyz-Wm-1^鶚W;%h*C'["L0и(1Am ~ IDATԧBx甋:i^JKM/%Z$WXjKoNډ6O:䂞sh~8$#>&œgϪ9#OQ>I<.nW.BΩ2艟Hc{ 䋿C8ɹԧfGrprZѧZ9uoOC6|{xSv9Y}[2=mw3?b;E׌\dDp*W8 '2A x.8wz y!B.)ÂB.)3(RޯŎG7lcrP':ā9ЕjO5_jׅ^9Qlj$h+[$}m[eԧϓ,^nO9?k3|T^c'9[8'Л6x>ۣ[!C?UfݒWoH=.n.gHkK{*k^zT9۾|wn7Weyz'i%m%:6l핿Vږh3ўyJ]GγÌ5RWOUcVnnl[e=d3(< 'c\Rcd `.i/~ 6%:b=F.b\ȄSs2kϥ-?$_GeNP׶.rg;suMAy.F==Ng|C.XB\F.~m/?m/1^ef5v"mE +:~ps32{WncUOLf PӞۈQ_[d*6†K15|KR Fg|Ν#o6O$ӗVȶܥ︔-6 14k_?X&.r3N G5֒ߵܜUC33[ S? ~V={Vzc~~[E^(W_^/_>7t}W._~-}ݒ>]G mݽ|襪G5f*/U;:lzmE/M~ 9OwjE't)/^,qBy{i%&S'{1ej\I3V\֘vT^{ϩ pj1&)ͫl7K=ݨ;OM5֩we#>gmaG߶EǴVSr[v}E[3G+uS'me+N6cEF^tJFそ+E/yޓMLٸnGԵeWTfq`k޼7qWLj_o?lRO7/kv9՞Oj5mtVk1컖+|{5vwrj,zlV/wyzz ݽ^]1[\Dͭ>w;/TεDgxO,{tw9 SAfN׋!Fiԋ%.GmߴSnh/4-yEy {㗊؉F}:~>>aG1&KA7CEi#)h=+fct_m=Z K 6ubvيsjԗz]}m [ Ts`4/)[f8WΥ(yу|0kwsO.%xU)ud}~P?"=<202p]J7.xk(xRr메wWwG'~9۶}^  {QcT5cm%+g5+c\ &] rˊ>e[ʯrn.]o l9 xRp뿤K@$  H@%4 H@fV<ǫOab`]{yj$  H@nA[P H@[D:ga/V8s"75')a?!*deaŹ?t2I@82 ȉ񟀜$V}%}7jh$  H@xGO@9'$  H&B$  H@88 z$  H@$  H@C=,+ H@$  H@$ pR= H@$  H@$  !z$  H@$  H@ xp$  H@$  H@pp=J@$  H@$  H\X˂-۸CTsm8 k.B}mvI|<-M裏'GiI ܅Vw1J 7y7# Hl.΂<pmy:Yb]Ih~쫯zN%k,q+;~~^n?'//5@Ks+w\hpÿ_Wƍ^`$ }\8 ^Ȏ"yS.uy$,m?p3K@ H@x/Oy. Y' H@$  H@|p o<-)sE&^} ]ʎs8R[uejߥv^RK@5=eD[ᶲL۹1pm#\[Fr{u;E何.}/.2zq6;vi>w+6ǗN:R>~{_BNl˭*|S7б0'_׏ۏ?wKO^dQZY?oM%=T-g{szV+2:#2?}EvHًC/Z[aclg{2?͏Ng? K^L~n+ߑ,Ѝcgܞع6WuBmcȊlWe8 X;UhkؐX冾nʇ^jc9fSftf;JuJsbsT "7 }z]v5gTw.§gw(l~?T㮗Gxz_2nOd%}%􎜙>iԗbKvLٓQ}q9"b_3;LQȀ9:N'v[YT_>V;fcw8f^Vimh˜=.1l6^"1E"~[-ն{0zzvǭie` V%x3| |?l ?}ëW'C_x)_ӫqT&DZ>?"y /,<fe}}=mZaI}ϟ?o>~OpJI//2|=V lG~6-9[(KuzWXߏDC5J{J5ߩOco۳Vg'w?؋K\Qָ1X7p믿ey=ppWp`'0nW?R]/o/ :[=zر'[ݶDY}ےZUQzxߊx\}mq~Cݎ+r񿖫||ߓ->UY3:l1ؐ2rۇsM+̐3ϪJ,Qh.Kq216&^򽖯#!.=Z[:ߊbйևMk1컖+_~kr]CC'>ӟqאNJ]!y<:v.(췿x)G6U> ЉwO MёK&˗][Zz);ȧ>k/w^x+wkѹ pRspϱj'/~’$es眻MHT]c۰`Oic|5Qti/FdT]7WF9?pX> ?RF1Y[[CCS籗kݪkLjeb-yyz'iWC-ue'8[tXR؍MpkSBp8I-䐗c#w/ɛ/Ɵ9/d?Qބ,VR+!Vs s)x N3>y7Iݰ3O3X6:#cXOY]Nm?so^L`{f## ckm}嚀 L2/UȓZ|_`6㰍Nؔqm,[ \1=m+_$5oR}X)g}oIz;J3'㏏uR(./w>)StsgwG6z[gB^Y[|}e]bme9Slۊ7t OkG&Q;ֽ';jdמ;;ȭYﶷ`ڦQXk߫3XWmJ %'~K9ϖzZ ?=Q0[WmY|'bzT~_~eN=n۸qW#F\QϨNܨ^~#9 ~a R1R4"^jzȫGcg#ckg}bʂ{'v[=~v̶ټk;Y~c_2=U7җlͻY_[YTz}\epN0L iWz}7GyTY<?Kyd<:0K iuĦׅj=WI;?Ʊ[혘H_Ps^ꃶ=\W5kvqgS٭WkmeWۖ#Jo5?$tr.Yܖ}Y1#fzS9>~zg 2+ IDATKtI[v7m{>Tye!ϖeKNߊs:zs+1YcM]&{ԇrU956o$ ?31GziLL&3Vb,m`&~R|Uʌέ85,Ge7)s-OtUVNY;k00;_^iջ;1'jjկ"BF@5ʳu:g~>[Uʖy GۮvWۭyVT;fc%kٜەҪV=-loIIwt jw_e-: S;jdk#yɟ()Ǚ<(]tbcd== FÇI lW&=9Vme=*w1;&wÊ}؀LVZZ"3`Ƈe˯ʯZ5_+KO;̔61yZU.{l&xdO8,H+~'ߥl:&Wl&(1_O+5xl=Նje܍>ٮUDN>q9~]qsOʵ![NCsS3 mi✘9F+y"-&uw^?O#S)eV<6aOĖB9dx:t9eR9m;i#$B?PRwMsb(R兎S&}7-h<1xt VmZVyRrRm[},{ܔm~0؞ߴu]U+}޺h8z|Uy#jΘqJ<8?5%>N-g~ UϩO+[|| *EHֿwX$.уy.ց/f򱕷Wc]_ΓY i ˪j=6&U3j/@\eZ ~'c)Y{=WjjG|gEʞ7i_-n/")؉Gmo~eQli,c4%{q}O9l8>B#< XtZ< kRېڼ::vR/oVi۷n;cc7sF~)r-r⛶+zk[y|G25qk5_井S'?y|YʓzN9Ǡ'N7:.NMu"3DFnS5yꅋ@`lm5fNjO'hژ6xYǩl>UÖ >nJ?mfqy>G/3tVsӷ<3ώ`n|`5WB>ms[:lCzwcY1VZUox?ø^T6S_DϱQ}OލȮSe[maMT=io}&s=K[_[jܝIsv#EKlWc nYLѡd31Qt-{?.BFu,.Ω;ep9-o6:ڶq⢧cms[x4|q7udKYw(W5qA097s궝f fwǣ7i 38nU}Gk_Zvl&#FrY([%H6NGr|ktKN \Ŭm|}WyF;.Hʔv=b :mՋ^}+m%Ǫn9o ?NW]e\N˫WW"7Gz%[ _gCy>i7Ա5)?oXbwdRW;.U;j>ttNL|Ib{XuE'SpVXO~3fg4DIdK#/*űV5:qݐ[㻍kcgįSbYcYq,Lކ@]aߵ,X=i~psk`@` fJ>Qj;#fcL9?1l|bcj|Q>z >{SʰeQ^/&Gur3bV8A^Oږ=_U]zLzucVݩsғ]UzW[cdz<,z ~=VDfۋ6&I駟>{MLWGGgdq)'V}uѳ)F jW}v':Y_XƕY:؊y8DMm77kmY_/c|EP_>3*E<:^X8οxzcɞ<$^]uK?gx#8e]+mȇ ;^ )h}J[Յra8zd%T gĉׅ&5om{&̖WxSdFhK̽lj=61x׊j9W|ݳ=NVh| 6^>EOB^s}==^{ >>k2Gx[_y]g5EX?kp̰>l=1T?Alm1:{1׫'>2Ȧ;'FΨO>tydlVKՕzRȄV_Q_9⃑?n+D| #ce^|!2؎N׏]6w[ tlMU2AT]BSu߹.ڸ )sʾ_+=Xu~oOT9{շWVR~mW'W=z^=cCʖ|YYWjcU:$XL>ÿY+:m7Zc߇O9|~q_ ZLܰ·X $  HXdHbd̞9JdQdkackz:F XP]iB^X_GY zn=-x"f$ b%&515$  H@&n8 Ibs>,է8?;z]x3ƢbcA.@rC43$ px>Q# HTz?@EU$  H-#XxW,ԝE=^+΂_˜|#"D'E{7yzm/$ 🀜 ȉ.'F 'fJ@$p0,d_O};?Ǣ#R'<կ^7VG6 >>% ]>& H@$pO܌ I==nKr]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H|nJ$  H@$  H@E'Ez%  H@$  H@$p.UH@$  H@$  H^\y땀$  H@$  H@ ׿'~#/~$  H@$  H@,c },- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  AIDATH@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$  ޮIc y1IENDB`1crSDSD00crSDOfiT#Fil(/Hm/Q@RʒćW||vy /@\k՟o)Ye*=h!+/OB8RJc6#"|jS>*pa«*M})BkGR~C8gnzKjb"~>mعdܕ; I~9=sor1yB=KƯ "(e:c$5u!f!aF̰ULb(=2y*]X$&;fW81SfV#s 3AP"Eq%RU3q_2&'p12*@I~$Rl L'򍵢%1Js熟&x%p.,02H&h\꙾KEaLoK3DdhhA8+ $>npܣDqsMHy HPCtg y!4`?!-%M"Bļ+ZjYSqÑUI-pB_?FJ &T0\T(*r_ %)K7ׂ!1lK*9Q#`&"׀ØKˆI(<04NyrDMrPS۲劗'mGaHA^Fq=zzߗk<{ȊFT}|HA) +Aw ,\8)IJe\Tw¹S`- 'eǥ-t zЏ?TAՏ?\ Abk &Q 153Qi)N:H˪Қ4F'fZ߇ kA+odLeH;(+!JҚ#ȰXbhY}jؒiN3Ӳޣ]\D?eUnךB4Kw e (̕ȊٚU {@P~Ι&PI&ZA;]؏ӁZ_t$NzV;嶚U+}#8܇ibϝ^אbbjb(;Ѷ->V++c{erτ8Ѽ'N !x%9vq,ۤ!oQͧe9#}~]JC|~ҐB qC>,fnZ66/v3_|~t spP 0Mg:?c2^#IzL۽2^/W4R(/^t_C=d0Tcç~>2kW PRW;G|_C}vy8[_O!|P(D-wTݿъ~ 5`$|!r wU(dž5o>y&̠$]aXԿRm,:xD83-̑{ÚOYluXQ+CV$ `̧ B@s,P&xSninT7RwiL[ "XҁS$܈,{R-HI6$AI7IDE+_мS*2Xmk'FER)^`[HsYCK]-Y*d$ H.ܔXt ħ Aۖ?A3ź \4Ecյgd))qi#*|X42FcKsB{A{274bE#m0QP}^Y8}Gjox ~("]I\đ^,c߬6GwiA]Z8 ^]ZmWf#WփnW\?R@pk~gN=e'#Fldit,?[u[bD>BG27\zu^>b6y= N7*VRe #BռT7V5֣^ iysVc. d ۮ(:Սeߗ XpPBGlT7f,уIJ<"'X2U1"_Ej]zmwܵz%.$"k;E}]BԼc!~v G_x#SoIz}BtWP8 'HԉFHutݬZ8(ϖ#Rƌ1ybUȋ9RvP%%nh#:7$/]=j.؉>(Hv,E鄬Vx|YU#eԼaB4BCt`n"%.G o^Υ<{M߳\t 3Yg}(bcXѪIFj8ۯHG2 X|Q⦬ H!ex>MUe)tQ\4y"Zp?,)c^]'J;! GӤnkE?0~:9ؼkB)z[u.T6@wi,4Jh2UZ] ;y9iO0ʎMoTwiEsJh%rWw.QIuKR $(w(RLЃ>ߴw(7vГdUsBdY5SQmE̓@G6W <5c6f⦨k ժ wNUۊ_M"u|bB6!jEPwiA]Zs T$u#2H  K`(%I( !cP'6 Ref^Kx/Gڹ"]>Goq4M& ƞ鎂CQqh1a9$?7t!n興^xp;ZH;rAqe4,j-F ‰i:} (SXv3=Emu4jwe]+O8I8R}hYP|E ݂:}RB'X&|VMD;mfG,-l$09/rdW8xp:];ScT'~38RM` 3?/9X3h505ߘղFn)?̀60@~CcI{k@7FGwQZ $,wO"Hy I7PS뼂QRo}@tf>>Խ^뎒ޖjU>1~'ه"5:Y}=i]x5>&X rFgl޼B za*Tqu9|`LD^`A[9\FbMw9#?RP0y_ 2~ZZ߻tfCRf$ f3` 01h/! 2 `\cأWP:_L{4YCe 2pݍ aJkL @-t_5r} +@"f%lyqp.&: Mnm6K6@N (>(39Ȁ_l= q9,lW p+V6ލ+aϠ+{eGkLUAQWFaOP8'.Un@H (3P I /`[wqz%I\%AՈfj-1{IPġ ( K81kANr  ##j&jpyb}M8pp8 k[3#Fil(/H%j]HZ@HYW@?oXln(Q^4}Imeb0\ڑc|ޱ+~y 6EŇ`Vs5/FY#I·ѯ"u*< zrDMkNLXKO-ooFcA:J¸(Ȓ>$^n\a\`vc[gWpSu t۲&ſ3+B;35[, P@3M +Rhg s+TAh߿k>*}po۠_U?a'BIҐv~/MqE ԥl-V74GjHeﱐQܗ'י::Z.oNc;;+}~̦ά3r;:S]wS儖x>!dUJ;]W*[m{Zٙ}C8V}aϗLzdTAE|R$m[aEw cŒpG`Ğ1ccc8%!% SʞIN{zˀ!=0ZaH&(%Z̸`DLD2*J, @f$AT%$Ђ0(8p4eU '%c+l0@u`̕8FJf3gP Q@ j@1-bp9yda @T6iq06?1"IXk7*Hy jLY 9`< 8x!)-XrBk4v ;n0 O$b(p <'tЁ #cnftA p10@Yt6+(C!33e%5pI4j0@xXc(Ne AEqiN 4 X 7IpA I/p8" afVc*! k p¸%lX ܀]0D#S2){3fq|2D 1 IAAX({uD=8#LD貰10bZ!g&e7*h(6mn`rB+`q 17e#[L)h&~vYlZ =t$5UZ@) t0…8F|A1'X tYjt4`Ϭ`dI<@nlj b& (D%*\Mrp\;Y rϒ ?BIז :h)d-  d "~ ˎ2If /#d&RQBRbE;ʼ^Z݌V1G a?)cA RBxB-S +ӡ(kv0[(L+8"wCXazocNdB&^<R(50oD1~2"ٮFRx.d(3qxE*ŝ79luDR^frVeTR}ypmG8g6/B7mEOWEkh,.E͵X1/1ZgGtW[gO_$ 'U!\Ԃ uҸW)~1CpbBSiKf\inMcAzx~p j?E`YGۿ]3p]< ""-\TYz2% jJKEs,;~o]^w/̀zK{ا=z3:?+FԂtv IqŒ}i0r2N LmRsKNQW3-4uh~p/DV-oK*qBoknk\'oz >]n4#EU %Ύ ֐6-Sn HlgrFgOU&!dHvMp]FwۉR"[}#f)"ѣ(}GTs_wn%#)oFv,/5:%|T.%ۀ5. R'5:?X5ZɼG|I*r\ 'Cn`3DS合2SeCodU`B6~:rR}Ѐ"@PWk?س%?#d"4F-LjEW#7Qﯡ#[Ad0, 5{2bPE@Q(>% 2cd&At"ƨBB?E,t-+߮u_1((Yo28/ "8K/?Ȕ{I^;$-!ÏKquO[RⅴăfHChm#}'$d7XEeI~,U~81 folcEbʠ}2YLLuN .9ĈD-y] 6Zr3%ױm*|IGE ITA4 N۶_^?Jd\A5V!yo¸ EsSZa^5,ti[S5!T`1zU!ˆ0}"+ѳ=GB %Y 5TĊѫ735nJ>hk;͕N}?}=ר$u&JћS817??ޏaN7X\E**i ߈?+ɥ/?3LTXKO#T[0u3t$^FE`ᘹ7ld6E)F2mb=5]%i>~R#vl):4>3P g:=3*xXA \9Ua|'@[l\9N4-#F2lqxpJm uU2:oF gBjC~oY"k qAPh㜾=y^BWԼO(xAM:hӲkY(}M׹R"KE虒PMʌf`nD=uZ~_J~Bo ]3$m6@rg!gh]gK I&u)9fxA81ܩ'ʂ?&DckK$<@d,Mʌ~=mK&v6Q[_ u-bE0l~ 1 wM^YKS9M-B޶B,ޒ["ž_̄V,4J@5ykyAPrOiY0/B&NKi6}߱& (N2j1!!45ˎGJTK ^_TUĉd }I`^cPl+Y7DU$sa#bkrl}})]GL$݈_YPovLH5Bb0i*OW,Ae0"vaPɂ`8N+ aɩ`Z2=w(\t+lg:r'stRwMC"-`X 3XXdH` }`z~ed 々g y_@P{vmޞ-@Jg90̛ x]K'ҋCVQ0 %+B>t Y!r,(80VdNƑϨ*RȈȔ I A! qX8!C@Q5TUY8``b h 'qn|pZQ C1#)i:V#9iVSEz̨dݍsH#UAEɰB("(]poM¢`W3m%p2z ȃo&9@Y8 O]cOWډ"9`Y8OBܓab?z/ܲ4@8$;k} \88۶Նx- Ds K.Ho~_gXyFYn ?Z(mIb~e:Pa KihksqסX]Vz@5ǵ1IS4=WmT$3b5tV*02S667#qti4;ٹrFUR TwZ' vGrr9 H:`AПZsw?Jb+:p6 vm`24~0wr=="^QCsF'v*,SЦM.vW-GW.Cge{gQٺ*/ ;Yy6V<|]w'GmE{!!EIUB&9osAAaIo7+!"(f^RÑo1 K^zQ(T$KTL4H0qRB5JbrF9׆#T#_ -D<@T^4v1]z@܊<`)$<&R30cPOF ¡(Ly̥U^LבqB  L#ĪbɐAOuk8h s+pGzuTVB f NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. dSaKqӕ| 'L #Fil(/H@O8 [m,J\B%T)XBJR*KRP U P` U *J,JA%T)XB%T)*KR*P` U s8?6m| s6y$#Fil(/H@OP?}#Fil(/HxOP7@`>)eLWj&A#Fil(/H0QPiI{7T+ClG BD\39址w8(MIB(p37_Xx 'oTOhՀ鿌,%0X&C} 2#&v"F}#~ *ީiW{ݽRu&_IelWЯ71y񲊺ZS5&7SJ32V󩐿) z* Tm:3RlʻNkkkEWoP{NwJ ETLKSy"ޟd^kek^P?{{Z#H=t?eHwBamTSqAUqq7 [a ;pjU a)Ƴ/+qnC6XH|%H%|(p<g#FDN:Pb!`C?zVzCg wv &Pg_,F ه*A-urC"!x&3cڇ)B 'qC6x`rLpst) tZUt4z?(\ ]}yy/BP;,prčen?SH`Tx̂:8*ѱI/JB&2DrcY 'KxYP~T|c1{i~XTP9{σF\diQzC K@RtwH"49 AJ *o "*1T "՝iN"W%::bLքu9ҭz#H );PMO[߲r(RV}?]֣4H-m!]7<- ^ƸS[)OeͿ-A^ctѧeԖD -V uV9sm ݲ?1uXޕ=8 '2>ʕ+S#W pz&QGM1);y3P?Noś=m`Þ {[ת&iFOa7K;,&! G` [bb\LnV6l8Wb;5;8Q^$V+5!a{)ۣ/O/h":S9?ٿҍWt㳊x7 g%xX6n oz'0Mc)rW^]tTW2_%Ja >&1O5z?Kf2E]{=%3-Q5[b,STH{J B8(P!]I)ڶ5UTQd<4և {6VfDη q0k6RRJjq_"<*|uؙ6J4"}Wo'{Bg?j׎]"oxU-JrEVR*-^茑^^/7 c.U- #RZ">]Ah胀DZæ=I┩ ֦DkXqp1혺g?-yba BD2)IINbEz3¡4`TQ{!-;lAZ Jv$#FS+bZ: BOId[:A(QD&L nb2FNDXP thg YYs@b̈nϦ':jѴ] BuRXN=lrB.M@5Zwkzmw޵**=ɗn1lZ`_ Dׅp~R܅'>i7 D胃z0]8A oZ`RD/kUjk15aPQ Ҁ\Kxe%G(|b`Jg4acDUX@!<4B5.B1d ̘p(;⅚ W 7(JXm jy޹]>qZ4PՊuGuk<}rlHs4umL>dirV2Vߠ٨|-q Q+@<ݼj@`mxFslC. 1B)A3Ӱ!S|8:ryp_-yF^n^aU JS E;Q|~v:~AMV$ЏuU7,f8H<d9ȴb. 7 n(ʟgJIEoA5N.·LB]EF[C#+V'YO"7ؑ+&!4kup!,[{Y.V [6_.=jH{9#K*}w]8% F oiuAC#x=Ynᜒ3JN]~-( \*p@BbK C6kMɀ عY| ; & UZ[<KȖIbba%0{ir/wX#M5Q#k ~mZN@td%q3լ~y 9_Bc l FQk4G8Rh|{l~B?/=G90@Dq"]TԭI V-98=hUiSZ!kE0݁J# I~ @Z*h<+#mA#{ּ~pg=c%WH D\4*3)K؁$kA:$H^30#mC<cΉsiaF~(J?.:*p0)A (36Խ"8hCmpoqng$N&)\F HoaZ^Bsp7g"u~r;0S|>( @ ȖOdk08M% lUĖHk ȰHi(']u$:R"Nb(YH@GgB vh)Vs6Wm3ILx/Gql9v gzb|ثlRF+etI~QAxA0@+QHyzÜH +jmxIy%o8i4xHϼ'? (x3(r` lkne) ~;sJ fH-᷍6ǿd q#Fil(/HJ\HZPHQW@'C P3\%.P M˽@BBgifF3rRXxb3܉JWAPF4 _WpLhK^qU5N !RzQ>n,UkHETDQh2@z+BkP q_!rgD6VMghWY **[t۲GVWep˦>K`KmŃZw4u†-9q|I)M %2D4y%E,˻vh}"7wuRK\ X%w!swK[fK8Q梅zqhnkc rW+Œnj-g>|ond\V[YznfRƦ}ljGRQfDc~9C:dwJ8ۚs r3wNA7rGs o(\HSXmop@ H332l" a`Q-[T2͂sQBF̉0AŒ?cJXs@**[D z8-@:F"\tBh,Ą# ,@)b| x/Ƅ#ȱaT 9a)Pط!)0O,`l,xlwb犱i.p92?h34Jb[hr# OCE hمx3&4`  2eA f-h Lg$f, 0.Q\@153 % .%!(4H!Pԇ1) 2``@ǔQTL *@%XjG#p%cH !̀!%` OgTK20TlBTRF~01T FSb 1[TvAr3a8?%hbM~82^+!Aj``?US֛aBJ<&iFs#3fW"L)!,"8iXe/d|ٻmhd{i1 C-1<.3EB8 ƣyL䎰Ül32],U$"qU"b{tU_H4wÄt3eQJғuK-rbP<-=4au=,/8h[+)^ڠ qB5ddʍՋj(JIM+M8ygX=|Y6Jf"Im;͕&V(>+Pi0jhbDaTyZ7˖Pf,?H\yI" :]c($R`SASc^uO.͙Ls^dZ5s|^4htY%+dzw>l{wYK,d{ANeVKw:=%f!zrޫ)!6z[ ՂDH1G|[Nc}|qP^#RpA6Z3Z$,XZçᾪ":ܣ8lq,vȟ(*>?S;mKБmXSk.ٝ넚] UNV֬otQ^nnt*դR$aqk)!b.P*6;lqRdr-X}:rt~0%#ц*^<]WJBa* ]=ћ1Z=ϺdE_7E&eԧ \EDe B;\;"r I?k  78qe$FQMcPe~ E[[>&I,Z٨7juUWKu'jdC8jk 3rVѩ斆/ ]DV10I"q#BbuE `gDC:XׇL+=1|$Ek4Kx׺b"\C}2K0ΖF|ʸZ]yRr d8rgL)1},ɐR|:cpX9T袊.iPG  ⳏcIbE[ ikIV5~˲9"> ߳Yj,9P <5z46 Uh1q_D{ 'T' on-tf禚MX`0>[LR֓BSUg0@3!M@|XY)Y?. YTB" (12LEDžiwx"9ݿ{p&_~+h2,vX{aV{Y,Ʊ Ǥ˱3=+1*8=fq? bpǰHE` {rс#tڪ$j~T'蟢%DLڡ /#G-VND~ }LO2EK跪1ן- shU`xiƅ#Ĝ']!a,3?zF~j ʸJL0a.9O>l%ysJ/x{+fW%;M*$AFm,%w]7j$m{: z+wmHlEAD!Y$a4;R[SE}C=hdֶ7Z3CT3-XW24ro(==i k&t\DoG rJAճ ֕ MeL*dSEH\4]TAUR JWx.[c{HQ3 8IE.5F}Hzw͚QyH:>Cla|v;^@?e,>'mx@͚a( ~nNE.jRyd8٬`T9Ųi mpRi(uN/6 %H푌\w+A~X:R)⪧8ju&vVX$&{8HUA/&pЪc=Kbdk봦MWRU1 ^j 8 (,]1|*@ <X&%@St7C8@7}LP2 IȲ)}ɍ)X=HLk=17S>lb;*XYy~l(&l%#h24'/ &ݓ"=[huWv{fj AUmۍy,-jvQ BbQJ#鿲y6a,E+"o I S&0Aß%pL>RɱEe5U */)q_T?Eb0:#@HZD<1E$L*"ġg8xVy2:\٪nZ'yB(V8WYMH 8).\Bp҇5sE},| W)n@z56U2c_O|D ~o؏=_݉pP+0-dۂv>N2!'%c'wʂ5B+[pŪ8`Ҕl0m'3xހ>w锨ڽ#/`2-st7l+ 6д6KQ_a!\ʣrLZOw@+oJ)Iɂz(*gҐ,dւ l%7V日/ftrC2- T<ΨbJLUK]@ u$`B@<adü-,*U] dm+:FUP^Xx$Z7pIF u@3}:GzTåt|hhdGW>%M@koSd@J޵ʈ&+N3*P<[[ y/Og"+n$0=UV^V@3/ꁗlMIL3+?2 0a?i<@%8= mxS G`Б eh |ϞP2Á)` 1/"JDBރחp +(8/Pkz˟ L<v(E \ĻuH[~ ݅dug b*9ݗsB vU5@pfb*qj #ш-D9atB3nL  [AtOpj1tF{7\U6Itk.'H?!Nx-Ped)ܠGPNCo` b+ѧQõ,TN(P\0y-G8Ј-a{ B&+xv+!ު7Oc_VkcN;aaKpv_j>ҽbFGi`׊9 0¿ϔ+jInD;)g(IT\l@!\tb g@IA7u=a(s%˱,X pdmFk<&8aưQ7(w1\] hpIЏ QFʓ[X;LolB{*n<U-YOAo|W("L =8%BnW261@[Àn0 0q_Q wm(eP i"xqOt9ze|܆39ܔp1N4,sH6:+ >n…:>w^QfEEo\laFԄ(8,Če ͍bjo͏y-م.l(Rw:m칤V3HVpgߒ"a!yY-)$yb,O^ ӹ^/.0#Fil(/H=cH.  l$l fJxs;Q{#W9{;cd+b9XMQ/[EbStzj@% A?~]R);Ƥ1D׊ aIL-n>/l:k5VI1nH -zs=)I,P4(9/jANzѢY! ((oiQFT$e M :Z,LںVh U.IhDe&+\Ѿ*l04H f_é{Ni  ,A1JPN'>ZfN׎4}0)HфnXc7YR{3n>e1t~!K+Kcl@jD'i#R"=3N+]^ ],׳ٛ ĔeW2.\1`ƴXL"rנ D{SL:jc+sųd> f< NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. :O-eOWJa&S$3q,#Fil(/Hh1 1 ?x ,>K7 ՇS@$#Fil(/H@ P?}#Fil(/H 1'ҀL Š Jm`a7Hq@#Fil(/HM0Q@Pʒ [.|#'y| y, 4+97AXDSלNVbN|lXSjVS{VLeiJ}"vw[σ}⣠voshU+zioT{PC05i㍒z"-%o|T{/kB+>md[}U/"+$魎;Rg6יw]]N֖|#řo*ZF\zC5WVļ~0Ɖ d (B d6򠡦A-:%吨I@@5 Id;_?vJ_lL@A;21JMc^wm`Y7ޓ>q2pgh"~ x~,z!2K?~˅^ hp$JJYJl:arYi nG%odѡ7X J]$)#$FœL :xT@j8Y#!2 ]@?)XDr(vL@˞) aEk 1}+zʢ:BNTz&xmDLe@`JA'! (1$Pt^11(*D 8-)DfL`s2$ZoB)18F8$/pS2r@WB.D &OˆY.ǐ@=&B  n6+&,Y }w,Kr`= DWJ22C8g5)8ymt` }J!Q;HqB槨x&lhX·1`p'- HNDT[h!,,F'>Wc~|SEΕ(c 5W.}֓> rj$>JU ;PT4QhʌQ|>d[֔ܒڛlМ?]@G%[.S9KJXg|Ob3\qǹTRR?8d^Nyk^?XeOy$tǚi6Id yp4^̨JKjG[.qQH[7A#;l0C&HN&^ Cٿy*jz֮GRמnרP,߭|)?q 'U ?MVǥ$M]XT]\_NO&zju柆zT/QPMaG4AAu$#uow ?@w!d^7&˄IN,衞>heat{S<ljĬ#~shY!h*>_,"+PW @N0M!'O!bFe췱Á TLo| (ou|+L@yl*̓=ou&#I3Uy?btY/5wT~n!m}sHi=boZ'fupW)>}vtǚSTG&?Z{\1wI\QΈ1P6~G5vFK:; f7ꐰd&PG– Ďhd 5(=IHU֜Q"yP|<Jԥ<1MzDj[m2:ӍNsR b.G>)RrfOeˑDrGą۶vlT-!!B>υې(N_zk[ l KWSS(?c͗hisޢʞ?>3:@ DfCs9f~D*xxdO= %4?f[[ +:U֎KӉ>V.CZ$4m{>D~j8jĿ_YNɶPޔckU`t?g6H\O!N로琖l DLj' ?U@|3$~մDI>|~'lv_Ep~e9z gXra4 n#:O!Nu^Cg1YN$6}\ $͊~W}ձtCB2̜/5zځCMtdK)$CZ;:!-5)͚I?bu42i|Yiv¤|o)[>d8QlQ o ZewY^:ƗzDa Oet_5-{uc}(UOf9r$n O2}I)F]FF|gnOYnDB]JרG$B^*2oѿ,GAh?5X+hTHRKӾX 7B2*虜1SMEHUhhldΝ1@#i5.FTR8i^Դ;DFY q iLHn:РNDk b2*OWNË O&3m?˂PPx"f6}U'|93 Wƭ 5|*ՍK>"釺oǃ7+]ÌӤLqE+@6i ]uT7,\= HͅNF&6O 'ZL{ewWF6#S۔ONT'EPvh jZ0YR?W˸!5NpZō{CZ3Fi{rsLI!fy =M`t!Y .j. åhc琖k OiSYJA -É_5$WPۢ.[bDM/fB>8t-V0VSo\rk^tiY&0HJ1Jtm{j4 [e9K1 .G͘9GX p"& 3H#" C(Ic 0 F ZH. 3<*۟(g vd8pTBhV6^ &H pC@iZ; A;K@"OP@|Q v!Ca:|r%i"~ Gok:?J:ZBU v./@4;ZZ 2C]xxUr[:[Y^{UZH;vs@2IhZ'TY)"D]jIJճZY G\lO'Mky$/Mu<<cyܘ?ߺNqȀoj^GըBC@1KHk@Ȓ^ >JoY#i E]MW;U 8fOu8d(&׃7 T`j`-X`SOaJ@TQ}j T66dk Ƴ5y k7k/q]||(v 1pTE'm@itf#H;vEwɴLh.aFEd4\xtT2VJE}T24ꀙ+0+Lμ<~@ST^9r;`?hΨ}KG["BBےFn)1 zCNԝ+w gqߒ>w4xX|+vڡ;99wH3&a'0 '8lrcwSOV$=p:(B;Pf Ǻ)C 8I) ˀ VfKC#3__\eHQ?ǁΏq2Yh]" ajĚ)1ӡXx)|7; `9HH N1x_ N%HaX4B,8] xUB4u:v@5_Qed_ R>%S ݪK;5k} ;`1p P(Kp6=WZ3)zWsP7; EKd(i&hӼm+ &NB#Fil(/H5J^eIV + |P(DI0d/ַ4ʭh@ۈYE=+_! 5[%Oh62D1D'>Yx9d/Rr^N 1$]<#q~GCĩ}EHtՕvx,۲OdWitH?PZ6bwEDpv/uŢWgtҩ "⻵є*iE\ S&ßo#.1E"7]ƥ黵fKT|$wk[v[#O-Lc M $]&宽zIq;K[P5ߝ{mvժЭWSz;WݎA(Y2ˆ[))+i俋E8}ok4;zD~S./|tG+"*q6?T>Ոe6g̙TJ3-Q2ۓ)f:1 abf &A !HXUcBjacV5@F0t2N\P@J|,̋ɂ 1P?@#)=`~mf3k8"j%"F6#s̜ 04llIPUq/reGf[gO^\ JWq%ZL:ESQUcz\kɧǥSE P*^NU?(7,8׎e M,p WR-n*?qjCu sj EDoEH/CZ.4o.0zZQjtxt Y%kA9 ͊f"KIdT;rP rL"ENyt\qE:(Ukb؋"ۙ^dqE:ӆ=PYA`H] ҡ'.$]NA`!zaU\R]ABhE#p |~HhR$jE>UQOgeh W ޚKrQծߪNw4UŪ9 cshT[a> rrY}_Nɿ9P{(=-șAEZ.Yr8eNǤ$(dEwh0']0'/Bћ8&0ϻNVV @ohO rۿ]:ܰ 6Ѫak4PJ$}lQ vG@#a ǣW}C5.n(wUWb4- Գsa5M$P"zUD:'TH<0m{ix*B8j}lABi& +8VkܫCt!&0I_Cgݣi< U75nTb "y$Hdj!I?XՍ`_JbWsEDHL!e[B\I(?coms"A$CxR.WQ.$ a¨R[%g|:/np{O)tEZ}W-0tŞWӶ6GudcnCqv|V{t'E㇮jc5u~t)_ UDG9q\zr5?UyȤ@p*:q ьӲGԐuEDE9$j-nX>U"3P wI [ZFȖ<׌tbH/K9BB"PQoP\K#*?9"d;RaN+Z{V(7$å5D"5 ⭄!W͖h2z\fl=s\b1хIYtBdmJ-'8<\4E5MFo+4梸U dZݽբKUVWZuŹԢ_}Vn>֎W<';AMIqL%|G(b$ M6I:x:KP HBנdoQn?]th9J/U quqՏc)N& ,nlKLyg(U|O¥xRnR9'T(3BOJ\2<~%MIƇ_('ڡ4AbDYY+ZοlSJ i1!9&EO%hFu2^(>%I3_T1}N\V4jHT~XL[[T=VE"ѿv 2" >̄?ٳ&V`WBsiB@Ab2_cA0͆Qi7_(kI(jב ]Yɪt[NQ[[N}&#g=8DǿBUȬ.[~ud?M s¸P~Rgڱ؃|X)/b@.J;:1ˈ` 8v; ePƆ,rFGvu"تCsч8.eqiKщS(z`5ѲĨd|uiTP.ٝz4s^)KwҹL%[()r6ݭUEE+)(ܚ;R#lHRĩB+~}X^$"6ivkۜKRv.FEiOGR7=td!}v0[dbڇ1RȉST>Y]$KnWN IX4EŠ%RCQ7cUiO4hK)FmBE/" `=5mRq"gِALxhlp4 DHV!fPTDX0TP#k9 P6 ZtoUMJ X]B3B۔㇋}^+' P4j,s:(7E{0K9vV=KfӒL[BYՁڵQ‘N/:M,~U!K@lU3B/0ro4~ ֪D Fb6hY7V=0"{n! ?Z f }]L@x;Ip@&Tǎ4e,ɲ)3%LӊHޒNTg0SϹ*1RTy290+l9>>,#iɖi|yc?M+sd3¡ kضG8]օRGmjv ā+(1ɞ,=qf(jI{LJUE{cfP>Lyc(^hekr$ jZu2͆TF? bS>H=™_E"Ǟ0= 7B#=i>8.T5v#ꑎ „!\J tF5PҁL[iVpj%"EӚ:M-~a-YQ,tmRxڍG?3JvuPM^@U~ϏG+KEݝvud痝NJ8Y梏Չ=F7-G /(AC}Qe;N`| ݊FDYGJ½Cy%.*4g ;%F$a`x4dy%*>0xpw\t>˔k.2T(bYML;xmv%Âr`Ųz4~ \ 4Xߛ Ǩhj'@ O&xTݐCN1y@^R歄m_X9 ?⑌ @~N|02pN|/O& MzpGX=2F,B9UיִL& q: /jƳ')=2W.!*oCp"` -,Xm 3 `[r 5tmu%l ?*,k*r5T8 ZJ ȥ:9s˶{S=LԵtUlf7qq!Y0}r)@yT1RA肨d5S,/{,3%2z Mxz:`dnܼz ހI6p?n])$?ĉ$/5DcaZaIP4`™N)L}D`P3\X+%kjGv4* *#e/eY K[SpN "1GP^kLe"MHl7 c>~ÙuX=ÙMG#\9߸h`Fu 52q x9 XpxcзbhK_#gJ!x,(: B+ T8C8{:@U'^"NB*2'B<*Q+3A:``:f2- v}1p>d8<t1Ǯzu@|\ (.L` !E l yP<hˁP}<2џoOT`hfCs@ũ,N)ڜE<Yg.ARim m!j=8BjjUDc87_Ɋcabì.!)NP_S T֬R0e SꨪŎ 뢉Ld m,ZhZk14Qa-R~@q!hQs־t#Fil(/H%H R;jHR6\lFsq 7*+yŠɐ8AC+"mz2ڳ Nuzszi$9^f +z [J-j>z|/- FH vNIJ=[dvD!JMnZL 73EFFLHҡ!My4=K[saJf U]|TuFvrmhYLPಬ j&) *?EPoΌ&edzp:Qo/4LdLI ;f*UV? U$Qvj%7v| O!Qk(c[کxG֌\izaL@_trF}> f< NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. :O-eOWJa&S$3q,#Fil(/Hh<-< ?x ,>K7 ՇS@$#Fil(/H@-P?}#Fil(/H-,<'ҀL Š Jm`a7Hq@#Fil(/H0} RAw #Fil(/H} ?,#Fil(/H0?С,l RS#Fil(/H* EB7l;Yy"vëU@ N v*ё:C0(H#?!5iXzddMS( pr1jlKhe8=D3n!J -&mZ0noZSщ{}4ԛ(faKJv PoUcnw$4W$b\(EOb>bӈ5y%KgzVR߱cCtEac|f(2 )?惣D$jw-Q_n_tB/ `6M~L Tisɒ!0rC$S_Cmm)d7>Scf~&SI$ipf>:FLIYj\5%B(>"*҇TapE;]KAs|n鍸+tuF kg Ҩ6#e4Hłl%:b쫯yi-o#FL?,n R.39uɈ6g%{3PmNc>5#3PDS,|%01Ei$-ORX#ȼ>ʋ[&=u^?rF76gEEUмBRJ ο190E]~pؽ Tyj.DmN2 S.oaU&0_5Kߺ;x2sҼEaA"muE7m5\e}'`ۥ04Q)p,2JKU Sn[skxx }4Ipir??!#QmDos2 ;{ M<]zxܮ1 U<^@E]\FP53vm{ϕ62[yiѹ֒*|HAя1%8;/`[D jUz3 ʑ@ 96D,^Gf p&bm^; B˿ 1)5O$w?)֜>Rl~j23D"3&-D8ByPAKIUn2%iU!} :(䮫H*X< kYjĕ*KF`G5z"b4Nb[-Sި´-D*(eBucV 9zV績O#ĺ8}Eb}ZI1PxlBQ@`*,$z*>C[Pf膗(tiș.SjZ܆)H}EH8gmOĊ+OltLiHtVa,vRoZVZ' vTA**fyiM^(n@c/ &_d,Ҿehr\-MǛNyEMc6R|% 8ۙSssm3R/ X.uPؘVT8<3zse_LV ݨu5!GYi<:4 bA@W R[Iɱ6Q ݟ,6{`/ŭ7YF,2rUOe3U.wbvin6)"nY 'TD$7 >{dPj$s 1։PLGsQ)ƣ,:8<|7A\̕}̰Ѹd+dlAq6Z>,IbwXQcAJ&tcd^lRa7<:22%R]F@D 0v{H|" 1H)Tr_`H4?T#5lǵrlpC<1?f2桎?|5&r2WSw(C|=-1B@%Mʦ|H 퐌eGA{~ca|b|^Yg&+Ga?i.u9lwc;gꕫG@9 dib[hVZm8 `I4ڮ/Z UuLT(H 4GB' $ʵ#t#Fil(/H]J!B6l;ɝ Y ({ im+FR!Ubn8aܦ4CF/z'qx0beQE MUhg݅,#/fg Пh2'88I&$C>_쀢ߖBE-]8Ts{=Kir[HN+kܐS3c'T䦹7%[G#'SĞrJXZN7`WRɃO0 gգVP"Tel44_17ד?rrr&iHҢbڶ, ~7b5w\޿O R:4/x(GZlŒ?5;O/O_.KZSU=VAfm&g w.Ǣo"ȟWUU{*F4?SKJ8wbHW7W\4(*α%˪F/NosXTrs~(\lP/'$vt01dԖf$ǻŒaIh^p@!YF& T҈A'Wb 9hh܏:u.?XUv8>I ?og,yCq#x'UK(2>[C8uL`#QZߵ]Ĥ5GԎ4;f RP1ڒX [EUg\ nDTaW}Gg\jZɼc:1J jMMljf/snt5C2A>3"xr9M H5{}?&t}`4D{r&n( gD`;N0kAx|>l"kA[3Mu3(rj팼Ue~] ;re,qb!6n i@kA˰ЋصX\ NzBaiCW XdzH@I͡P@-BP 6=K?>#`[UZ_7uH#+Fj#*2U7߉v"YGN3dDd70S&l)AZjh!'n!CS$X.ɨ-H5jHŅ7./Y/bOWkŸ߀i׈hAr7f|z6%X_ҍ? 7u2N[TWXnt$ ? ?1h.s YȩW`Q EKV 9O.ЊZ)P mwBzw07 O=|(S»U";m-IM=`]'qp^ *ML/ue)PㅻsKiA#b]i [o M2=qk%Cm*6ƴ5G[ 1gj-tyPW_ l0/TYk:T,Eh@p *Dx0bVIqGfbi67:LgR-tS:<:YI  |za;.y.VL )忌imn֝ѝʂw;ME8BPZE@ÎZ8vi6vӤXЏGAV'-s'j).dEJe$t@+} *ArL[TQ@N,0|C ?h1JQO0 x*!Be66an majbmomboaP"oe]Xueǵ^wjgbfY5s N\`6&I2(вĔLswNe]RHqF+ _K4<@'I+]nK?d_3LB*=rZGo <*Klr@,52&+ח.bBv~^(6'ս( ܜhbbLb+: @fq|;j9^M 7܅|;τjh/f舄-4k3A{[DZGY[3Y 8Yd:6N\aXjg2cKBadح_ٸLޣ#p<  t3f6AőCI<D.dEguHvY.0O:i1TfqnX>"z'622bOvj@&pR*br_(L]25AB XΦ⑋FUr%%#'78<<.7>^ #h x 3R8T*]]5&qId@ tcp H*ЄL8~μ/A{b?Yc"ڹO3ڇMI@%5j.!ɑ I2GJ6V(#@ < XtmR8*Dc?OS+ӝ9ɴwVs~ ڼ.&$4M'"<2=-x29s ;^V /[UuL[ |SL+WeʳP_UQDZtf>/JYYxP]~ؚYY!!ϝkw`=HT".F00fI65+ y9U"um'H%9*29fPrT5zQl,HkQO UQ8Ox3$* 77阂P@?f+-1Q( 0V*?Y< hwǿV(EL ƘUAj?THv-t}2yY"!p|ƌ7&ʲ*!a404T_#4hXX_,(md8DJ?.<"@/'Vk!LýqOԺL^ً͢,;mxŽGar>TkB{ރ;&wfYm#ϹY=g'gmH}1Iɠ,.`'9He'f#XC/<.mWZc!CY8QJ2hT™ԄD.c`y!F#`) VA(  :0ַ^k 1Îƹ0#IʬVF&eq S g?:r'@WT-']^8{q4dMs8Rp{XN8A67% Cs H̲mud,Ⱦ [hwȠi0O)$tN3,? <=_6dԚr.O,%nHj\pHM/t Z s`mWʺsv\_Fbmj=~h,̏o.!,,MECi(5zJںQC.|S R΢ GںOίf`H?#k쁙 s!ݺɨڴy[lZ5#hUC:o-EUUz:$g57f5!Z)́p5MKlDo~3̀'颺(QiKf/ED`CjhCr#Qsەe0F:vf-)(L4= e@7ڲa̒gjղ5 U(-&9j xןfۗF41&V06770o 4M P23P0 j̜_Ǖ^sjibfYuB [^Y`kP*#eXN e\V-xxw3DcK* zR0Dܖ' 3 ˱uϚzQEjQtȬB,zfƅ |!=Ѡl-Q&KSs S11$[dU]lFj`2X ,'g%7ȘUEgE.;X{NC+@mvƌ?wfmoFheyFւO>Z!wb3k"|Y\W涡S&4+B`}'36,ÁuqYxʙ6@)t?0r}#C=g6pU 2l &@ 'u]fodA@jə5Ŭ'IfZ9Y/f1Ξ\`f`GEHs!;uqCPyd9 ]":I8 ,CuJ)N#O``d7Z{BkPs DmO$Jr]p8,wWTA?| \#S5//nbal V sa[HWqHE=(bF l K5{'aԪBȄ^;`,t IZ?'>mbq`ʕԆD64Ut6 {tAF#ڲGk*7GlZ(M )|xZ|vсS3; @ *Zp #T9 ',z^m@]d W.V5<ǽu(!kmcF0q\}z@q4b&/PJd.ptsU,1`Oebm{g w'a>k"'lCY7{'%r9~x˖AMRk%b|I;o1{$czTJL VM8!N1J "4(c (`0 _ -c_n$, `кFȔV5Z18V#QcگUiTy}Kt!(u3“.MYQ12`Ѝ  ԝׄu:Wudߥӽ9Wrh*ZYE)(Gs[_g-3Mp詒gƑ^Dv P+Ik_3upܙv*5a )۽pV0I:(-W%L=T$)1erI⪘ʬ.l4/?#Ϡĕ(:~WD"6$z Î aGG9WƊ).+<10+P O0 9.0@0`@'h'%D$YhڇJ/X ¦M #{fWKޝ}ї e%Ėhf"ldP( 1  t8{ &0f@O50[5?R $,`\!1 Ι`uFixT챬,'vn^KU4|S ݀t 2hnʼn γ"/34݋{~v] - Fhp`d CW}bjza 1ЬG8#Fil(/H<,-------------------5e(7 8e(k=;[WwODJ?YyGx\ݵ /t,uUi$]t}R`jo;(w6OtD4tK.2qqǂsZ}w%gYnZ0~Y8cC} 5b1j>rrR}jwL=MAW 0=<:z U\xaS w9P 8KL)6W% a @ ۾۾۾(SѾ۾۾۾;.x]7F4Ұ~"U*!&]@0OCFEõprEŋfla51qYD 'Hl;{&֩"s)Jbu5 JXI ׃5r DՔ8dlM0Q( S#ź* 1ڤ5-*RKɹ%mhlpȉѐ v"YZ*w3/+[F!$#2;qJɞRGߣ!eisժҹ#9s`OrAѬpДP+T3..%ap}i9B!t?ҵk 0Ϟ, z+}?V?L}=_;0G, ?:cCUV|QKh Ԫ1; ~M2+DeϣS8s-"y)e G SUҖ.NL(,gTE oh_'/k(7ڱ W:d ><B9npSGa=|O:JsGKkT JLk9 JW{SvRL 黅Il& kfN2*eנOa47CCKR%3ױmZ3۬ A(Ӷ9eŴ4kڎ?;R. :,:iZ\w`ꔃ>0I-h=CIkN-@QivHgPR0`Odj~H3[1i2j{hGD:Ͽ֫Frh #85`eKCsAseuV͍Jk]\E0pmmmm xmWϷmU1A֘'Q}pG+9BV"0K+ϟ~RyIm7ATrVSIh:'J6Hbm3SX[e_ʢNʤ 6 T\Qƪـb d.}, 4XS? y6= D!hFl|'XDy"6 zυ`ӣXfs ol65u ҭȉ䩡gꉶq"b+Zn9YCMYD:AF6]QaA.fJbBAyhCXK̆IDkɚ=i/zFd4Y8%åRveQ3Avs;P)ٹrwEAtZ[=qCL 6G=7 nA'?|0 @b>Dڿvяʦ:y "r0`1n#IΘ쟖y&_Ij5Z ]=9BU7FVm-' Hzx:;4lCB7D2( ۱0aO[BI/"N%N-%-I]pK [p8 ['lC?8QDf#Fil(/HMDs5s(X@Hk32CfIyhP끞*=xKS,I D٥3 E\ǃG%Wċ8uA;zdo?a,B 횘5vzm3}~TeJH8%*j|٧PgbCA@ȣUXaB^os5F:'7g 6]\߃HvUivoy\ 1kooce\Us/ 8(&WoE% ,=ď Q~1Kt'RiJ|<82ap@-#CDNܼk̽c;EN%avM)60\r7[Xp -]Ql=dءm[Eo[EofwtjZwy]D*t;{vTxuQ$0EHuǎy]8K2b%  `&Pt {hJu$gikb` HhAWR"H,eZG53")el 'E(vgx=>+ `8>ZI!ӴFJ -ՍkgplMX$#DxCeFMAPGk|i.KIŀxItdnGHΚcuuC#SmA:9/BdB+h+^UZlY =:jvZ'vWW!EM1f 2$|\7`K8sM˒eۿ93>2],P9'属nݦ@ dXo#~&;uUb*L)0V}PIpaiUUt3P2'NB8Q&OwYT29N(2{mŧT:)i0ʅ 9N^l89דUY[=PE Fe0ᨩ2 ~5>qבcv{I}3^-l!h-98=ܞ\Dփkb wju AS,MaAƄDKKP55n" 'c|~D4yi:LIZfqlFW٭N ѱms ~ݾ6۪^VvfV@0+2F,{JiTդfv X]\M)%Z &VyB1va9dzh蜇5Z"N/e#F'QldEmG%>@zBnH[q%hb~ bA U^J6TG4֟eӀ3 Pq"0̾+&C''mR5Bڪ模BfF`P@( ! C8B" # @xg teV D '#Lz,|8!G"^H5O_(yl] %5q7{gr &~ Ϣnzj:WRurQf5W:.bߕH…A+9Bnz%/6>ט$BVu Fv`l|m UfoZB>0Ec\tԅlف<iP Ga0vMĭX>z |zlz#AB1$Y0(C64LI,a۴۾۾۾۾퓸Yּ`*X鳻SCGŁFaҕW2. G)\̌Y&H%3GLL5,xN'$;Yv8b/;l"{FdIQ/O=jog; F$ %|BȺJr`φc74PX.l+8e3m HP6B NpYk)&~p(DjL&xF +uFwMȊDq$%<ܪEDѥ%iHؖA/$XO55[d+'q-!Z~e>Ί)g{s)V~7EъqK~% ovQ4\MJ,z|FUWnȯV 131jBCWPâ,bߔk85ɭD_Cg"thUP(FN1Y9阉kCbF%/ u`#qe@_b}<`.b`,Q%Stʨ'(etDo?:BC!`'aS[ХBSf̌dE.x4ȵ0)ViGG\55f6} %or@ܭ0 #؄ .ET,O@YDₛjn.|&"}_ޞM9 uy5)bgZwC1SYcUPxjmɒY2U{gh xy B&z_luR##S_r?-. 1=u#i789.L XFDPE-^W o=mmmmz5Vڎ_YT^?ឳAuD!JWn!oOVХ_VS*N"&& ԤBaCO7}"<3&(U>}W8j IzK`^uRZfft%mVUF/PP{tF {|p̫N Xٌ(V9%5.Ѫk,=k3Y=gҷO-3JSP\L^HT*! j:KuH`S h9AJ. L:*e݋L09 ˩ē8DkIi'? DEqw Y~_5~j;5tHAiw03`+P aR,U|jZ368-cӁu$)23 A @(HI@L//ʇq)6Ǯh;nl|캱6Ǯh;nl^NhZ;r(yǵMq@iÓmD}@\Ul"ne,Cx.513^89A,{LWM"H 32zU@A:-H}B㊚eZ:cdžYU͎쟷Čf505> &駊vv46CuUu,еΝ%KS?Cx)[֖Œ=̱ 7/wNPpZ_ƪ&+t/8jD)vID8b'kD)vCxn#Fil(/H0/СX; /X#Fil(/HD)Lw3 ;|Ճjw2W3Vz&|f0\U~)ɨsU=\L<WNض Bg:?pSIJQs4X0t@#u;+SL晞}g1s:M*ɨqm?%x2XX* tJnԝy1b~ӧw   |#/K8Q=';.< %:M9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 16 Pl BWG@~X0Y[5oS$79X(  :FY0&hA2)IR  (3 N5QrqI/8D*nҥmaK=uSO[MzPU$o`V(0#Fil(/H)44''FEJu2 ;zցhz1U7Tx*ye4[S|-ʥuX<[O;YQԸ @f9>mRGMTv}7V/r@!x?*QK曠{e5q9P}.ʥtl>${1W[) rNl՚|0`}|Ԩu   |#/K8Q=';.< %:M|9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 14< @ WV(l|+N ߪ*X@ 2p&`K PHp}iDLP@*F``S\5R%Tu)5.cE%!Uz⢞6E=mw(pB]U7+ #Fil(/H)11%%A@Is1 :xg|0T;Su-xc8YQz0ˢx[;YR:\Uй @d7=lPSFPWx{;¯U.o>!{B*PI枡yc9o8S{2ˢvi=#~0V^( pQi֘~/_zzթs   |#/K8Q=';.< %:M|9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 11< @ WV(l|+N ߪ*X@ 2p&`K PHp}iDLP@*F``S\5R%Tu)5.cE%!Uz⢞6E=mw(pB]U7+ #Fil(/H0WС ,,@ PxQ#Fil(/H   5:#78r^}PUH`h -@5'.Z +&I-Q0/ \  /       kP[ aX@An{09 $P,P,T-PPPP8THE)4#Fil(/H d,#49"57t`OSG^k큕,?4'.Y +&I-Q0/ \  /       rTRpPn/\_@PAŢ@eY@Aa0*XJ@ #Fil(/H t) 38"56vcMSF\n탒+>3&-W +&I-Q0/ \  /       rTRpPn/\_@PAŢ@eeY@Aa0*XJ@ #Fil(/H0kСZVO? #Fil(/H + DBbd/b]>EϏ Bcd/c>2 B&\zd/dOcBXd/eivQrBVjd/fPl<AB3sd/01l #hB;d/11lE7BWd/21 mBKd/310m'BSd/41Pm#<B:>0>d/51{mBr2d/61+IoBYKtd/71|fz BU&8ld/818)Bid/91"'B7d/a1ЫWB/d/b1 NBvN-d/c1ڭ# Bd/d1 Bod/e1! %fBL,d/f1"%SB֠,(d/02#z#aBBmd/12$,֬vBld/22%FʫBFd/32&'HÖB d/42'w#Q뚑BQsd/52(;$+pBEd/62)CvKB[md/72*0Dl2Bg~d/82+|"'+7BQAd/92,MvBd/a2-6KB']d/b2.hxBx8d/c2/t"MGOBod/d20Ht8BzYmd/e21H$BUd/f22>HpBI=d/033!pBK7nd/13EPPJUJUedc/1c/Bd/edc/ThmbppPNG  IHDRŐg pHYs+ IDATxwxTe?̙ii^H# DB( 6)+uyuqm꺫ؐbAitB =6L9s~ Ra2s]\ffNgLs~q BH"uB>J!^B!B( Bz!J!^B!B( Bz!J!^B!B( Bz!J!^B!B( Bz!J!^B!B( Bz!J!^B!B( Bz!J!^B!B( Bz!J!^B!:B!8Î;qY@pp08BNzu:dp:J%J2!}`@@@@y/q*8p %@ !\Μ9gJ$ 2"Yc`@ J|,t^6lZ|+%.AaF\\0"[38_pB6nڂP{L8+I'zLGQ@|fnBaRك^{ h$&&b;vW\鉔J%d28tph2eY  BĠ~]OQQ-[~HJJĈïz=6nڄS'sQU]aV0d`L0mZޏ'rPQY ]Svd2)4` +k,[߾lq#!Hq&+F *ɄO??t:֮]FAA򐗗'N 77vD&a1Pajka!HqVvdeEJJr jmm-^yuzdJyUW2o׎ݎ'p߽\1X,xͷn-bA(`5ǟNjb2|B6Fv W>7٧1s ( u_k @\\/_Z G(O~ʕ jjxG;~U6 d- yrBZP!d.^z FZ~ضm;l6Ξ;V0Bzn:yO.p8@%IBkLF#>eVN'd2)8Ε8NFd?o.Ŝg} |Gǟ\Fpfl6l6 V <[fdtMXŗXv= bbv}Xl9^,~'Q@F,m.LUZ֭CCCML@ +WDII }Q߿r*/v1 H'pYoQ_߀Bzz:nzqHa9O]ЧO, N,2j7QQY#cذ e'ifԟ?7,3O?~߼3زe+8"< ݂WQ]SGbϞ}X,m<ŵO^ =}ÁB޽fW' ZJ ѣi&ncǎ8r)S _}Պ|̙3Ç_~"!q(//Dz+5.6Crr_dgfc0}4(+**{>b<ϭNĄ OFwE;qDGEYBE hQ[[[:x٘m^r͘;w6{ z={,k@N{LΜTUUmss3XJ*Xq8XhQoP(ѣ1rHHUJk鼳嗵(**jGwAEegKٹkr-?Z$JGpHqalݺq0 n̄KMME\W|_/s miM% 9BjNfE@OzCsyy.Z((SӉb1BǒNTk?;<d?m@R@0ߥZWP`@D"K d2'_֮GyYu:Lfp@5??nqdx*op:񀊊 ꫯ<j$''#::viii @ @ ?!Cx jB'&&"&:=:n9v )uV~X,;¢"jB.CP@V#4$-תJ))n77L-cYBCcaXT!(0III_W-ZoqUaL2< !qV+;?:/g^i(+s3Ͽl ;eYp(q=oKH Tn >5GV7 @(JT*h4L00l0q?~c"zvQ\ B9 \0 D"G_(t=7%ZmH$0uvGEEaѢEHNN.b$46 v|0dd is!}Q@L&Cll,N>s^_eY4 8 P(0 >gO:rMXɓ'~z߿yyym.䪶g>ߥ GrbS Sס\Ӊ={૯.t-v@CCC0 RSS1rH 4>!$k0 }:j|˖-=:"¼o:J  >>۷o|a 0Gns]F 466BVc֬Y>}:BBBp-`СAyyy] ݎ1cxlbB^PcAAAhhh@NN?o8UUUPTHKKkJ>00Æ èQo'  N\qրX,Fdd$65jnft=B*L}̙3St:QQQ8qb!((0`>HRbB_[lYfa̘1PT>'!\( zf?t^;DLLssř3gPXXL`qBz J|8رc=d2a޼y裏ƗeYաqqqPխQFZfmp?ƌbP̚5 [nEff&5oF!'d B"&&ڋQ h[o5kpM9s&PXXgϢո#D]]Q[[F>qj /jwnT*wGpFv~m\#G'nR>$| tcc;wĺu뮋l6;YVV0cƌVjPPp}9v80 |r`YǏ?CQWu8 AdlXA6UmOSD%>d6f:_N~?|cbjIeZ_ ރ lݶ=y- J%tfQ- EeVWX"D;?}!ݚw}o/pkB!BCB }>郰k6uLKz'bv6l]T*@H$,]y]YYoߎiӦ:B |7nύ1ӧOCTdR)Z-~i~}pϿDVVW.=Q BnQL&֮];v@(a\Baw-L&1ydH$_CHtqThǣGk>};qHb `44шbCkf? h7AjAT?ӧC%u:kAߤjRD@?4  ]s:V[ !22a^SSxC:qq_0 |8nС#0L\ǏGtt4q2Q?U +?:]S$VW|o,ٳZF*w8CQQ,Ye+hj|ÇeGƤ;hu,PYY>l1֬]V0 2駞Zl9?;vi8~2f̸O=Wĸt JBCCjjjPTT["??566ٳ5jT #ln34`h[_J  00]SN%Kr? Dq:UCؾ}98EEزe+!sh4!!C]] z IDAT7欱ny9r`Ѐ T&E}]=JJJp@K-oz||/08} \&}=8'rN◵[6B& {yJ%`̙H$r86 ?0>#ZʫK6l:\jzI H X}s3 Z0|!%%e`Y999ҲRlo.Z  X @~}5l};8C}Cn @Qq1l6Tp154uA! p8`0X"C8ѣ޿<{0{KP* `YUUUؽ{V:a(BX,^=gLL ^u|mVJ 󑑑  FJJ ] 7& K y= F瓀,Naaa0avPPY^@H{oKbb"fθǏGs(**tK<7pv?ϟnbL>~aLP=z4fΜLJie\9KIIjw?h2l2nS}['!!~2/>{؉藚Xapz:OJr2d2)l6,))) 5%?GA&:f+?"nn6%x}wK(ѣz_a0m48p[nxl `Ȑ!xqM7uB܉E"E GWQQ>\SneI4v^NlO~ƪkh!5FX1G†uuiXh4u5#G#V{10 Z=^߯biѳx\ǼpM7̛7Kf-r<4iR%!.RKvOX,x㍷aӖk/Vګs459CuM kjpQlظJ~A<_Z\4h4w1_|5]/NZm-߰oݺ ~>}.]]sS7vjm~6JPUUۋpx@ رc:** cƌڵk=fP(ƟH$H..޴mvڳq LP~Z y@p<ܳ9s=={bl6l6'hhlkҢFs>ߏGbllw466drd6T^6_gHͦ`L|("ᡇ–-[p)tH{JH" 2 nlWBzDP]S*ׯqZ46F ""jڣ1IHH&O;g>fPP& l6ǎ`߾q]:a„0a<Ιsqq8p٨8{/. uu;HOmt@ӧc޽{qFy\!!!hZ'|! =8NF8Nf"cQQŰljsSqvk~YX W曯c@Zq:())oCDG#V ~y =xW!燴4;/G>lAuUے6 WrOv4J0 {?_Ϲ5!!!55Z!((-fPXX$K"}z2ocԨQ:t(y᝷<Q2 2 qqqOJC~'uC "< MMMJ< o_(Jhkbw=Ћڇƙn.)-qA1`@?v':* 6pǘ4v'ģYǾ/sO,]1 xH&#g{Ic@ҒRlش;vf@ Rtd()q :Sls{@DG=?nXZ#pȱ6"..@=}>p!f L&>\1>\gT*0a,ۘ7|MF`Ν=fCff&Үyݲep)EvQEEN< ɄkBALL4*jۼ&?{t6{7̡}p#74Hѥ""1嗐/_'88>v Baax[pȐtsbtB ֵszz@M$%/0 VPPP:&44?(9X Omnz֭[OzSN?kZzk刊”)SvJB,ˢgΞEQQKQXTيor_1-]wNr>lnpg4m~GYYV>78]j ~ǶpT.P[[fC 4$Ægbo8 8rrNuh6V#88!!!ҠhڜlP^^}QYY2TV_DD&!+k,"##!Z='Jɓ:uǏ0 .\;SvXr#kqy|B.,^&d*˲xw:U}eYlڴ 6mx\mHJJ!Kl6~W]6 oeYڵ }Y<-..nWB64 JKKpBرUߏ^x=L;ecҥ^6ZBwQŶlق磲+sU*9so}\.H$nGSSN>ǏJ\KJJB|.R,bXz5 2Xқr98!}w'/dY F'O/DZB|vH }\7 0<'nhĈ4iN:T ..zJTbtBzJaX,ddrZZ8?~<0!t-{Ӊoe}UP!\FB!|Mdeea_sf3Qh4̘1סtJvv66okBHOE @7qjkkP:d2aժU(++u(BA @7܌[f:NZtB9";;atZFFfϞ:B!YbE_~ɾB-npСCÍD"J~;~mj !DXbnd#~u=BJl߾~)JJJ;2 ׿pm8Oƣ>+33}ۘfTWW#44rc!x悂0}tZ ?< Vb„ ޗHNN%4pBƟBz0Cǝwމ1ͮz5pȐ!(,,9:B>P"66C>V) gX ByeϭR'ч( 6ץR)vm^ v~ |1t눉/]g}{vA&vyI(`Ϟ=00 lP*PT0!׫Jo:!TQ^y'xy먮{s^ =Y/́N Chᇭ&t:QZZ}}NT*ǛP(]r'}>BH7D"fGeUN'B!Z-;,pY~?Cs3NUBj⫯`eZ<.;WOԓ>J!刈@II),+F#T**+`!ѿ_ 9c`Y ryEq;NP "eYEEeUMzC !tbհT*fs%?P]]zCӁe\܏?n N)\6 lU&eYfB{YHX|qVPHD??+oaZ,vN"'JۼnK[s8cDv̮|%Mbt:QPX 뗊444 ojBp`cm3))ӧMZjs ,]F vygÿEc6a8demr'OڵqYCC 1>wL []k8bm=zhk`2M4`dD1h4-noFq ul6d2L~[|%MD"h:]L&Z8NȤ$$$ 00 (..AAoh˲ϲ,=/s|XLv?N7.^x>:* \ee%~]޳q6l;ٸ[/kW_#'$ֶ?[~F8^}q{F&a0  |&na^LMhnnFsaIHF7X,8"aa8;v&_vۢ ?\z竪{n@0{quN:+?F=MpԄ&@~JWs6'1D"L.ܐ?gZ_#tbʟ&cXf& 9'O2ހ' R> W</I!' %s- yJ*kJHqUUUVt cYV6 "J!uȥq3 (!ĝJyyy\EzB!]?GDC"nwOZ\Qwg*W.\wi ۬c dsU\kוKp&(,,DAAƏGy!8t:Bբ(..3вt*'>,,b6|䛠;3 qn BpPxy%|..0 Cp)za3L8~8<\@`0tB.cĉAr/k0Q_y!!D-j0O~^&@`0pZUeY4_27?&sK뛚ܪ]/(1ee,Z0LK) JXr%Bh4ʼ@"o߾>n L H$eQYY 5F6d] h2a2Z`Py}K^ؖee9CUu5ӕcV{ŹsZm 11[M#8+WDqq1t:]?ભB3X6_ &}`` b1XEiY9?J>"׎f[OeWbGE;sE=˲سgJ"/[((bP]S|%8q$cVΝvIII- IR]]˯]dd$_1K$0h:x\z燘h`8G>,,80^2l1Q?7q jǷT*u.*.*ƦM[0thaXa2`41|0JT(HOmojEaQ>3XbEDD8$ L&NO>SU6X!O>3g6 uuXgp||%>f6[PPPС.322?!W),, b Wb_~ZjQh4ߟm` V?>w{ӭ!3z^yeNd2 aQV^zQjd`[=1 ;Nߣ ,bߐ{4B4>(,*p~=ӧ!%9X)) u.[ @cZbٝ>A e>"ӭ;wC СC !ח=4D{ d&%p:YTWW#?sLդ&'l>?0۰|w??sI&usWW_GAee(+kWƑ#Om K IDATNe+zLrsO Yc1|0|JQݧE饎=z%!!! mwor:(((hsBcB:L 222k[ڀ-MJJD%Un& ]GXRao17QXTA!2`!BaWTVL&Exx $R ^Gmetr`!FB?? f_H 9^jn=, X a q18~Gd4?@ ~>&ɍ=ȣ>]vMff&{=tn t:̞=۶mkw8lݺ4,ˢ8PLҿ%-HZ 8W=Bѱ+TUUV *J j&ɭ0 sXV~fV NF, T*"qj ˲oh@}]= FjDFF@&k|z|pĉW.44[ jnB a6bu궪];??С/؞ҵG(VHT:}0 BCBr\j?oi>TPPN6"}t4 !θ.z8fnn@ss3  F#߅#ˡP(T*VT*! !!J[iG\y=nTڭsյ݅%2`=?fWF#"HT*RJRH$H$uv;L&z=h˲|x.^; %to=t(//GAA_?0LW`'pDDD ""ӧ"""y~vefvϿ6_@,CV|5kVؓp8mv;rssxOQрek[ ZgvԠGee%d#3B!BCChkw8^GEE*++QPP"MMM;P(p!>>@o(F%K|I-v.vt4  @R.e[mv[z-l!w r88c[![1Ċm:z+hio1 àaWT(//GT*qr\s\ӧQ[[Vttt3=7n3<+B̙3H{{{T*'\KJJ|r,X"I !V+mۆݻws=I J|lڴ k׮ [d2'R?!̝;/2.sիё8)3}v / 6 n4MyhZagX/> & . n;j ;ׯXH ?,:_|Q0m,|C__rrr @ }z2(^:lT10P?]v\oǰFCk֬gm={Ǵ.ؾ};o@ 8~vȑ#_N l֭[9Fŋ,Z(p|]]nZ񓚚 6.ogŰFCt:\wua)K/ż.BEәnСC] {g8ՆpB̘1c2^۷oGoookϺu됝_"㍩X bΜ9uIXH  /;˰|ΓMa_˅W_}5~{jss3;i!{\e٘@rr2nQsq\سgOTS#E.S&BB,;vnPE"(X,6H-  7[oo'իWbN<)(%q( ׯc΅ p9c(H$aSe,ˢ+VܹsbOp^ǻ襤$$@ GpV Zh4hZAP@T")) III`vlp8lZMnߏm۶E|`0G Į]*f]wR:tHP[RA!33P*HMM;tv;kPSSV޺d2dff"33iiiAZZZ-RSS R͆>l6l68VT@ BHh;p B@AAf̘3f ??Zk5 T*a@ ͆NKطo_؞H$sw ̙3 [NRaʕEmWVV~?û-lB+V"X,Ƃ P\\,3. UUUxpX,H\. ӉeD"&~2V?^C3z<cZ@t"d~833s\a`q%-a?~̧SE#===u!>`Y[FH3gF|'NJ(ߏN E±cx= b>{D"ju@ 1EGG J%h߱Μ9Lpa"[.޽`hi"!.seb1Z-q#0 g4q8i4Mv#pf@Atdgg`0@Pʀg4#A666K$%TĶP0BR.ggg+D*HF>\QeY455ѣ8vΝ;ٶK 7 J!JP(FA&-ᨪ-#QRRq ˲]HQ R9s –0Ȉy}غu+۱`";;iii I $pSÂ֢ǂBPwh4Fd3 + ǥq-ò,, v؁;vpW(ʂ`@vv6ӡT*T*Rh,d2HRd2( bC &DB >ݻǗ_~kkkM94MZF^^ބm<mA D񠵵wARRz=t: 1sLFdgg#77L/$`YGs=BQ*ʊ; -5Lq xYVAZ6n#*|dd‘#G iӦaٲeXlz= 4$aI8rw^AefFA dD҇f9lPXXx-WQQ7A5΍ àݨݻ7n=!W 7yىO?tBcEzzzDIyNz&y322㬭$8fk4XD[[jjj&S;vH?04ɺ| ĥ6 RhE. L&ŋT+`JwkJD 6 5xFT$.=j$Ph4 ݼӧOOPSS[&ֶ-W\\s{Xp!~9sw$V4M@KB zC9EEE(++`N㢣Z-(˲iKk2P]]jX,Ag\q^ArxD"8hG233KbӦMđ#GpA8qB2h!@ %+J|ŝw9^9˲Zزe N>l4 '$DtuudSd2!-- eeeg.zomm <X̫V~@w)])p=YIP]]-HeѢEtٳ7gΜ@@f7`MV`0L8@qL&hCmjLd4/EQ_jjIhFOOكgywj:a^;w~-h]_:W_q^Ʋ,Ξ=[.i?\IIɤˏr999M $y,zz{[qv})Ek s/oZ۰mt /nĜxB X|ys\Sʈ%TƈMpZ%N70R )04##HD"N'X]V~sqWiY)\R=)ǥ`UU鞄h&D>&IP|WfYм%KRv7YF#`4immd$3cƌ5d2$---eZmU$?ψi̘1~n Aڵ3o ?ӼKe~?r kB,r(#<>˲:~x^0 %˚)D_Iutt٬X"j"ꪈexH$8Pp x. 1&z D"Aff.M%㦛 9Y?y(ÌiAMitPȑ<O_CQٷ?Z|_߸13?@NUc }~Jerss *PXX88|ŎEXt vڅƦfx>H$T*nXy}ﺫ cpbfɪShim '1/Faᴸp1l}L RBzNUn|.\C`7~'.]ш'.^paT"hY^4tH6m.\ h K:(R !J1g쐿Y `=p8|!&HEA&׿yy_aݣ$xǬq߼7p:z/D*\&CfV&XQ{~[mLJ~1Eaӽx"W_w4 e!ѨQTT":flL.ǃ܌ǟ v;^D"$[~~bU1Z X,p:]#"r W<ܳqÈNVٌ'~Þy'OU܈}޹Q@WN%P3Z|gIIII(//O$>|~?{Bn7L--d233` l]]=>R^뮻eHhi*8px<ؽ{|F쫢u舑?}'!֬YEβ,{scwql-NA~~~{6`ÆРۓ">ݳESR4  Y*|?oᡇ ,سs|~,Y݈!@QɤȽLd]NA~~a߿Ǚ3goe.q07c~?vM7477ϪU7`KKo}='*.O_x~gFױ_fxF!>̛/33f͊xCnr&\. {PeryD΍B Khh41cr{rTIΝbވ.Y*/ŭDĭŮO>E?N']]`&a>$a45)ipUFj6A1[,hll k=ҍYeϞXY:˯Z"% ͛³008`0~=˧Nq}RR4Xd'L\~χO QbJRXޯX3jf-HrrrT5[fڴi(,,XN!DHIIj.y-H/cڴiި,ޏfvM$%%ǏO?3gbٲe?>ҐB!Gb``ܒ%KPJbr 5cY>@57ز[!Cԭâ.Y2h/**\~i1@ S%^,r=j4ϋ4m*l}C cgG*֣hDiЌ1ŶpAhc' z"`ɒŰp_֎tvZaeb*|W,t tb999HOOeY]]]X,m)BiiiTlydpݨ^Gjj~ n===FGGpUWM'Tx<AAьX@`Dۑ[o,NF1.FSS>l)IDATCr0\Ĵq_evł;v^re$5)dff#bNp 'Oɓհ_ n?z ÎKqc<9`¦Ki:$Sx}FY6`헄HQQ!.Z8gDBMѤ@(~ 8Q$bbɤ !Ir`޽^Gzz:` nZ"K dCww7, <hT*,Y$*=! /d^%==;|> b``7zzzۋT<^g*T8~NOOGJJ ^!lzxF=Ea+i@hƹsp9b$''Cr%-- zL~]]]Z hĒ%ONdRSRz 7/t?3xr˱`'uz!y0Ayh:a@ o^/uiHHa/2R҂g{U'O]"g4BP@,j4b}bɤ a.a N7o*p8hooǎp4t:'upE֮.XpIJ!~1`ܹxQ[[}\z`ZN^Gaa!-Z  +++ns֭[#G`8<:::`ZXd޼yx?R֭ 7܀/2ޝQh[Ƽ4Mq؜X5^YY~ߡ 9N@0\`Xn<{ѢEXj՘i޽{y I) . KɄzbk\O.#++:YYY!ˎ4XFX4}q٩F"Id * jyyFdff]. 0 l}6l}xKڊ~.aRUj5g$ Ӕ 1Oʲ,hL^/ÁAܤR)-04J&b~nz4  `n7$ juH322V!ɸA߀p}poSZ[VVz=r9wor97U˝.E|AxkF<M|ۏp39V%:ܳ#-&} p❀&VWeeeX+~F^ya EQ#z0ܗDhpD uKL5ɿs@ @ LA a 1@\Xh򌹐J%#W. @ Bl S@ LA a B@ S"@@ @  D0!@ )@ LA a B@ S"@@ @  D0!@ )@ LA a B@ S"@@ @  D0!@ )@ LA a "ysg@ !ΐ@  D0!@ )@ LA a B@ S"@ ۵"IENDB`python-attrs-attrs-bd2446d/docs/_static/social card.png000066400000000000000000001135551476453530700232140ustar00rootroot00000000000000PNG  IHDRLBZiCCPiccxڕ3lmyɶm?l}1^k5xPE2i=*R]; >" Ugt| k߯_/o ׏7P2G_M He &b9/į`[Q+`GsbΑGsbЀ|#"ysDYCQeE:߆D4@W鮳&X[u _}TP-fRQ)IK RaJ_daѷ|&s/pon&sg]&so=egճkivO>+Z3ضY߄%l͟b'8/zdϞpA赏e) èZg^ pHYs.#.#x?vIDATxׁ(=  000000000          000000000         pj=_}+$ N(h8)(N )Ŋ ,Kp/iJp)A}oWf2=3ϵ_y=k}@^zU\ӧ /_VǓ'Ot00@޽oݻ$&HHغiu֯Z:>~K_0\J;v={۹÷o`@*W*3830޼ysС_>|`'h٪M$i%d>f:ÇA?#*U#6l$ʕq((;% F6ݺ"+K;|0`>}zҥ#""5k֭['۷o~z {rk'oǷ0@Zj)kŊkҤɐ!C,Yb6X{҅ %JYxY `ǎcǎ crr.(Y jĈsn{W>Quh0bwYSC?ĉz*Ǐ[*}jwJyI&K$v$_tL#m6m>Pzϟk'+_# l8VZM& *sέ\fΜI3㱭_KEGԞAW^MK;gϞ˥'eee]MO?}#GRRRm߾i;vʽgϞ!_,KJJq>Sȓ mRJI" ( 0@=|hh K/QJ%#gؼy.Vo:u*ϳFkƍ/?ȱnz7`Y&YwЯQϗ+ުu1cb%{NѣGW>bd Y.ckӶvڝ$ 'Nwj5YߺMccO<%wlGXR%2eܻwO;͛5@@= Wo$;2/`uɧ_xvh0wYYG;%"ꂒ^ G~a~7{ g7N z F233t颜%O@_ɚK.I+WJgi۩S'6gXt ;}Fꔵkiɜ_oL?)6/YÒ%K$^NnaY,JyUPdQӜ9s/`?W(ڵk„ -[,ZAiyV|֡vݻw cPڏ?(?c,3]peOᐩ`y#n ٕۿ ɓW%qF? FSծ\"UC[%R[r\68 Y,ʵdYa~c ߍ8*/`(/ Rw:2\Mjj S3:wfu|׻jL*9~,^D;`{b<}+wCvxU\,_ cѢEw4jh˖-RQ;LW}Y./Xp5_ҬΒa2%h1vJ``ORJޯ6%xteS0r|MժU%gggܸq#22RNX˗5 PKH )>H0I ma͚52.UTZZKF Ff~T:cFՉc$X]$!a6zŏWXCW~C+}cmA~YVp1m$ K??\[u!7"QA҉:UiR,2Ǐ @9sY)))~>})V4gRʇ s{ n$YM >{L 6v>]L۶}vd~EvةI1sݻwEdFZN1T&snHƖ#u `(W\Q>gϞwg:~V7IuqϛFGFe*n9_ d5~u\;F--X's=3Y&?Ҳj2]QM !0}|v7oތ3)l!OdA12j*K,]tYcXm+~ev,9~aa W;c֬ن;wwcJ'w4%&@8q 0ӦM6}$ҲϚ=fLl2 ;R'eqPphJߠRI&T z3dՉu>/ 3ĔyEwRayR#G&@o *0lj3`dC<🏂d"%kى,vȦk,hgեIB#ʖ-޿CݹX&%%kkG5;nz .Z]Ji RK٪iiǵle nk㮬TMP8 7|Rҥm [n+m~cdEUEڂ .^TgI%)(P[Z‹,vq*ݐB֞Gy:tPjժ%u5T!_3bs-YYY:/2'i5iN_4/D> cFɹv v7 |<~*uQhq`{=F}:͞pD8/D> c0l6HIM59)ݸx" WIΝ;? 66V")WaM999o?+R1<f:RVß|3m'Nӭ>ݍ[`0$]vU5k4ppHqiƻ=1,m(77Wٳ#l/D>L@\jurz7Ggddhc*Ujդ AZjӻwQ̘'ō3[N;[Oϋ֮[guΝxORa {Z ]ff*T *$#ʮd{Y\B%=P@#G#+]&bݻwI]%m-Z I&M$B T^7w\1N=k8QwM`HHHP^'뮵Ǥ);ٵk)~`ҥʻ-O>Uvf Sٰ&0Zܨ(]իWlc৶$$t#a50%RRR R\9UZl=C6N@5ى Sm۶lc„ `!PCCC=/_^{L2+ۘ2ew,X@ɓ'O'URE@:udO?@udv11R+88XYf]vM 7>>^{́jj…`%##CСCt]y5 ]NNu]2[Hu7nv@2e(;<<\O?mFW-[:uZbŨQZn]tie3f СC6nܨmF*wC>}d7?0icmĈ~ڶm5j8w90 *w_8 ?HRZsVde'OT=zH/mO`111gwþ}ݛݻwKc!2TRE4h ҥ3fhwξzoFm 22Ry@[}{p%Y\tmC`+WV&U+l]6}*k׮چlopp˗/k7Y` *\ҽ1ƍi cVXaΝrɽP ͚5@pu1.;sLbT~~w-Zvء]@VǎS#5**jĉ[n҅H;֭[Y9F.3ӧe NYC1fի/^jk׮iپ}%KnZƍQ{weyv:uJ.m6Ǐh!+@%V)SF 岛7on޼*$+V1bġCL¯^j۶*QF ڵk0 &ZիJvxIIIdeʕ6lzԦViڴi[NK, eRya9ȑ#_}L +*[lLLLjj)S;ț_%JPpX- @ ˗e3!UV;v3fΜ@ `׮]JRhٲLnI29a\/_^k#Y/ m 0Y;~xU .ςO=zthh)RdƌVI9.*WܬY3skgo]*4dِ0y<]PV;who߾-֤I&i /|6ȐW:..N#e֬Yd^^zrAizj 5j`ӧ{F[6lؠ!;;СC[lQ>@E߿_{{_Evޭ@<۷T[J,)׹[jժ[;wٳgZky_;]D Znn'N(xQŊe+ 0@V_kksU/oI ^Tvmـhڵe{F޽k߾rXfͤ);$KsCҼW{˚5kkJ.k{9?޴iG@޽kɒ%[VZ\3h DeM^|@AAA UXk׮iXbrMrrvRUBI&O`HY5j(ƍѺu œQQQkr6m<8fHx*Nac>|XJ@޽{נAuex/VXYB%i5$D@nݪd0ӧϔ)WA򤻎ѣʢh00ѩ]/X<gP =LF@?[~6m A$=ql۾]ɺ kYD#P\9_ +]CWvٞ${```ʕ bŊndHII``ǏKϟk/_V\Y_^n.~]瓉'/\h [$#{|#Շ ߰vZ^bŪU֬Y=z >|񉉉iii7nܘ7oINN5a|4WÆP4l" tM*t]!{=_3_n /^\*ȧ/+5]z՜Z.]#eR;)˳)S̶!;Ԟ7mNTv};]ޚaݻch0#ZC!6?*EJv޾=R'DUYsnԐ9FO4d|nB wڥ>0`gVJ/"KI=ex۶9=X;ĉr5 Y1gm @˖-U` ._o:v%5u|,Co衐# C-<^%0 sRbcȲ  _~H"*I7o^RRqe/D[IH-~dUoR*pZqsP$7{ 60Ğ={<7!CBd*`|g+VUVu)pprrc:.0/Y4ߠ8I&am6eTwёưk׮w RK_, x&O"oܸc|޳#Չ CH;.GJǣN~کӾ+wY?˗/noׯ Z:s.sεkN#Fh3&u\ `(k'}FVd; TŬ%p}pEV{˫H Eq ƭ| -X/46\A`+]kk޽{v^^=פ!q2|o <#jԖ4# pW$$Ȅ4ׯ$q$a$YYY>1WNޣ'Ot~d[3 yLСCM[9O 3FׯkhU"cd0V1#NW&%%k=zȐd3O CV5[9XJO;߼D姤… g,_\JFk4 I-NVV/SNY]du~4mfus3q!$h3O,@qYf)w^I.S/R\s:vbE\Vf6|D/zy\~C;FRs8fPӥRnnv}k'!˛ ΐI`KI*{ #˦ǎӞwi/;w?c\)5}5ۀzg}u~!լUg]ka\pѰ( ֭Gխ;>Y{ˆ  2e _E6 }~'C[.]$ԥ?l "óf8v󓜜HןQIgvzi٭WL|hľqAzk~Ø2up}`qÇׅMo)u kjsO\W^Re:8.ee3ꋬ,-l0 [`/<0ФI/? A^Vt L2߿/4M/(X+]\W~i˖#GU\R'1Y^ 7od׻y` ,,L *am>HRb ذѣ ܹc,OUlΜ^ f:V#ܥqYÇAB=HKŋ+QV-m'UVU&u t0mۮ2 f,].. av.p޷o_yrY$Ql5)Xma)Ȗ=gg#Ye˻AlٳgͳڵvҼyseoҁ,~!`47o34Zrk*VH78w+}SRS - f7CrT޶:Qj'hnz(`/'*{0`/B[bb$wr%]Cotcv۶gŶl~flb^3mNjjվulBcaEنrD|<Ȗښo )% `kY3/_TMJj*V ;(_xa[%-M673}*? t,]ߚi_BLΝ;"X6ѲeKC6Bm9svhhvލ BURݚ#[Ք~2t5.]jִLb!{_@)effa/qbK+VhF?Ahm  a%o߁2ˇH,HKճ CGnz6ݽ{[xnn1c `BP\\lȦvJ:JC6QB|eÌ ޾}琖L` ` n芝;wC*M&D7n0ă}oJw7DB$B!i;` _'$&In+WCSQQtxN>dC< M]+ ^KgnO-ah]!e h+ ,\P`֬Y7ЩS*B˗ugP}L@:Ҿ4~$0x.}/ Cqn WALbI&gmr nݺ?V!б(:@ x 6$( PڷֻД4lY1DG(ޠ&D7ăJi /rcǎV>!x>|k5nvnh/iPW#^8eee 8C޶m;ݯ_Vo C(\z-Gbӷ?lF~oP"޽{Yt47]FFFN^4!L¶Jw~6IAм|=j G&sHOP'kY47<'NSaҤ)㕍`|zvxl=ԩ` U0,PdDm A!9HS@Zoȇ9sZgTܻwO y; y"K| ec޽6l QQѸ=)//Wu6^㑣G#o}ظs2a9`-߸y'N;sV`ovY(C}ah''DFFak֬ Ѕ[u\\ɓP H4pE _ٹ(,+ +c,@ 0)HQUH#AeEHDUmkW?)vvv!@\_tnmmp8DSwww ϙg=$zOV㢯fT*@ dwH~_Dqp8|yy`~~^Z0P.eEݷ75;;;HDqxx-8==8P(dJRrR~Z`в,1 `Zz<1ib6׫@$+@"BA0zf܅t``zzZ`d2+b6\__+f__  d2)ښ2`J˲9@~_̃p8X\\ J)noo0hkk{W A?nIe ɉ|>_RjrT*rX,6::lV 0pttZ>??7 e&ٙѨ"Ѩ\.G{_WIЀ AF2 2JXPhP 1d-apqvϻk4Zo b1(|||z. ?j5~`X^^?R)~`x||L$%V@÷J RRjvj} ?-Ir\V_^^?zRDfggnnnsgv@C&j60da`` \.www@C.//bTӉ !C'''G@ʆ"`n er9mllҔ$I^ !s4MOOGf$I M0d8Bj"`J o{{;\]]z"72177z)zbff3[__DX {Z` }+++v;Lҷv#`D e;;;0d6HM__Y z}}M$$Ix3~~V&+JLHIbQkJ Ą{hHHim$QBjks'.サk븧<x|\.777WQQ!V[[{~~.88PU5~mK(凴TDpotuu]\\H>G)J*JCZؘ,C&q\wlV06N^WۚeyTUxL&z6, (`hoo099)4022"@noo#_ |_مgf / <00xe_ |?/N8`b$n;`0FEL0o݅sTɾ-d_W,.TpwHp<BpI@'A/߷NܙSH|}bvvvZZھ}vp5klٲ%%%'^p ñ000y'O.XH8xP&56 7D/0gC-`""""ɺjBύ7cgs7^RQQիWh DDDDD dfe&aG =8jݿ_hAe&0%%%*BϦDThܾ}[Ν;7l|}I0&%&wz̝g7JG2wƸȨE7>gΚd+VbANn.f#<Hee38tNr8il*ڠ@ja ӆq[B&6d6ƿQ'D>T1Y|Ǜm"_.܃f Юe@ISΜA]q n||P/뱫V tҔ?8xK XhX8qy[dp 9PUF{wtDDD}<,AoOEJ^;+**zuIƍ0:v־/^Dظv |šwH?K=(@hٳo}pFee%)Ϙ# 68hTA,PzzDSD,ݷ| "`w՝\0t5n7gH9si_ 7Nxs (P8>> $\ߏ? <>=l0nzPW:ޯ3=8'zmyy[> P{sFk/z,]"d(߲}%{`&""""Rc߾W60SD=1jּttgRZ}Gj X`UF5] ]:Ï>X=l0)itŸ?n}7IG-`tZ7X}еv:T 6P0}M #G^( X*)韙%%%FFF]իf͞% kj/Y&Q-  g/zLDDDḒ[tבD*ѣGY[}{"cڲe˴i߿/|:`@oaC;HƏ׮.nc<KKoV͕ D~ tҎ;f͚ճgϖ-[Zk˗U1K֟u֢E KΝ;˯5DD%cV#iABVVV~~I ՆUsW^5jc X+zڵkIIIs &^ɀ9\|?%̜9GF_1c$96l;qv?N3JK 9s֨/{w200ѿ%%Ν۩S'"""9{)[vZADD'>>A~mN,*|ҙQ{h[@i`_ l~wz_!3xL D(Ƿm⌴4aRJJ s[nmqFnn ""_ e;<(ty6Wob_|*z_ ,$s0S0Y8&-H2>>>((:YwNAf+{jjvs煞0 QJډr7+CL>fW iͲnc~g0O<1!\Ġ=;vd&['0::믿?DDgL҅nmUUгuvΞ3W4`h B\p0pf("#1` Lt Fdc׮]&O,/=|P7VzNJ[cJ;Qn07oNnn lt={RF nٺMxج/ک!l<Ǜm`0 fA6fϞmQvo+2_v4v+e!F6m\32LLa$L%% `"קO قl`Y# łU~+;ƾ:/hj[j_;9`sTcǎ7i  c_}B8-)X>6|܋/.?~Z+ ɣڈȟL1:LDPņ6g&[xyIS͚B-al.Tϒ;`0v-*|駂lXTDDbW6S4W7v[?[O0Ƅ.YǐKr#%%%dΌN`74W? DN]' `QaܹXrSؤIS춀r,vx ʕ1Fw:l#-p^QQةvFM&iŊGatNN΁1<,,!0hW͕mFuv@1?U̙;)LEFF>kjl{k?zXPgw͙3ǢBbb 95ȿegW\ƴT=|HsI遭J8‘1 sM1{,T{Nm]OBXK10ȴXP|3۷5c-LD){6|0J O,* ?FDD%v_Qʝ>dԅ\`#Fn۶m/Wɽ?`φb&RXɶڴiKLj 5~/aS0DDDUѕV!R0c]R,W73000BMF6(++ˢBPPkDDds@o[nӣQhʯ0:tX7/|力 ٳg*g*ԄZʢBhh(L""’6&o'OPGMu9m^UU%|JoNö`}D 5A#Fp~DDW7XaOvI&| uPNYD iهI[׬u={Ƈ66}BMMB [TIDDX|<%e<д` ֭'Xjm鲐-t#pCޙ3Æt-[JNְ26}BM[-@XNݢ{g,0|M[ZĤ*dl*k^|y퓧LC᯻@igGٓu|7byGɇDV5kK7SbcW!77K@{r/-AN6=aSbVV6*;O8­10;92>ϘUφb&t =zԢ°aÄ DDDԊ\K=Eg_0QllEe˖ fQ!&&F1o:$F~,*;w!PjҜ4_NI,=(jmkoGsN0?jʲL,|x$ j$ i&|w`nTUXZuXAhF,LӤ +R,E!P/|8fьPti1C$(P}װŽ?kێ;֭[ vq={Dw .TqKHc,Ժm׮C@8998.F4f?ء[=xjժ,>Ŋkٲ%Y>i ህSjBQ$,X?#˜6mZΝ+W,QO>75j NIT-Zt_/0=|wE$T-V!C_>##CJ*bCM^PXf耱7͋U4㯎wŦ=Lzl磵z6x;`i 8d1DDD, WBU*jHq?CCBya"Q=4hP^{ycHBos*%J 6ϏKotV# ⊪UcJDDD,s {~ϟ)]2HJ+T^=/΀@{H}kc'hŋw5h7nP"""r&i1 B)5*$-vۨgH8 ai[naZC]={{7&NشiS)dZnc#JDDDsN Jֻwo ʯ333՛gU>-A+_6m4iD 7 b]K0/3ܘ 5p@pX+҆ @6ߖ-[sA30ae.I/%xz!|qnIUl߾]¯f͚/_TcZH )iB"}-ٰaC>cٳgnl3XԼqFM2Eș:u$9OhŊBV\7oP>GQ ρ~euKa0=z)~z/m111ByթSc6ҳǒVs\z"n>pJIIQ"""r& wۏuEP(t7 eO>qzMbbPd%"""7 `j۶^{ӧO~&W~RJ fÇ%!7m4f߾}(޽{c !DDDt5\qqqZ>SZj1>#:t>q"SoEt `""" `:uP jݼy3$:tHtP^E9s^zf 4B,, ϋE[ڵk7bv 4H(>}oPZKqk l޼Y=%:`={Cp?ާ6Q>ꫯ 6nܸ3gb'T=y$47ʾx"##իNڻw /_LӫWƤÇq _>HrN7n:q\f͜9sRRR׿{޻.]+WF>&Me֭۵kױcp?^|?"|x"m،V [nn+HDDD,C`Mٲe6m'Q<-Y-V}ʧţ0Wopq{c6~x- |=By_=NN5]?SdVZC2={P>OxԍHRpH'ԩĉ׮]c>,V;88O`ٲe8pD' AQFi&ĥh[mRSSq+KǏ^B;vnO{Vx|ԂuS";,QP4dffo|!Fp+ۣGٳg *<1}-Jt}Po>,ׯ_bbb2eIix&\IHH.6'F͛jA/)˷Z37UvmPRA }b' N83l-9{F ֡|۶mcǎmӦ |EDDMz+ ޽F1d. Hj?3RDDD, -uE; .h!IK@,--M,<ίAh""""sN1O?ipz%ϟSNbaŊꜫW]YDDDD, b!>>^UfMp1ECX8:'==]?~\ Xh׮0 0@,|GDn X uΧ~*ʕ+xwB߾}'DDDD, &58v ͚5S[,$%%s^|U,0r8p.nݺQϟ?K.=\0E:{,HDܹs\.]Z,ܸqC)׭z222-xՕ)SƁ +H (QHBg50#""bL_PbŊxarE58-H oѢ8 =ҶlْXӪU"ԩS'_-X@,`:TtRmd  ]?Æ ;q ""bL7 O'*EݻX0u]qÆ oԩXp#""bLBD¡C9 $w˜^|9/{XS.]$/c Wà&sxǓdNNM89&n? """t1T'O UxIuΝ;ȭ[ ۷j֬:0 2D%Kb%dѢEbGJO.0+3֭0 DDD, MJ+}Jw.,YС_ѧOXu `BCzҠE4m4 ǏMFԔ޽{b~ⅺbٳQF a~`"""tQ5yf Ⱦ}DuҥKBҥqرcGZDwm 1"""dqA4ܹsZZwut~8yʕǏܹsոsÒ.]xI A]\~])ȏ?n9t{=ڲe >ZhqXGpL锔4޽ۧm߾}E|Oր>|X,;Lm׌XԀ:gΜ9b!..N +]H+^ ;|)SX 1*U*狅͛+E+D,mV*u릪dd/X`ذa:uB za$*T!FDD쒖Ry ڵW,|Jw.Aㆯ۳ ק+ƌcRll,:qJ(!ѭf͚JeUWMع/{wD " >X1sQ4mTqPTTV@.(kv"{qI`FN"6"q/߽{:pաFy."ǎȳӧON&x RdffFu + E"==]q0rH4Z1d (i`qi<ơ`la8 u֊#G(&#V@^ńY'O8HNN&A3٣8ڵ+ VP'XD ,"HU4o_% }bbd6lMl-pWqLG-ZQo&"q[F6w&#;"sQ`҃˗/:iSq0vX5kȀZybCiiikג!׬]cβ¢"4ް_hv]jrrʢE!:v1)[j$$,v l>6}!KA bA\\ NCqn:*VѰUl2~A&»V$,vУG4-[A`&M(JJJ ?Sz${] IЏ#F` DlDeӦM6DqbԩSdϞ=SLț N7rd߾ǎ6 ?CD=~GN55VW9ztOx-ׯ? []!`oy6߾tC@O?1"l)ef`ʔ)҃[*4h`&… >1pQ 4U8q:u" m۹*&6w6mڈ37nTt֍8'A3N>8:u* V`]j ' һ":/8o9޸^c{mK UnnZyf.[0>yM?f+Dh{fȈv<|kݳwzS `<{eh˦xҀ4A,/%$A޽^,ZHqA~7@ E#տP$I9k((,BʥnO-2DB9ft `(C'@`o0!2u# w)pWˤ2i6F fl6~o\z 5\EJl+N<8@YC**!T;'Ν8@i]tQ N!Lq;>|uxW x(/6Cٚy!]GWW@%E"Vp&YTMOuu5Y _$4!_d}bW$h_pTT1 / w'i3R2&ؠMn=W0_v8.ǎpUC1dE \`G#s8p Z ` V ?"mc3f YV?3YM7n$mJsrA ~G4cΜ9fI'Dwo~/<=3ƍ"̀p PDG$xp<8hG2{ǎ B 0]} ;wq5HRRX`$ t4`ҥ  KaeL1H6";;[q Sn hIDc ˣV Y֞zc#"̀\\io, r5xf-%sU.k(FXN@6ϸ^~MJ`c80cߒAog.+E b~d5x_s%pq/ TG[VܹtJѣSNUA8p5htn!Qi$e9hBeXI0x9ݺu4=2 `smS/˗/Q㺃\~N.1iΗs `W`)&rrrZZz4ĉ\oya޽\lƧ>g7l 6,==/Lt 4 ~D\5HcF[/yNIIgϙ' ;wr5ư<]z`8~>g_.| NvUm^ `W`paĦMjL8((( @#A !77WKvzHq (4UÃNLL||m"AW_}{Wop"PRZjAբUWB`D_IK luvR޽zUVUmڴyK!ff;{qW^g47l-ܼ7,՘~8| 8H޶-1}+E k MB"Vb AIBBBygϞ'OX}9l ׯ_?wΝ;+'oNe̙QF* -X|ulvfϞ 1@~y7ԭI&Xn|~-Ƈ-;&M)1"m"6Y4j`ڌ`i]siK-"`PG~`x<u|  .կX"]VqHϿ~,0,sDtqIz0zhJPPw55\984wޅE9'NlٲAdIF~SAj-Z`GGGٸp">Ŀ v 37V "d"' {{:v:/d( ii~?^-]4 #[.+E R>>&$$ *ry (–vP[lHe$=)_=W%;ABߠ_9w\UUsXs碧1Ѝ,EݺuCyʕ/_F$%yuQf )"%K޽>H 9q pJKKo#ȑ#<K-!<{ر|w™|1>xmqg}:JIqkPE_ ԽzDV*_>bb_⫬81͡nXXXdd$|A o1XoMHDW`]r2"(1wl`Qy TgP ZY:B6ƱAO#? h8xѢ3yn6sgթW0v LNNYv7yٰa#~ g >2;C8TgJM:7Cz?"A~<(b),*۶ѣG*s c fXm$/_)"me3\zrMƅ<9/_N!:Հnj`EmD!!a[f޼yx9<%_E|S UrPI>vIʢ(Wu,j |f͚aÍ4l3;@S\\j9s6;/I6 %bl۶m[m5=1nE̙^?GUV"F]?C?\ŜEȪ_{|X~ֲm6 ĨVҘ8E*`+^xwФ-,/%- ; ȿwzȵe^4`tl+p)#D#1+h%'%%Uq_qx8jÇ'58S,V[2=oF L c䐂悳wB͛7Fx yR?iu7ѣgo޽GACLHH 'm߳Ǭ&+]ڭs"m 8@LXe R'G) q s!Y' F5`LSiLpԹ|sszHf-~4-- `7`? `>7Ws=zLod"" JzKRgMrr =0A0U ,a;wF0p@aGE?p,˓nzh>%l;?o$_Uj N+F۶=VAP-rD)XIITb6),I2z7m$ eΜ92|XBe` +<:=`=pAA?cf B`L>S槟\b4-Vd,%?qB1|tݺT ?7RTvt9tX1+cǎ¢ex%KTXX2r\aVVr0uTa E' 1ƶ-ZV'N7&'w2!LKX{"P꧟~%ݧ `6ݴzⅱrjPMbnf|bC'ϝ;4r~Embt]ayfDDDA˰2i$a"ׯoM8iX&,qMi NMK7ٱ!%= 5wޥ*o" OUب蘏yNIY={Q)Krc!6Y &B\=>v}.X g#Gٻh}-^ bV[S9sn=mzpI֬I%СCN>;dffJbpJjt'Ն V+ >trY7@{r "nVC-ZL0^BkpZ={fQzP ؾ:upG+#b>a yl2#oX̶matԪ]Oa*i{tg+o('ov0̚L1+ rʢ,7n܉' Φ#G-$_k=l0a II, /T Xؾ}{oݺu8uj+Ev%-[uv WgLDH"ۦmol>~ISB$sss)Va[ل$255Gv$p:t LZcR'oӦ3 4(MΚ5kG[nݚ?~͚5t G]_ =z7YlAo_~'OJVC >/@ Ifӏ"2$J)/:PSyQ3Nôi3ڜ)V5o\Dݺuɞe_8ވ߿ e]s΂ |p'dz[l`̛B^ZAk׮gϞI?cp/ 4jKUGݻ']cIOC"%!VܧA:Q繁Uc%! <)VA͛OpO7{)s-N41.^|.]hlιs.]&&MfM tȮGװaCOڷoecx/Xq*MMG\$t/TXV+=elt`M5  `@#14(>u4nԵ[@{"nݺ-!`M3 ` T_lذo߾XF a/** 7Z.ƈ^|Mzzŋ5/޹s_~%ul mVh̑#Gd)UxepwMׯyPFKJظ5kQ{xpؑe̿ǕJCս-Q 3yG""?`+-dgg]1ۼ\pAi;i>fѬf"챴zEITl7)o׮]?XZ鶍{RNr#ʸaL:iӦ"j`DD?~@[ueyO?6Ef`X>,>x?7o޺r*,}wGL>]c򍘰^ʖhZ7@X(?,yqur^z ̇~ѣGaGǥ v1i3g>]7n8~8$*uԩJ*!ړ~E*&EaR8O>JNNmxĈkFRQ9qD!(d>c8wEQc:C|\ؕcD/>X ֞={:NXë,t]vK6/!Q+#bX,VkA&G-[VZE))-sǏ A( Y|"D.sΥjD/233+% T*NHH}9C A( YmΪŠFJe!ll4:#*d66&sx_ !y}u0p/۶X9rK1Ɛ ! Ѻڈvr)%ֺR>eRwG"-X{SR?kk`NQ}DZ,4M iڀlyj8f"Ua1'ca jA1-UvL` 0`  0` 0` 0`  0` 0`I\v 0`0`0`  0` 0`0`  0 0`0`   0`0`   0`0`  0`0``  0`0`0`~@ $q\-Ijz   0`0``  0`0pFIENDB`python-attrs-attrs-bd2446d/docs/_static/sponsors/000077500000000000000000000000001476453530700222165ustar00rootroot00000000000000python-attrs-attrs-bd2446d/docs/_static/sponsors/FilePreviews.svg000066400000000000000000000121051476453530700253420ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/Klaviyo.svg000066400000000000000000000125621476453530700243630ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/Polar.svg000066400000000000000000000126251476453530700240220ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/Privacy-Solutions.svg000066400000000000000000000310731476453530700263550ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/Tidelift.svg000066400000000000000000000040421476453530700245030ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/Variomedia.svg000066400000000000000000000036141476453530700250230ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/_static/sponsors/emsys-renewables.svg000066400000000000000000000510431476453530700262270ustar00rootroot00000000000000 python-attrs-attrs-bd2446d/docs/api-attr.rst000066400000000000000000000125771476453530700211710ustar00rootroot00000000000000API Reference for the ``attr`` Namespace ======================================== .. note:: These are the traditional APIs whose creation predates type annotations. They are **not** deprecated, but we suggest using the :mod:`attrs` namespace for new code, because they look nicer and have better defaults. See also :doc:`names`. .. module:: attr Core ---- .. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True, unsafe_hash=None) For example: .. doctest:: >>> import attr >>> @attr.s ... class C: ... _private = attr.ib() >>> C(private=42) C(_private=42) >>> class D: ... def __init__(self, x): ... self.x = x >>> D(1) >>> D = attr.s(these={"x": attr.ib()}, init=False)(D) >>> D(1) D(x=1) >>> @attr.s(auto_exc=True) ... class Error(Exception): ... x = attr.ib() ... y = attr.ib(default=42, init=False) >>> Error("foo") Error(x='foo', y=42) >>> raise Error("foo") Traceback (most recent call last): ... Error: ('foo', 42) >>> raise ValueError("foo", 42) # for comparison Traceback (most recent call last): ... ValueError: ('foo', 42) .. autofunction:: attr.ib .. note:: *attrs* also comes with a serious-business alias ``attr.attrib``. The object returned by `attr.ib` also allows for setting the default and the validator using decorators: .. doctest:: >>> @attr.s ... class C: ... x = attr.ib() ... y = attr.ib() ... @x.validator ... def _any_name_except_a_name_of_an_attribute(self, attribute, value): ... if value < 0: ... raise ValueError("x must be positive") ... @y.default ... def _any_name_except_a_name_of_an_attribute(self): ... return self.x + 1 >>> C(1) C(x=1, y=2) >>> C(-1) Traceback (most recent call last): ... ValueError: x must be positive .. function:: attrs Serious business alias for `attr.s`. .. function:: define Same as `attrs.define`. .. function:: mutable Same as `attrs.mutable`. .. function:: frozen Same as `attrs.frozen`. .. function:: field Same as `attrs.field`. .. class:: Attribute Same as `attrs.Attribute`. .. function:: make_class Same as `attrs.make_class`. .. autoclass:: Factory :noindex: Same as `attrs.Factory`. .. data:: NOTHING Same as `attrs.NOTHING`. Exceptions ---------- .. module:: attr.exceptions All exceptions are available from both ``attr.exceptions`` and `attrs.exceptions` (it's the same module in a different namespace). Please refer to `attrs.exceptions` for details. Helpers ------- .. currentmodule:: attr .. function:: cmp_using Same as `attrs.cmp_using`. .. function:: fields Same as `attrs.fields`. .. function:: fields_dict Same as `attrs.fields_dict`. .. function:: has Same as `attrs.has`. .. function:: resolve_types Same as `attrs.resolve_types`. .. autofunction:: asdict .. autofunction:: astuple .. module:: attr.filters .. function:: include Same as `attrs.filters.include`. .. function:: exclude Same as `attrs.filters.exclude`. See :func:`attrs.asdict` for examples. All objects from `attrs.filters` are also available in ``attr.filters``. ---- .. currentmodule:: attr .. function:: evolve Same as `attrs.evolve`. .. function:: validate Same as `attrs.validate`. Validators ---------- .. module:: attr.validators All objects from `attrs.validators` are also available in ``attr.validators``. Please refer to the former for details. Converters ---------- .. module:: attr.converters All objects from `attrs.converters` are also available from ``attr.converters``. Please refer to the former for details. Setters ------- .. module:: attr.setters All objects from `attrs.setters` are also available in ``attr.setters``. Please refer to the former for details. Deprecated APIs --------------- .. currentmodule:: attr To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0. It behaves similarly to `sys.version_info` and is an instance of `attr.VersionInfo`: .. autoclass:: VersionInfo With its help you can write code like this: >>> if getattr(attr, "__version_info__", (0,)) >= (19, 2): ... cmp_off = {"eq": False} ... else: ... cmp_off = {"cmp": False} >>> cmp_off == {"eq": False} True >>> @attr.s(**cmp_off) ... class C: ... pass ---- .. autofunction:: assoc Before *attrs* got `attrs.validators.set_disabled` and `attrs.validators.set_disabled`, it had the following APIs to globally enable and disable validators. They won't be removed, but are discouraged to use: .. autofunction:: set_run_validators .. autofunction:: get_run_validators ---- The serious-business aliases used to be called ``attr.attributes`` and ``attr.attr``. There are no plans to remove them but they shouldn't be used in new code. python-attrs-attrs-bd2446d/docs/api.rst000066400000000000000000000566541476453530700202250ustar00rootroot00000000000000API Reference ============= .. module:: attrs *attrs* works by decorating a class using `attrs.define` or `attr.s` and then defining attributes on the class using `attrs.field`, `attr.ib`, or type annotations. What follows is the dry API explanation for people who understand how *attrs* works. If you'd like a hands-on tutorial, have a look at `examples`. If you're confused by the many names, please check out `names` for clarification, but the `TL;DR `_ is that as of version 21.3.0, *attrs* consists of **two** top-level package names: - The classic ``attr`` that powers the venerable `attr.s` and `attr.ib`. - The newer ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes. Additionally, some of the APIs that also exist in ``attr`` have nicer defaults (for example, `attrs.asdict`). The ``attrs`` namespace is built *on top of* ``attr`` -- which will *never* go away -- and is just as stable, since it doesn't constitute a rewrite. To keep repetition low and this document at a reasonable size, the ``attr`` namespace is `documented on a separate page `. Core ---- .. autofunction:: attrs.define .. function:: mutable(same_as_define) Same as `attrs.define`. .. versionadded:: 20.1.0 .. function:: frozen(same_as_define) Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*. .. versionadded:: 20.1.0 .. autofunction:: field .. autoclass:: Attribute :members: evolve For example: .. doctest:: >>> import attrs >>> from attrs import define, field >>> @define ... class C: ... x = field() >>> attrs.fields(C).x Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x') .. autofunction:: make_class This is handy if you want to programmatically create classes. For example: .. doctest:: >>> C1 = attrs.make_class("C1", ["x", "y"]) >>> C1(1, 2) C1(x=1, y=2) >>> C2 = attrs.make_class("C2", { ... "x": field(default=42), ... "y": field(factory=list) ... }) >>> C2() C2(x=42, y=[]) .. autoclass:: Factory For example: .. doctest:: >>> @define ... class C: ... x = field(default=attrs.Factory(list)) ... y = field(default=attrs.Factory( ... lambda self: set(self.x), ... takes_self=True) ... ) >>> C() C(x=[], y=set()) >>> C([1, 2, 3]) C(x=[1, 2, 3], y={1, 2, 3}) .. autodata:: attrs.NOTHING :no-value: Exceptions ---------- .. module:: attrs.exceptions All exceptions are available from both ``attr.exceptions`` and ``attrs.exceptions`` and are the same thing. That means that it doesn't matter from from which namespace they've been raised and/or caught: .. doctest:: >>> import attrs, attr >>> try: ... raise attrs.exceptions.FrozenError() ... except attr.exceptions.FrozenError: ... print("this works!") this works! .. autoexception:: PythonTooOldError .. autoexception:: FrozenError .. autoexception:: FrozenInstanceError .. autoexception:: FrozenAttributeError .. autoexception:: AttrsAttributeNotFoundError .. autoexception:: NotAnAttrsClassError .. autoexception:: DefaultAlreadySetError .. autoexception:: NotCallableError .. autoexception:: UnannotatedAttributeError For example:: @attr.s(auto_attribs=True) class C: x: int y = attr.ib() # <- ERROR! .. _helpers: Helpers ------- *attrs* comes with a bunch of helper methods that make working with it easier: .. currentmodule:: attrs .. autofunction:: attrs.cmp_using .. autofunction:: attrs.fields For example: .. doctest:: >>> @define ... class C: ... x = field() ... y = field() >>> attrs.fields(C) (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')) >>> attrs.fields(C)[1] Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') >>> attrs.fields(C).y is attrs.fields(C)[1] True .. autofunction:: attrs.fields_dict For example: .. doctest:: >>> @attr.s ... class C: ... x = attr.ib() ... y = attr.ib() >>> attrs.fields_dict(C) {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')} >>> attr.fields_dict(C)['y'] Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y') >>> attrs.fields_dict(C)['y'] is attrs.fields(C).y True .. autofunction:: attrs.has For example: .. doctest:: >>> @attr.s ... class C: ... pass >>> attr.has(C) True >>> attr.has(object) False .. autofunction:: attrs.resolve_types For example: .. doctest:: >>> import typing >>> @define ... class A: ... a: typing.List['A'] ... b: 'B' ... >>> @define ... class B: ... a: A ... >>> attrs.fields(A).a.type typing.List[ForwardRef('A')] >>> attrs.fields(A).b.type 'B' >>> attrs.resolve_types(A, globals(), locals()) >>> attrs.fields(A).a.type typing.List[A] >>> attrs.fields(A).b.type .. autofunction:: attrs.asdict For example: .. doctest:: >>> @define ... class C: ... x: int ... y: int >>> attrs.asdict(C(1, C(2, 3))) {'x': 1, 'y': {'x': 2, 'y': 3}} .. autofunction:: attrs.astuple For example: .. doctest:: >>> @define ... class C: ... x = field() ... y = field() >>> attrs.astuple(C(1,2)) (1, 2) .. module:: attrs.filters *attrs* includes helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`: .. autofunction:: include .. autofunction:: exclude See :func:`attrs.asdict` for examples. All objects from ``attrs.filters`` are also available from ``attr.filters`` (it's the same module in a different namespace). ---- .. currentmodule:: attrs .. autofunction:: attrs.evolve For example: .. doctest:: >>> @define ... class C: ... x: int ... y: int >>> i1 = C(1, 2) >>> i1 C(x=1, y=2) >>> i2 = attrs.evolve(i1, y=3) >>> i2 C(x=1, y=3) >>> i1 == i2 False ``evolve`` creates a new instance using ``__init__``. This fact has several implications: * private attributes should be specified without the leading underscore, just like in ``__init__``. * attributes with ``init=False`` can't be set with ``evolve``. * the usual ``__init__`` validators will validate the new values. .. autofunction:: attrs.validate For example: .. doctest:: >>> @define(on_setattr=attrs.setters.NO_OP) ... class C: ... x = field(validator=attrs.validators.instance_of(int)) >>> i = C(1) >>> i.x = "1" >>> attrs.validate(i) Traceback (most recent call last): ... TypeError: ("'x' must be (got '1' that is a ).", ...) .. _api-validators: Validators ---------- .. module:: attrs.validators *attrs* comes with some common validators in the ``attrs.validators`` module. All objects from ``attrs.validators`` are also available from ``attr.validators`` (it's the same module in a different namespace). .. autofunction:: attrs.validators.lt For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.lt(42)) >>> C(41) C(x=41) >>> C(42) Traceback (most recent call last): ... ValueError: ("'x' must be < 42: 42") .. autofunction:: attrs.validators.le For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.le(42)) >>> C(42) C(x=42) >>> C(43) Traceback (most recent call last): ... ValueError: ("'x' must be <= 42: 43") .. autofunction:: attrs.validators.ge For example: .. doctest:: >>> @define ... class C: ... x = attrs.field(validator=attrs.validators.ge(42)) >>> C(42) C(x=42) >>> C(41) Traceback (most recent call last): ... ValueError: ("'x' must be => 42: 41") .. autofunction:: attrs.validators.gt For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.gt(42)) >>> C(43) C(x=43) >>> C(42) Traceback (most recent call last): ... ValueError: ("'x' must be > 42: 42") .. autofunction:: attrs.validators.max_len For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.max_len(4)) >>> C("spam") C(x='spam') >>> C("bacon") Traceback (most recent call last): ... ValueError: ("Length of 'x' must be <= 4: 5") .. autofunction:: attrs.validators.min_len For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.min_len(1)) >>> C("bacon") C(x='bacon') >>> C("") Traceback (most recent call last): ... ValueError: ("Length of 'x' must be => 1: 0") .. autofunction:: attrs.validators.instance_of For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.instance_of(int)) >>> C(42) C(x=42) >>> C("42") Traceback (most recent call last): ... TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, validator=>, type=None, kw_only=False), , '42') >>> C(None) Traceback (most recent call last): ... TypeError: ("'x' must be (got None that is a ).", Attribute(name='x', default=NOTHING, validator=>, repr=True, cmp=True, hash=None, init=True, type=None, kw_only=False), , None) .. autofunction:: attrs.validators.in_ For example: .. doctest:: >>> import enum >>> class State(enum.Enum): ... ON = "on" ... OFF = "off" >>> @define ... class C: ... state = field(validator=attrs.validators.in_(State)) ... val = field(validator=attrs.validators.in_([1, 2, 3])) >>> C(State.ON, 1) C(state=, val=1) >>> C("On", 1) Traceback (most recent call last): ... ValueError: 'state' must be in (got 'On'), Attribute(name='state', default=NOTHING, validator=>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), , 'on') >>> C(State.ON, 4) Traceback (most recent call last): ... ValueError: 'val' must be in [1, 2, 3] (got 4), Attribute(name='val', default=NOTHING, validator=, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), [1, 2, 3], 4) .. autofunction:: attrs.validators.and_ For convenience, it's also possible to pass a list to `attrs.field`'s validator argument. Thus the following two statements are equivalent:: x = field(validator=attrs.validators.and_(v1, v2, v3)) x = field(validator=[v1, v2, v3]) .. autofunction:: attrs.validators.or_ For example: .. doctest:: >>> @define ... class C: ... val: int | list[int] = field( ... validator=attrs.validators.or_( ... attrs.validators.instance_of(int), ... attrs.validators.deep_iterable(attrs.validators.instance_of(int)), ... ) ... ) >>> C(42) C(val=42) >>> C([1, 2, 3]) C(val=[1, 2, 3]) >>> C(val='42') Traceback (most recent call last): ... ValueError: None of (>, >>) satisfied for value '42' .. autofunction:: attrs.validators.not_ For example: .. doctest:: >>> reserved_names = {"id", "time", "source"} >>> @define ... class Measurement: ... tags = field( ... validator=attrs.validators.deep_mapping( ... key_validator=attrs.validators.not_( ... attrs.validators.in_(reserved_names), ... msg="reserved tag key", ... ), ... value_validator=attrs.validators.instance_of((str, int)), ... ) ... ) >>> Measurement(tags={"source": "universe"}) Traceback (most recent call last): ... ValueError: ("reserved tag key", Attribute(name='tags', default=NOTHING, validator=, capturing (, )>, type=None, kw_only=False), , {'source_': 'universe'}, (, )) >>> Measurement(tags={"source_": "universe"}) Measurement(tags={'source_': 'universe'}) .. autofunction:: attrs.validators.optional For example: .. doctest:: >>> @define ... class C: ... x = field( ... validator=attrs.validators.optional( ... attrs.validators.instance_of(int) ... )) >>> C(42) C(x=42) >>> C("42") Traceback (most recent call last): ... TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, validator=>, type=None, kw_only=False), , '42') >>> C(None) C(x=None) .. autofunction:: attrs.validators.is_callable For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.is_callable()) >>> C(isinstance) C(x=) >>> C("not a callable") Traceback (most recent call last): ... attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a ). .. autofunction:: attrs.validators.matches_re For example: .. doctest:: >>> @define ... class User: ... email = field(validator=attrs.validators.matches_re( ... r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")) >>> User(email="user@example.com") User(email='user@example.com') >>> User(email="user@example.com@test.com") Traceback (most recent call last): ... ValueError: ("'email' must match regex '(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\\\.[a-zA-Z0-9-.]+$)' ('user@example.com@test.com' doesn't)", Attribute(name='email', default=NOTHING, validator=, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)'), 'user@example.com@test.com') .. autofunction:: attrs.validators.deep_iterable For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.deep_iterable( ... member_validator=attrs.validators.instance_of(int), ... iterable_validator=attrs.validators.instance_of(list) ... )) >>> C(x=[1, 2, 3]) C(x=[1, 2, 3]) >>> C(x=set([1, 2, 3])) Traceback (most recent call last): ... TypeError: ("'x' must be (got {1, 2, 3} that is a ).", Attribute(name='x', default=NOTHING, validator=> iterables of >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , {1, 2, 3}) >>> C(x=[1, 2, "3"]) Traceback (most recent call last): ... TypeError: ("'x' must be (got '3' that is a ).", Attribute(name='x', default=NOTHING, validator=> iterables of >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , '3') .. autofunction:: attrs.validators.deep_mapping For example: .. doctest:: >>> @define ... class C: ... x = field(validator=attrs.validators.deep_mapping( ... key_validator=attrs.validators.instance_of(str), ... value_validator=attrs.validators.instance_of(int), ... mapping_validator=attrs.validators.instance_of(dict) ... )) >>> C(x={"a": 1, "b": 2}) C(x={'a': 1, 'b': 2}) >>> C(x=None) Traceback (most recent call last): ... TypeError: ("'x' must be (got None that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , None) >>> C(x={"a": 1.0, "b": 2}) Traceback (most recent call last): ... TypeError: ("'x' must be (got 1.0 that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , 1.0) >>> C(x={"a": 1, 7: 2}) Traceback (most recent call last): ... TypeError: ("'x' must be (got 7 that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , 7) Validators can be both globally and locally disabled: .. autofunction:: attrs.validators.set_disabled .. autofunction:: attrs.validators.get_disabled .. autofunction:: attrs.validators.disabled Converters ---------- .. autoclass:: attrs.Converter For example: .. doctest:: >>> def complicated(value, self_, field): ... return int(value) * self_.factor + field.metadata["offset"] >>> @define ... class C: ... factor = 5 # not an *attrs* field ... x = field( ... metadata={"offset": 200}, ... converter=attrs.Converter( ... complicated, ... takes_self=True, takes_field=True ... )) >>> C("42") C(x=410) .. module:: attrs.converters All objects from ``attrs.converters`` are also available from ``attr.converters`` (it's the same module in a different namespace). .. autofunction:: attrs.converters.pipe For convenience, it's also possible to pass a list to `attrs.field` / `attr.ib`'s converter arguments. Thus the following two statements are equivalent:: x = attrs.field(converter=attrs.converter.pipe(c1, c2, c3)) x = attrs.field(converter=[c1, c2, c3]) .. autofunction:: attrs.converters.optional For example: .. doctest:: >>> @define ... class C: ... x = field(converter=attrs.converters.optional(int)) >>> C(None) C(x=None) >>> C(42) C(x=42) .. autofunction:: attrs.converters.default_if_none For example: .. doctest:: >>> @define ... class C: ... x = field( ... converter=attrs.converters.default_if_none("") ... ) >>> C(None) C(x='') .. autofunction:: attrs.converters.to_bool(val) For example: .. doctest:: >>> @define ... class C: ... x = field( ... converter=attrs.converters.to_bool ... ) >>> C("yes") C(x=True) >>> C(0) C(x=False) >>> C("norway") Traceback (most recent call last): File "", line 1, in ValueError: Cannot convert value to bool: norway .. _api_setters: Setters ------- .. module:: attrs.setters These are helpers that you can use together with `attrs.define`'s and `attrs.field`'s ``on_setattr`` arguments. All setters in ``attrs.setters`` are also available from ``attr.setters`` (it's the same module in a different namespace). .. autofunction:: frozen .. autofunction:: validate .. autofunction:: convert .. autofunction:: pipe .. data:: NO_OP Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. Does not work in `attrs.setters.pipe` or within lists. .. versionadded:: 20.1.0 For example, only ``x`` is frozen here: .. doctest:: >>> @define(on_setattr=attr.setters.frozen) ... class C: ... x = field() ... y = field(on_setattr=attr.setters.NO_OP) >>> c = C(1, 2) >>> c.y = 3 >>> c.y 3 >>> c.x = 4 Traceback (most recent call last): ... attrs.exceptions.FrozenAttributeError: () .. tip:: Use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient. python-attrs-attrs-bd2446d/docs/changelog.md000066400000000000000000000003271476453530700211550ustar00rootroot00000000000000```{include} ../CHANGELOG.md :end-before: Changes for the upcoming release can be found ``` ```{towncrier-draft-entries} main ``` ```{include} ../CHANGELOG.md :start-after: towncrier release notes start --> ``` python-attrs-attrs-bd2446d/docs/comparison.md000066400000000000000000000041011476453530700213720ustar00rootroot00000000000000# Comparison By default, two instances of *attrs* classes are equal if they have the same type and all their fields are equal. For that, *attrs* writes `__eq__` and `__ne__` methods for you. Additionally, if you pass `order=True`, *attrs* will also create a complete set of ordering methods: `__le__`, `__lt__`, `__ge__`, and `__gt__`. For equality, *attrs* will generate a statement comparing the types of both instances, and then comparing each attribute in turn using `==`. For order, *attrs* will: - Check if the types of the instances you're comparing are equal, - if so, create a tuple of all field values for each instance, - and finally perform the desired comparison operation on those tuples. (custom-comparison)= ## Customization As with other features, you can exclude fields from being involved in comparison operations: ```{doctest} >>> from attrs import define, field >>> @define ... class C: ... x: int ... y: int = field(eq=False) >>> C(1, 2) == C(1, 3) True ``` Additionally you can also pass a *callable* instead of a bool to both *eq* and *order*. It is then used as a key function like you may know from {func}`sorted`: ```{doctest} >>> @define ... class S: ... x: str = field(eq=str.lower) >>> S("foo") == S("FOO") True >>> @define(order=True) ... class C: ... x: str = field(order=int) >>> C("10") > C("2") True ``` This is especially useful when you have fields with objects that have atypical comparison properties. Common examples of such objects are [NumPy arrays](https://github.com/python-attrs/attrs/issues/435). To save you unnecessary boilerplate, *attrs* comes with the {func}`attrs.cmp_using` helper to create such functions. For NumPy arrays it would look like this: ```python import numpy @define class C: an_array = field(eq=attrs.cmp_using(eq=numpy.array_equal)) ``` :::{warning} Please note that *eq* and *order* are set *independently*, because *order* is `False` by default in {func}`~attrs.define` (but not in {func}`attr.s`). You can set both at once by using the *cmp* argument that we've undeprecated just for this use-case. ::: python-attrs-attrs-bd2446d/docs/conf.py000066400000000000000000000137651476453530700202150ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import os from importlib import metadata from pathlib import Path # Set canonical URL from the Read the Docs Domain html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") # Tell Jinja2 templates the build is running on Read the Docs if os.environ.get("READTHEDOCS", "") == "True": html_context = {"READTHEDOCS": True} # -- Path setup ----------------------------------------------------------- PROJECT_ROOT_DIR = Path(__file__).parents[1].resolve() # -- General configuration ------------------------------------------------ doctest_global_setup = """ from attr import define, frozen, field, validators, Factory """ linkcheck_ignore = [ # Fastly blocks this. "https://pypi.org/project/attr/#history", # We run into GitHub's rate limits. r"https://github.com/.*/(issues|pull)/\d+", # Rate limits and the latest tag is missing anyways on release. "https://github.com/python-attrs/attrs/tree/.*", # GitHub just completely broke anchors hashtag modern web dev. "https://github.com/python-attrs/attrs/commit/88aa1c897dfe2ee4aa987e4a56f2ba1344a17238#diff-4fc63db1f2fcb7c6e464ee9a77c3c74e90dd191d1c9ffc3bdd1234d3a6663dc0R48", ] # In nitpick mode (-n), still ignore any of the following "broken" references # to non-types. nitpick_ignore = [ ("py:class", "Any value"), ("py:class", "callable"), ("py:class", "callables"), ("py:class", "tuple of types"), ] # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_parser", "sphinx.ext.napoleon", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.todo", "notfound.extension", "sphinxcontrib.towncrier", ] myst_enable_extensions = [ "colon_fence", "smartquotes", "deflist", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "attrs" author = "Hynek Schlawack" copyright = f"2015, {author}" # 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 full version, including alpha/beta/rc tags. release = metadata.version("attrs") if "dev" in release: release = version = "UNRELEASED" else: # The short X.Y version. version = release.rsplit(".", 1)[0] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = "any" # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # -- 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 = "furo" html_theme_options = { "sidebar_hide_name": True, "light_logo": "attrs_logo.svg", "dark_logo": "attrs_logo_white.svg", "top_of_page_buttons": [], "light_css_variables": { "font-stack": "Inter,sans-serif", "font-stack--monospace": "BerkeleyMono, MonoLisa, ui-monospace, " "SFMono-Regular, Menlo, Consolas, Liberation Mono, monospace", }, } html_css_files = ["custom.css"] # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If 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 = False # 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 = '' # Output file base name for HTML help builder. htmlhelp_basename = "attrsdoc" # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "attrs", "attrs Documentation", ["Hynek Schlawack"], 1)] # -- 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 = [ ( "index", "attrs", "attrs Documentation", "Hynek Schlawack", "attrs", "Python Classes Without Boilerplate", "Miscellaneous", ) ] epub_description = "Python Classes Without Boilerplate" intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # Allow non-local URIs so we can have images in CHANGELOG etc. suppress_warnings = ["image.nonlocal_uri"] # -- Options for sphinxcontrib.towncrier extension ------------------------ towncrier_draft_autoversion_mode = "draft" towncrier_draft_include_empty = True towncrier_draft_working_directory = PROJECT_ROOT_DIR towncrier_draft_config_path = "pyproject.toml" python-attrs-attrs-bd2446d/docs/examples.md000066400000000000000000000525751476453530700210600ustar00rootroot00000000000000# *attrs* by Example ## Basics The simplest possible usage is: ```{doctest} >>> from attrs import define, field >>> @define ... class Empty: ... pass >>> Empty() Empty() >>> Empty() == Empty() True >>> Empty() is Empty() False ``` So in other words: *attrs* is useful even without actual {term}`fields `! But you'll usually want some data on your classes, so let's add some: ```{doctest} >>> @define ... class Coordinates: ... x: int ... y: int ``` By default, all features are added, so you immediately have a fully functional data class with a nice `repr` string and comparison methods. ```{doctest} >>> c1 = Coordinates(1, 2) >>> c1 Coordinates(x=1, y=2) >>> c2 = Coordinates(x=2, y=1) >>> c2 Coordinates(x=2, y=1) >>> c1 == c2 False ``` As shown, the generated `__init__` method allows for both positional and keyword arguments. --- Unlike Data Classes, *attrs* doesn't force you to use type annotations. So, the previous example could also have been written as: ```{doctest} >>> @define ... class Coordinates: ... x = field() ... y = field() >>> Coordinates(1, 2) Coordinates(x=1, y=2) ``` :::{caution} If a class body contains a field that is defined using {func}`attrs.field` (or {func}`attr.ib`), but **lacks a type annotation**, *attrs* switches to a no-typing mode and ignores fields that have type annotations but are not defined using {func}`attrs.field` (or {func}`attr.ib`). ::: --- For private attributes, *attrs* will strip the leading underscores for keyword arguments: ```{doctest} >>> @define ... class C: ... _x: int >>> C(x=1) C(_x=1) ``` If you want to initialize your private attributes yourself, you can do that too: ```{doctest} >>> @define ... class C: ... _x: int = field(init=False, default=42) >>> C() C(_x=42) >>> C(23) Traceback (most recent call last): ... TypeError: __init__() takes exactly 1 argument (2 given) ``` If you prefer to expose your privates, you can use keyword argument aliases: ```{doctest} >>> @define ... class C: ... _x: int = field(alias="_x") >>> C(_x=1) C(_x=1) ``` An additional way of defining attributes is supported too. This is useful in times when you want to enhance classes that are not yours (nice `__repr__` for Django models anyone?): ```{doctest} >>> class SomethingFromSomeoneElse: ... def __init__(self, x): ... self.x = x >>> SomethingFromSomeoneElse = define( ... these={ ... "x": field() ... }, init=False)(SomethingFromSomeoneElse) >>> SomethingFromSomeoneElse(1) SomethingFromSomeoneElse(x=1) ``` [Subclassing is bad for you](https://www.youtube.com/watch?v=3MNVP9-hglc) (except when doing [strict specialization](https://hynek.me/articles/python-subclassing-redux/)), but *attrs* will still do what you'd hope for: ```{doctest} >>> @define(slots=False) ... class A: ... a: int ... def get_a(self): ... return self.a >>> @define(slots=False) ... class B: ... b: int >>> @define(slots=False) ... class C(B, A): ... c: int >>> i = C(1, 2, 3) >>> i C(a=1, b=2, c=3) >>> i == C(1, 2, 3) True >>> i.get_a() 1 ``` {term}`Slotted classes `, which are the default for the new APIs, don't play well with multiple inheritance so we don't use them in the example. The order of the attributes is defined by the [MRO](https://www.python.org/download/releases/2.3/mro/). ### Keyword-only Attributes You can also add [keyword-only](https://docs.python.org/3/glossary.html#keyword-only-parameter) attributes: ```{doctest} >>> @define ... class A: ... a: int = field(kw_only=True) >>> A() Traceback (most recent call last): ... TypeError: A() missing 1 required keyword-only argument: 'a' >>> A(a=1) A(a=1) ``` `kw_only` may also be specified at decorator level, and will apply to all attributes: ```{doctest} >>> @define(kw_only=True) ... class A: ... a: int ... b: int >>> A(1, 2) Traceback (most recent call last): ... TypeError: __init__() takes 1 positional argument but 3 were given >>> A(a=1, b=2) A(a=1, b=2) ``` If you create an attribute with `init=False`, the `kw_only` argument is ignored. Keyword-only attributes allow subclasses to add attributes without default values, even if the base class defines attributes with default values: ```{doctest} >>> @define ... class A: ... a: int = 0 >>> @define ... class B(A): ... b: int = field(kw_only=True) >>> B(b=1) B(a=0, b=1) >>> B() Traceback (most recent call last): ... TypeError: B() missing 1 required keyword-only argument: 'b' ``` If you don't set `kw_only=True`, then there is no valid attribute ordering, and you'll get an error: ```{doctest} >>> @define ... class A: ... a: int = 0 >>> @define ... class B(A): ... b: int Traceback (most recent call last): ... ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, converter=None, metadata=mappingproxy({}), type=int, kw_only=False) ``` (asdict)= ## Converting to Collections Types When you have a class with data, it often is very convenient to transform that class into a {class}`dict` (for example if you want to serialize it to JSON): ```{doctest} >>> from attrs import asdict >>> asdict(Coordinates(x=1, y=2)) {'x': 1, 'y': 2} ``` Some fields cannot or should not be transformed. For that, {func}`attrs.asdict` offers a callback that decides whether an attribute should be included: ```{doctest} >>> @define ... class User: ... email: str ... password: str >>> @define ... class UserList: ... users: list[User] >>> asdict(UserList([User("jane@doe.invalid", "s33kred"), ... User("joe@doe.invalid", "p4ssw0rd")]), ... filter=lambda attr, value: attr.name != "password") {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]} ``` For the common case where you want to [`include`](attrs.filters.include) or [`exclude`](attrs.filters.exclude) certain types, string name or attributes, *attrs* ships with a few helpers: ```{doctest} >>> from attrs import asdict, filters, fields >>> @define ... class User: ... login: str ... password: str ... email: str ... id: int >>> asdict( ... User("jane", "s33kred", "jane@example.com", 42), ... filter=filters.exclude(fields(User).password, "email", int)) {'login': 'jane'} >>> @define ... class C: ... x: str ... y: str ... z: int >>> asdict(C("foo", "2", 3), ... filter=filters.include(int, fields(C).x)) {'x': 'foo', 'z': 3} >>> asdict(C("foo", "2", 3), ... filter=filters.include(fields(C).x, "z")) {'x': 'foo', 'z': 3} ``` :::{note} Though using string names directly is convenient, mistyping attribute names will silently do the wrong thing and neither Python nor your type checker can help you. {func}`attrs.fields()` will raise an `AttributeError` when the field doesn't exist while literal string names won't. Using {func}`attrs.fields()` to get attributes is worth being recommended in most cases. ```{doctest} >>> asdict( ... User("jane", "s33kred", "jane@example.com", 42), ... filter=filters.exclude("passwd") ... ) {'login': 'jane', 'password': 's33kred', 'email': 'jane@example.com', 'id': 42} >>> asdict( ... User("jane", "s33kred", "jane@example.com", 42), ... filter=fields(User).passwd ... ) Traceback (most recent call last): ... AttributeError: 'UserAttributes' object has no attribute 'passwd'. Did you mean: 'password'? ``` ::: Other times, all you want is a tuple and *attrs* won't let you down: ```{doctest} >>> import sqlite3 >>> from attrs import astuple >>> @define ... class Foo: ... a: int ... b: int >>> foo = Foo(2, 3) >>> with sqlite3.connect(":memory:") as conn: ... c = conn.cursor() ... c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS ... c.execute("INSERT INTO foo VALUES (?, ?)", astuple(foo)) #doctest: +ELLIPSIS ... foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone()) >>> foo == foo2 True ``` For more advanced transformations and conversions, we recommend you look at a companion library (such as [*cattrs*](https://catt.rs/)). ## Defaults Sometimes you want to have default values for your initializer. And sometimes you even want mutable objects as default values (ever accidentally used `def f(arg=[])`?). *attrs* has you covered in both cases: ```{doctest} >>> import collections >>> @define ... class Connection: ... socket: int ... @classmethod ... def connect(cls, db_string): ... # ... connect somehow to db_string ... ... return cls(socket=42) >>> @define ... class ConnectionPool: ... db_string: str ... pool: collections.deque = Factory(collections.deque) ... debug: bool = False ... def get_connection(self): ... try: ... return self.pool.pop() ... except IndexError: ... if self.debug: ... print("New connection!") ... return Connection.connect(self.db_string) ... def free_connection(self, conn): ... if self.debug: ... print("Connection returned!") ... self.pool.appendleft(conn) ... >>> cp = ConnectionPool("postgres://localhost") >>> cp ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False) >>> conn = cp.get_connection() >>> conn Connection(socket=42) >>> cp.free_connection(conn) >>> cp ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False) ``` More information on why class methods for constructing objects are awesome can be found in this insightful [blog post](https://web.archive.org/web/20210130220433/http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html). Default factories can also be set using the `factory` argument to {func}`~attrs.field`, and using a decorator. The method receives the partially initialized instance which enables you to base a default value on other attributes: ```{doctest} >>> @define ... class C: ... x: int = 1 ... y: int = field() ... @y.default ... def _any_name_except_a_name_of_an_attribute(self): ... return self.x + 1 ... z: list = field(factory=list) >>> C() C(x=1, y=2, z=[]) ``` Please keep in mind that the decorator approach *only* works if the attribute in question has a {func}`~attrs.field` assigned to it. As a result, annotating an attribute with a type is *not* enough if you use `@default`. (examples-validators)= ## Validators Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments. *attrs* offers two ways to define validators for each attribute and it's up to you to choose which one suits your style and project better. You can use a decorator: ```{doctest} >>> @define ... class C: ... x: int = field() ... @x.validator ... def check(self, attribute, value): ... if value > 42: ... raise ValueError("x must be smaller or equal to 42") >>> C(42) C(x=42) >>> C(43) Traceback (most recent call last): ... ValueError: x must be smaller or equal to 42 ``` ...or a callable... ```{doctest} >>> from attrs import validators >>> def x_smaller_than_y(instance, attribute, value): ... if value >= instance.y: ... raise ValueError("'x' has to be smaller than 'y'!") >>> @define ... class C: ... x: int = field(validator=[validators.instance_of(int), ... x_smaller_than_y]) ... y: int >>> C(x=3, y=4) C(x=3, y=4) >>> C(x=4, y=3) Traceback (most recent call last): ... ValueError: 'x' has to be smaller than 'y'! ``` ...or both at once: ```{doctest} >>> @define ... class C: ... x: int = field(validator=validators.instance_of(int)) ... @x.validator ... def fits_byte(self, attribute, value): ... if not 0 <= value < 256: ... raise ValueError("value out of bounds") >>> C(128) C(x=128) >>> C("128") Traceback (most recent call last): ... TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=int, converter=None, kw_only=False), , '128') >>> C(256) Traceback (most recent call last): ... ValueError: value out of bounds ``` Please note that the decorator approach only works if -- and only if! -- the attribute in question has a {func}`~attrs.field` assigned. Therefore if you use `@validator`, it is *not* enough to annotate said attribute with a type. *attrs* ships with a bunch of validators, make sure to [check them out](api-validators) before writing your own: ```{doctest} >>> @define ... class C: ... x: int = field(validator=validators.instance_of(int)) >>> C(42) C(x=42) >>> C("42") Traceback (most recent call last): ... TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>, type=None, kw_only=False), , '42') ``` If using the old-school {func}`attr.s` decorator, validators only run on initialization by default. If using the newer {func}`attrs.define` and friends, validators run on initialization *and* on attribute setting. This behavior can be changed using the *on_setattr* argument. Check out {ref}`validators` for more details. ## Conversion Attributes can have a `converter` function specified, which will be called with the attribute's passed-in value to get a new value to use. This can be useful for doing type-conversions on values that you don't want to force your callers to do. ```{doctest} >>> @define ... class C: ... x: int = field(converter=int) >>> o = C("1") >>> o.x 1 >>> o.x = "2" >>> o.x 2 ``` If using the old-school {func}`attr.s` decorator, converters only run on initialization by default. If using the newer {func}`attrs.define` and friends, converters run on initialization *and* on attribute setting. This behavior can be changed using the *on_setattr* argument. Check out {ref}`converters` for more details. (metadata)= ## Metadata All *attrs* attributes may include arbitrary metadata in the form of a read-only dictionary. ```{doctest} >>> from attrs import fields >>> @define ... class C: ... x = field(metadata={'my_metadata': 1}) >>> fields(C).x.metadata mappingproxy({'my_metadata': 1}) >>> fields(C).x.metadata['my_metadata'] 1 ``` Metadata is not used by *attrs*, and is meant to enable rich functionality in third-party libraries. The metadata dictionary follows the normal dictionary rules: Keys need to be hashable, and both keys and values are recommended to be immutable. If you're the author of a third-party library with *attrs* integration, please see [*Extending Metadata*](extending-metadata). ## Types *attrs* also allows you to associate a type with an attribute using either the *type* argument to using {pep}`526`-annotations or {func}`attrs.field`/{func}`attr.ib`: ```{doctest} >>> @define ... class C: ... x: int >>> fields(C).x.type >>> import attr >>> @attr.s ... class C: ... x = attr.ib(type=int) >>> fields(C).x.type ``` If you don't mind annotating *all* attributes, you can even drop the `attrs.field` and assign default values instead: ```{doctest} >>> import typing >>> @define ... class AutoC: ... cls_var: typing.ClassVar[int] = 5 # this one is ignored ... l: list[int] = Factory(list) ... x: int = 1 ... foo: str = "every attrib needs a type if auto_attribs=True" ... bar: typing.Any = None >>> fields(AutoC).l.type list[int] >>> fields(AutoC).x.type >>> fields(AutoC).foo.type >>> fields(AutoC).bar.type typing.Any >>> AutoC() AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None) >>> AutoC.cls_var 5 ``` The generated `__init__` method will have an attribute called `__annotations__` that contains this type information. If your annotations contain strings (for example, forward references), you can resolve these after all references have been defined by using {func}`attrs.resolve_types`. This will replace the *type* attribute in the respective fields. ```{doctest} >>> from attrs import resolve_types >>> @define ... class A: ... a: 'list[A]' ... b: 'B' ... >>> @define ... class B: ... a: A ... >>> fields(A).a.type 'list[A]' >>> fields(A).b.type 'B' >>> resolve_types(A, globals(), locals()) >>> fields(A).a.type list[A] >>> fields(A).b.type ``` :::{note} If you find yourself using string type annotations to handle forward references, wrap the entire type annotation in quotes instead of only the type you need a forward reference to (so `'list[A]'` instead of `list['A']`). This is a limitation of the Python typing system. ::: :::{warning} *attrs* itself doesn't have any features that work on top of type metadata. However it's useful for writing your own validators or serialization frameworks. ::: ## Slots {term}`Slotted classes ` have several advantages on CPython. Defining `__slots__` by hand is tedious, in *attrs* it's just a matter of using {func}`attrs.define` or passing `slots=True` to {func}`attr.s`: ```{doctest} >>> @define ... class Coordinates: ... x: int ... y: int >>> import attr >>> @attr.s(slots=True) ... class Coordinates: ... x: int ... y: int ``` {func}`~attrs.define` sets `slots=True` by default. ## Immutability Sometimes you have instances that shouldn't be changed after instantiation. Immutability is especially popular in functional programming and is generally a very good thing. If you'd like to enforce it, *attrs* will try to help: ```{doctest} >>> from attrs import frozen >>> @frozen ... class C: ... x: int >>> i = C(1) >>> i.x = 2 Traceback (most recent call last): ... attrs.exceptions.FrozenInstanceError: can't set attribute >>> i.x 1 ``` Please note that true immutability is impossible in Python but it will [get](how-frozen) you 99% there. By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example. In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes. In Clojure that function is called [*assoc*](https://clojuredocs.org/clojure.core/assoc) and *attrs* shamelessly imitates it: {func}`attrs.evolve`: ```{doctest} >>> from attrs import evolve, frozen >>> @frozen ... class C: ... x: int ... y: int >>> i1 = C(1, 2) >>> i1 C(x=1, y=2) >>> i2 = evolve(i1, y=3) >>> i2 C(x=1, y=3) >>> i1 == i2 False ``` On Python 3.13 and later, you can also use {func}`copy.replace` from the standard library: ```{doctest} >>> import copy >>> @frozen ... class C: ... x: int ... y: int >>> i = C(1, 2) >>> copy.replace(i, y=3) C(x=1, y=3) ``` ## Other Goodies When building systems that have something resembling a plugin interface, you may want to have a registry of all classes that implement a certain interface: ```{doctest} >>> REGISTRY = [] >>> class Base: # does NOT have to be an attrs class! ... @classmethod ... def __attrs_init_subclass__(cls): ... REGISTRY.append(cls) >>> @define ... class Impl(Base): ... pass >>> REGISTRY [] ``` Sometimes you may want to create a class programmatically. *attrs* gives you {func}`attrs.make_class` for that: ```{doctest} >>> from attrs import make_class >>> @define ... class C1: ... x = field(type=int) ... y = field() >>> C2 = make_class("C2", {"x": field(type=int), "y": field()}) >>> fields(C1) == fields(C2) True >>> fields(C2).x.type ``` You can still have power over the attributes if you pass a dictionary of name: {func}`~attrs.field` mappings and can pass the same arguments as you can to `@attrs.define`: ```{doctest} >>> C = make_class("C", {"x": field(default=42), ... "y": field(default=Factory(list))}, ... repr=False) >>> i = C() >>> i # no repr added! <__main__.C object at ...> >>> i.x 42 >>> i.y [] ``` If you need to dynamically make a class with {func}`~attrs.make_class` and it needs to be a subclass of something else than {class}`object`, use the `bases` argument: ```{doctest} >>> class D: ... def __eq__(self, other): ... return True # arbitrary example >>> C = make_class("C", {}, bases=(D,), cmp=False) >>> isinstance(C(), D) True ``` Sometimes, you want to have your class's `__init__` method do more than just the initialization, validation, etc. that gets done for you automatically when using `@define`. To do this, just define a `__attrs_post_init__` method in your class. It will get called at the end of the generated `__init__` method. ```{doctest} >>> @define ... class C: ... x: int ... y: int ... z: int = field(init=False) ... ... def __attrs_post_init__(self): ... self.z = self.x + self.y >>> obj = C(x=1, y=2) >>> obj C(x=1, y=2, z=3) ``` You can exclude single attributes from certain methods: ```{doctest} >>> @define ... class C: ... user: str ... password: str = field(repr=False) >>> C("me", "s3kr3t") C(user='me') ``` Alternatively, to influence how the generated `__repr__()` method formats a specific attribute, specify a custom callable to be used instead of the `repr()` built-in function: ```{doctest} >>> @define ... class C: ... user: str ... password: str = field(repr=lambda value: '***') >>> C("me", "s3kr3t") C(user='me', password=***) ``` python-attrs-attrs-bd2446d/docs/extending.md000066400000000000000000000231261476453530700212150ustar00rootroot00000000000000# Extending Each *attrs*-decorated class has a `__attrs_attrs__` class attribute. It's a tuple of {class}`attrs.Attribute` carrying metadata about each attribute. So it is fairly simple to build your own decorators on top of *attrs*: ```{doctest} >>> from attrs import define >>> def print_attrs(cls): ... print(cls.__attrs_attrs__) ... return cls >>> @print_attrs ... @define ... class C: ... a: int (Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='a'),) ``` :::{warning} The {func}`attrs.define` / {func}`attr.s` decorator **must** be applied first because it puts `__attrs_attrs__` in place! That means that is has to come *after* your decorator because: ```python @a @b def f(): pass ``` is just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) for: ```python def original_f(): pass f = a(b(original_f)) ``` ::: ## Wrapping the Decorator A more elegant way can be to wrap *attrs* altogether and build a class [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) on top of it. An example for that is the package [*environ-config*](https://github.com/hynek/environ-config) that uses *attrs* under the hood to define environment-based configurations declaratively without exposing *attrs* APIs at all. Another common use case is to overwrite *attrs*'s defaults. ### Mypy Unfortunately, decorator wrapping currently [confuses](https://github.com/python/mypy/issues/5406) Mypy's *attrs* plugin. At the moment, the best workaround is to hold your nose, write a fake Mypy plugin, and mutate a bunch of global variables: ```python from mypy.plugin import Plugin from mypy.plugins.attrs import ( attr_attrib_makers, attr_class_makers, attr_dataclass_makers, ) # These work just like `attr.dataclass`. attr_dataclass_makers.add("my_module.method_looks_like_attr_dataclass") # This works just like `attr.s`. attr_class_makers.add("my_module.method_looks_like_attr_s") # These are our `attr.ib` makers. attr_attrib_makers.add("my_module.method_looks_like_attrib") class MyPlugin(Plugin): # Our plugin does nothing but it has to exist so this file gets loaded. pass def plugin(version): return MyPlugin ``` Then tell Mypy about your plugin using your project's `mypy.ini`: ```ini [mypy] plugins= ``` :::{warning} Please note that it is currently *impossible* to let Mypy know that you've changed defaults like *eq* or *order*. You can only use this trick to tell Mypy that a class is actually an *attrs* class. ::: ### Pyright Generic decorator wrapping is supported in [Pyright](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`681`. For a custom wrapping of the form: ``` @typing.dataclass_transform(field_specifiers=(attr.attrib, attrs.field)) def custom_define(f): return attrs.define(f) ``` ## Types *attrs* offers two ways of attaching type information to attributes: - {pep}`526` annotations, - and the *type* argument to {func}`attr.ib` / {func}`attrs.field`. This information is available to you: ```{doctest} >>> from attrs import define, field, fields >>> @define ... class C: ... x: int = field() ... y = field(type=str) >>> fields(C).x.type >>> fields(C).y.type ``` Currently, *attrs* doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers! Originally, we didn't add the *type* argument to the new {func}`attrs.field` API, because type annotations are the preferred way. But we reintroduced it later, so `field` can be used with the {func}`attrs.make_class` function. We strongly discourage the use of the *type* parameter outside of {func}`attrs.make_class`. (extending-metadata)= ## Metadata If you're the author of a third-party library with *attrs* integration, you may want to take advantage of attribute metadata. Here are some tips for effective use of metadata: - Try making your metadata keys and values immutable. This keeps the entire {class}`~attrs.Attribute` instances immutable too. - To avoid metadata key collisions, consider exposing your metadata keys from your modules.: ```python from mylib import MY_METADATA_KEY @define class C: x = field(metadata={MY_METADATA_KEY: 1}) ``` Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways. - Expose `field` wrappers for your specific metadata. This is a more graceful approach if your users don't require metadata from other libraries. ```{doctest} >>> from attrs import fields, NOTHING >>> MY_TYPE_METADATA = '__my_type_metadata' >>> >>> def typed( ... cls, default=NOTHING, validator=None, repr=True, ... eq=True, order=None, hash=None, init=True, metadata=None, ... converter=None ... ): ... metadata = metadata or {} ... metadata[MY_TYPE_METADATA] = cls ... return field( ... default=default, validator=validator, repr=repr, ... eq=eq, order=order, hash=hash, init=init, ... metadata=metadata, converter=converter ... ) >>> >>> @define ... class C: ... x: int = typed(int, default=1, init=False) >>> fields(C).x.metadata[MY_TYPE_METADATA] ``` (transform-fields)= ## Automatic Field Transformation and Modification *attrs* allows you to automatically modify or transform the class' fields while the class is being created. You do this by passing a *field_transformer* hook to {func}`~attrs.define` (and friends). Its main purpose is to automatically add converters to attributes based on their type to aid the development of API clients and other typed data loaders. This hook must have the following signature: ```{eval-rst} .. function:: your_hook(cls: type, fields: list[attrs.Attribute]) -> list[attrs.Attribute] :noindex: ``` - *cls* is your class right *before* it is being converted into an attrs class. This means it does not yet have the `__attrs_attrs__` attribute. - *fields* is a list of all `attrs.Attribute` instances that will later be set to `__attrs_attrs__`. You can modify these attributes any way you want: You can add converters, change types, and even remove attributes completely or create new ones! For example, let's assume that you really don't like floats: ```{doctest} >>> def drop_floats(cls, fields): ... return [f for f in fields if f.type not in {float, 'float'}] ... >>> @frozen(field_transformer=drop_floats) ... class Data: ... a: int ... b: float ... c: str ... >>> Data(42, "spam") Data(a=42, c='spam') ``` A more realistic example would be to automatically convert data that you, for example, load from JSON: ```{doctest} >>> from datetime import datetime >>> >>> def auto_convert(cls, fields): ... results = [] ... for field in fields: ... if field.converter is not None: ... results.append(field) ... continue ... if field.type in {datetime, 'datetime'}: ... converter = (lambda d: datetime.fromisoformat(d) if isinstance(d, str) else d) ... else: ... converter = None ... results.append(field.evolve(converter=converter)) ... return results ... >>> @frozen(field_transformer=auto_convert) ... class Data: ... a: int ... b: str ... c: datetime ... >>> from_json = {"a": 3, "b": "spam", "c": "2020-05-04T13:37:00"} >>> Data(**from_json) # **** Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37)) ``` Or, perhaps you would prefer to generate dataclass-compatible `__init__` signatures via a default field *alias*. Note, *field_transformer* operates on {class}`attrs.Attribute` instances before the default private-attribute handling is applied so explicit user-provided aliases can be detected. ```{doctest} >>> def dataclass_names(cls, fields): ... return [ ... field.evolve(alias=field.name) ... if not field.alias ... else field ... for field in fields ... ] ... >>> @frozen(field_transformer=dataclass_names) ... class Data: ... public: int ... _private: str ... explicit: str = field(alias="aliased_name") ... >>> Data(public=42, _private="spam", aliased_name="yes") Data(public=42, _private='spam', explicit='yes') ``` ## Customize Value Serialization in `asdict()` *attrs* allows you to serialize instances of *attrs* classes to dicts using the {func}`attrs.asdict` function. However, the result can not always be serialized since most data types will remain as they are: ```{doctest} >>> import json >>> import datetime >>> from attrs import asdict >>> >>> @frozen ... class Data: ... dt: datetime.datetime ... >>> data = asdict(Data(datetime.datetime(2020, 5, 4, 13, 37))) >>> data {'dt': datetime.datetime(2020, 5, 4, 13, 37)} >>> json.dumps(data) Traceback (most recent call last): ... TypeError: Object of type datetime is not JSON serializable ``` To help you with this, {func}`~attrs.asdict` allows you to pass a *value_serializer* hook. It has the signature ```{eval-rst} .. function:: your_hook(inst: type, field: attrs.Attribute, value: typing.Any) -> typing.Any :noindex: ``` ```{doctest} >>> from attr import asdict >>> def serialize(inst, field, value): ... if isinstance(value, datetime.datetime): ... return value.isoformat() ... return value ... >>> data = asdict( ... Data(datetime.datetime(2020, 5, 4, 13, 37)), ... value_serializer=serialize, ... ) >>> data {'dt': '2020-05-04T13:37:00'} >>> json.dumps(data) '{"dt": "2020-05-04T13:37:00"}' ``` python-attrs-attrs-bd2446d/docs/glossary.md000066400000000000000000000144001476453530700210660ustar00rootroot00000000000000# Glossary :::{glossary} dunder methods "Dunder" is a contraction of "double underscore". It's methods like `__init__` or `__eq__` that are sometimes also called *magic methods* or it's said that they implement an *object protocol*. In spoken form, you'd call `__init__` just "dunder init". Its first documented use is a [mailing list posting](https://mail.python.org/pipermail/python-list/2002-September/155836.html) by Mark Jackson from 2002. dict classes A regular class whose attributes are stored in the {attr}`object.__dict__` attribute of every single instance. This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances. This is the type of class you get by default both with and without *attrs* (except with the next APIs {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)). slotted classes A class whose instances have no {attr}`object.__dict__` attribute and [define](https://docs.python.org/3/reference/datamodel.html#slots) their attributes in a `object.__slots__` attribute instead. In *attrs*, they are created by passing `slots=True` to `@attr.s` (and are on by default in {func}`attrs.define()`, [`attrs.mutable()`](attrs.mutable), and [`attrs.frozen()`](attrs.frozen)). Their main advantage is that they use less memory on CPython[^pypy] and are slightly faster. However, they also come with several possibly surprising gotchas: - Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies `__slots__`: ```{doctest} >>> from attr import define >>> @define ... class Coordinates: ... x: int ... y: int ... >>> c = Coordinates(x=1, y=2) >>> c.z = 3 Traceback (most recent call last): ... AttributeError: 'Coordinates' object has no attribute 'z' ``` - Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that. If you must inherit from other classes, try to inherit only from other slotted classes. - However, [it's not possible](https://docs.python.org/3/reference/datamodel.html#slots) to inherit from more than one class that has attributes in `__slots__` (you will get an `TypeError: multiple bases have instance lay-out conflict`). - It's not possible to monkeypatch methods on slotted classes. This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell. If you really need to monkeypatch an instance in your tests, but don't want to give up on the advantages of slotted classes in production code, you can always subclass a slotted class as a dict class with no further changes and all the limitations go away: ```{doctest} >>> import unittest.mock >>> @define ... class Slotted: ... x: int ... ... def method(self): ... return self.x >>> s = Slotted(42) >>> s.method() 42 >>> with unittest.mock.patch.object(s, "method", return_value=23): ... pass Traceback (most recent call last): ... AttributeError: 'Slotted' object attribute 'method' is read-only >>> @define(slots=False) ... class Dicted(Slotted): ... pass >>> d = Dicted(42) >>> d.method() 42 >>> with unittest.mock.patch.object(d, "method", return_value=23): ... assert 23 == d.method() ``` - Slotted classes must implement {meth}`__getstate__ ` and {meth}`__setstate__ ` to be serializable with {mod}`pickle` protocol 0 and 1. Therefore, *attrs* creates these methods automatically for slotted classes. :::{note} When decorating with `@attr.s(slots=True)` and the class already implements the {meth}`__getstate__ ` and {meth}`__setstate__ ` methods, they will be *overwritten* by *attrs* autogenerated implementation by default. This can be avoided by setting `@attr.s(getstate_setstate=False)` or by setting `@attr.s(auto_detect=True)`. {func}`~attrs.define` sets `auto_detect=True` by default. ::: Also, [think twice](https://www.youtube.com/watch?v=7KnfGDajDQw) before using {mod}`pickle`. - Slotted classes are weak-referenceable by default. This can be disabled in CPython by passing `weakref_slot=False` to `@attr.s` [^pypyweakref]. - Since it's currently impossible to make a class slotted after it's been created, *attrs* has to replace your class with a new one. While it tries to do that as graciously as possible, certain metaclass features like {meth}`object.__init_subclass__` do not work with slotted classes. - The {attr}`type.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using {func}`gc.collect`), for the original class to be removed. See issue [#407](https://github.com/python-attrs/attrs/issues/407) for more details. - Pickling of slotted classes will fail if you define a class with missing attributes. This situation can occur if you define an `attrs.field(init=False)` and don't set the attribute by hand before pickling. field As the project name suggests, *attrs* is all about attributes. We especially tried to emphasize that we only care about attributes and not about the classes themselves -- because we believe the class belongs to the user. This explains why the traditional API uses an {func}`attr.ib` (or ``attrib``) function to define attributes and we still use the term throughout the documentation. However, with the emergence of {mod}`dataclasses`, [Pydantic](https://docs.pydantic.dev/latest/concepts/fields/), and other libraries, the term "field" has become a common term for a predefined attribute on a class in the Python ecosystem. So with our new APIs, we've embraced it too by calling the function to create them {func}`attrs.field`, and use the term "field" throughout the documentation interchangeably. See also {doc}`names`. attribute See {term}`field`. ::: [^pypy]: On PyPy, there is no memory advantage in using slotted classes. [^pypyweakref]: On PyPy, slotted classes are naturally weak-referenceable so `weakref_slot=False` has no effect. python-attrs-attrs-bd2446d/docs/hashing.md000066400000000000000000000120071476453530700206450ustar00rootroot00000000000000# Hashing ## Hash Method Generation :::{warning} The overarching theme is to never set the `@attrs.define(unsafe_hash=X)` parameter yourself. Leave it at `None` which means that *attrs* will do the right thing for you, depending on the other parameters: - If you want to make objects hashable by value: use `@define(frozen=True)`. - If you want hashing and equality by object identity: use `@define(eq=False)` Setting `unsafe_hash` yourself can have unexpected consequences so we recommend to tinker with it only if you know exactly what you're doing. ::: Under certain circumstances, it's necessary for objects to be *hashable*. For example if you want to put them into a {class}`set` or if you want to use them as keys in a {class}`dict`. The *hash* of an object is an integer that represents the contents of an object. It can be obtained by calling {func}`hash` on an object and is implemented by writing a `__hash__` method for your class. *attrs* will happily write a `__hash__` method for you [^fn1], however it will *not* do so by default. Because according to the [definition](https://docs.python.org/3/glossary.html#term-hashable) from the official Python docs, the returned hash has to fulfill certain constraints: [^fn1]: The hash is computed by hashing a tuple that consists of a unique id for the class plus all attribute values. 1. Two objects that are equal, **must** have the same hash. This means that if `x == y`, it *must* follow that `hash(x) == hash(y)`. By default, Python classes are compared *and* hashed by their `id`. That means that every instance of a class has a different hash, no matter what attributes it carries. It follows that the moment you (or *attrs*) change the way equality is handled by implementing `__eq__` which is based on attribute values, this constraint is broken. For that reason Python 3 will make a class that has customized equality unhashable. Python 2 on the other hand will happily let you shoot your foot off. Unfortunately, *attrs* still mimics (otherwise unsupported) Python 2's behavior for backward-compatibility reasons if you set `unsafe_hash=False`. The *correct way* to achieve hashing by id is to set `@define(eq=False)`. Setting `@define(unsafe_hash=False)` (which implies `eq=True`) is almost certainly a *bug*. :::{warning} Be careful when subclassing! Setting `eq=False` on a class whose base class has a non-default `__hash__` method will *not* make *attrs* remove that `__hash__` for you. It is part of *attrs*'s philosophy to only *add* to classes so you have the freedom to customize your classes as you wish. So if you want to *get rid* of methods, you'll have to do it by hand. The easiest way to reset `__hash__` on a class is adding `__hash__ = object.__hash__` in the class body. ::: 2. If two objects are not equal, their hash **should** be different. While this isn't a requirement from a standpoint of correctness, sets and dicts become less effective if there are a lot of identical hashes. The worst case is when all objects have the same hash which turns a set into a list. 3. The hash of an object **must not** change. If you create a class with `@define(frozen=True)` this is fulfilled by definition, therefore *attrs* will write a `__hash__` function for you automatically. You can also force it to write one with `unsafe_hash=True` but then it's *your* responsibility to make sure that the object is not mutated. This point is the reason why mutable structures like lists, dictionaries, or sets aren't hashable while immutable ones like tuples or `frozenset`s are: point 1 and 2 require that the hash changes with the contents but point 3 forbids it. For a more thorough explanation of this topic, please refer to this blog post: [*Python Hashes and Equality*](https://hynek.me/articles/hashes-and-equality/). :::{note} Please note that the `unsafe_hash` argument's original name was `hash` but was changed to conform with {pep}`681` in 22.2.0. The old argument name is still around and will **not** be removed -- but setting `unsafe_hash` takes precedence over `hash`. The field-level argument is still called `hash` and will remain so. ::: ## Hashing and Mutability Changing any field involved in hash code computation after the first call to `__hash__` (typically this would be after its insertion into a hash-based collection) can result in silent bugs. Therefore, it is strongly recommended that hashable classes be `frozen`. Beware, however, that this is not a complete guarantee of safety: if a field points to an object and that object is mutated, the hash code may change, but `frozen` will not protect you. ## Hash Code Caching Some objects have hash codes which are expensive to compute. If such objects are to be stored in hash-based collections, it can be useful to compute the hash codes only once and then store the result on the object to make future hash code requests fast. To enable caching of hash codes, pass `@define(cache_hash=True)`. This may only be done if *attrs* is already generating a hash function for the object. python-attrs-attrs-bd2446d/docs/how-does-it-work.md000066400000000000000000000125501476453530700223460ustar00rootroot00000000000000(how)= # How Does It Work? ## Boilerplate *attrs* isn't the first library that aims to simplify class definition in Python. But its **declarative** approach combined with **no runtime overhead** lets it stand out. Once you apply the `@attrs.define` (or `@attr.s`) decorator to a class, *attrs* searches the class object for instances of `attr.ib`s. Internally they're a representation of the data passed into `attr.ib` along with a counter to preserve the order of the attributes. Alternatively, it's possible to define them using {doc}`types`. In order to ensure that subclassing works as you'd expect it to work, *attrs* also walks the class hierarchy and collects the attributes of all base classes. Please note that *attrs* does *not* call `super()` *ever*. It will write {term}`dunder methods` to work on *all* of those attributes which also has performance benefits due to fewer function calls. Once *attrs* knows what attributes it has to work on, it writes the requested {term}`dunder methods` and -- depending on whether you wish to have a {term}`dict ` or {term}`slotted ` class -- creates a new class for you (`slots=True`) or attaches them to the original class (`slots=False`). While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally. To be very clear: if you define a class with a single attribute without a default value, the generated `__init__` will look *exactly* how you'd expect: ```{doctest} >>> import inspect >>> from attrs import define >>> @define ... class C: ... x: int >>> print(inspect.getsource(C.__init__)) def __init__(self, x): self.x = x ``` No magic, no meta programming, no expensive introspection at runtime. --- Everything until this point happens exactly *once* when the class is defined. As soon as a class is done, it's done. And it's just a regular Python class like any other, except for a single `__attrs_attrs__` attribute that *attrs* uses internally. Much of the information is accessible via {func}`attrs.fields` and other functions which can be used for introspection or for writing your own tools and decorators on top of *attrs* (like {func}`attrs.asdict`). And once you start instantiating your classes, *attrs* is out of your way completely. This **static** approach was very much a design goal of *attrs* and what I strongly believe makes it distinct. (how-frozen)= ## Immutability In order to give you immutability, *attrs* will attach a `__setattr__` method to your class that raises an {class}`attrs.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute. The same is true if you choose to freeze individual attributes using the {obj}`attrs.setters.frozen` *on_setattr* hook -- except that the exception becomes {class}`attrs.exceptions.FrozenAttributeError`. Both exceptions subclass {class}`attrs.exceptions.FrozenError`. --- Depending on whether a class is a dict class or a slotted class, *attrs* uses a different technique to circumvent that limitation in the `__init__` method. Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes. ### Dict Classes Dict classes -- that is: regular classes -- simply assign the value directly into the class' eponymous `__dict__` (and there's nothing we can do to stop the user to do the same). The performance impact is negligible. ### Slotted Classes Slotted classes are more complicated. Here it uses (an aggressively cached) {meth}`object.__setattr__` to set your attributes. This is (still) slower than a plain assignment: ```none $ pyperf timeit --rigorous \ -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \ "C(1, 2, 3)" ......................................... Mean +- std dev: 228 ns +- 18 ns $ pyperf timeit --rigorous \ -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \ "C(1, 2, 3)" ......................................... Mean +- std dev: 425 ns +- 16 ns ``` So on a laptop computer the difference is about 200 nanoseconds (1 second is 1,000,000,000 nanoseconds). It's certainly something you'll feel in a hot loop but shouldn't matter in normal code. Pick what's more important to you. ### Summary You should avoid instantiating lots of frozen slotted classes (meaning: `@frozen`) in performance-critical code. Frozen dict classes have barely a performance impact, unfrozen slotted classes are even *faster* than unfrozen dict classes (meaning: regular classes). (how-slotted-cached_property)= ## Cached Properties on Slotted Classes By default, the standard library {func}`functools.cached_property` decorator does not work on slotted classes, because it requires a `__dict__` to store the cached value. This could be surprising when using *attrs*, as slotted classes are the default. Therefore, *attrs* converts `cached_property`-decorated methods when constructing slotted classes. Getting this working is achieved by: * Adding names to `__slots__` for the wrapped methods. * Adding a `__getattr__` method to set values on the wrapped methods. For most users, this should mean that it works transparently. :::{note} The implementation does not guarantee that the wrapped method is called only once in multi-threaded usage. This matches the implementation of `cached_property` in Python 3.12. ::: python-attrs-attrs-bd2446d/docs/index.md000066400000000000000000000111531476453530700203340ustar00rootroot00000000000000# *attrs*: Classes Without Boilerplate Release **{sub-ref}`release`** ([What's new?](changelog.md)) ```{include} ../README.md :start-after: 'teaser-begin -->' :end-before: ' ```{include} ../README.md :start-after: 'sponsor-break-end -->' :end-before: '' :end-before: '### Hate Type Annotations!?' ``` ## Philosophy **It's about regular classes.** : *attrs* is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class. It can be used for data-only containers like `namedtuple`s or `types.SimpleNamespace` but they're just a sub-genre of what *attrs* is good for. **The class belongs to the users.** : You define a class and *attrs* adds static methods to that class based on the attributes you declare. The end. It doesn't add metaclasses. It doesn't add classes you've never heard of to your inheritance tree. An *attrs* class in runtime is indistinguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached. **Be light on API impact.** : As convenient as it seems at first, *attrs* will *not* tack on any methods to your classes except for the {term}`dunder ones `. Hence all the useful [tools](helpers) that come with *attrs* live in functions that operate on top of instances. Since they take an *attrs* instance as their first argument, you can attach them to your classes with one line of code. **Performance matters.** : *attrs* runtime impact is very close to zero because all the work is done when the class is defined. Once you're instantiating it, *attrs* is out of the picture completely. **No surprises.** : *attrs* creates classes that arguably work the way a Python beginner would reasonably expect them to work. It doesn't try to guess what you mean because explicit is better than implicit. It doesn't try to be clever because software shouldn't be clever. Check out {doc}`how-does-it-work` if you'd like to know how it achieves all of the above. ## What *attrs* Is Not *attrs* does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies. All *attrs* does is: 1. Take your declaration, 2. write {term}`dunder methods` based on that information, 3. and attach them to your class. It does *nothing* dynamic at runtime, hence zero runtime overhead. It's still *your* class. Do with it as you please. --- *attrs* also is *not* a fully-fledged serialization library. While it comes with features like converters and validators, it is meant to be a kit for building classes that you would write yourself – but with less boilerplate. If you look for powerful-yet-unintrusive serialization and validation for your *attrs* classes, have a look at our sibling project [*cattrs*](https://catt.rs/) or our [third-party extensions](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs). This separation of creating classes and serializing them is a conscious design decision. We don't think that your business model and your serialization format should be coupled. python-attrs-attrs-bd2446d/docs/types.md000066400000000000000000000130671476453530700203770ustar00rootroot00000000000000# Type Annotations *attrs* comes with first-class support for type annotations for both {pep}`526` and legacy syntax. However, they will remain *optional* forever, therefore the example from the README could also be written as: ```{doctest} >>> from attrs import define, field >>> @define ... class SomeClass: ... a_number = field(default=42) ... list_of_numbers = field(factory=list) >>> sc = SomeClass(1, [1, 2, 3]) >>> sc SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) ``` You can choose freely between the approaches, but please remember that if you choose to use type annotations, you **must** annotate **all** attributes! :::{caution} If you define a class with a {func}`attrs.field` that **lacks** a type annotation, *attrs* will **ignore** other fields that have a type annotation, but are not defined using {func}`attrs.field`: ```{doctest} >>> @define ... class SomeClass: ... a_number = field(default=42) ... another_number: int = 23 >>> SomeClass() SomeClass(a_number=42) ``` ::: Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features, though. One of those features are the decorator-based features like defaults. It's important to remember that *attrs* doesn't do any magic behind your back. All the decorators are implemented using an object that is returned by the call to {func}`attrs.field`. Attributes that only carry a class annotation do not have that object so trying to call a method on it will inevitably fail. --- Please note that types -- regardless how added -- are *only metadata* that can be queried from the class and they aren't used for anything out of the box! Because Python does not allow references to a class object before the class is defined, types may be defined as string literals, so-called *forward references* ({pep}`526`). You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`). In this case *attrs* simply puts these string literals into the `type` attributes. If you need to resolve these to real types, you can call {func}`attrs.resolve_types` which will update the attribute in place. In practice though, types show their biggest usefulness in combination with tools like [Mypy], [*pytype*], or [Pyright] that have dedicated support for *attrs* classes. The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you write *correct* and *verified self-documenting* code. ## Mypy While having a nice syntax for type metadata is great, it's even greater that [Mypy] ships with a dedicated *attrs* plugin which allows you to statically check your code. Imagine you add another line that tries to instantiate the defined class using `SomeClass("23")`. Mypy will catch that error for you: ```console $ mypy t.py t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected "int" ``` This happens *without* running your code! And it also works with *both* legacy annotation styles. To Mypy, this code is equivalent to the one above: ```python @attr.s class SomeClass: a_number = attr.ib(default=42) # type: int list_of_numbers = attr.ib(factory=list, type=list[int]) ``` The approach used for `list_of_numbers` one is only a available in our [old-style API](names.md) which is why the example still uses it. ## Pyright *attrs* provides support for [Pyright] through the `dataclass_transform` / {pep}`681` specification. This provides static type inference for a subset of *attrs* equivalent to standard-library {mod}`dataclasses`, and requires explicit type annotations using the {func}`attrs.define` or `@attr.s(auto_attribs=True)` API. Given the following definition, Pyright will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods: ``` @attrs.define class SomeClass: a_number: int = 42 list_of_numbers: list[int] = attr.field(factory=list) ``` :::{warning} The Pyright inferred types are a tiny subset of those supported by Mypy, including: - The `attrs.frozen` decorator is not typed with frozen attributes, which are properly typed via `attrs.define(frozen=True)`. Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782). Generally speaking, the decision on improving *attrs* support in Pyright is entirely Microsoft's prerogative and they unequivocally indicated that they'll only add support for features that go through the PEP process, though. ::: ## Class variables and constants If you are adding type annotations to all of your code, you might wonder how to define a class variable (as opposed to an instance variable), because a value assigned at class scope becomes a default for that attribute. The proper way to type such a class variable, though, is with {data}`typing.ClassVar`, which indicates that the variable should only be assigned in the class (or its subclasses) and not in instances of the class. *attrs* will skip over members annotated with {data}`typing.ClassVar`, allowing you to write a type annotation without turning the member into an attribute. Class variables are often used for constants, though they can also be used for mutable singleton data shared across all instances of the class. ``` @attrs.define class PngHeader: SIGNATURE: typing.ClassVar[bytes] = b'\x89PNG\r\n\x1a\n' height: int width: int interlaced: int = 0 ... ``` [Mypy]: http://mypy-lang.org [Pyright]: https://github.com/microsoft/pyright [*pytype*]: https://google.github.io/pytype/ python-attrs-attrs-bd2446d/docs/why.md000066400000000000000000000331361476453530700200410ustar00rootroot00000000000000# Why not… If you'd like third party's account why *attrs* is great, have a look at Glyph's [*The One Python Library Everyone Needs*](https://glyph.twistedmatrix.com/2016/08/attrs.html). It predates type annotations and hence Data Classes, but it masterfully illustrates the appeal of class-building packages. ## … Data Classes? {pep}`557` added Data Classes to [Python 3.7](https://docs.python.org/3.7/whatsnew/3.7.html#dataclasses) that resemble *attrs* in many ways. They are the result of the Python community's [wish](https://mail.python.org/pipermail/python-ideas/2017-May/045618.html) to have an easier way to write classes in the standard library that doesn't carry the problems of `namedtuple`s. To that end, *attrs* and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing `namedtuple`s, they are a huge win. Nevertheless, there are still reasons to prefer *attrs* over Data Classes. Whether they're relevant to *you* depends on your circumstances: - Data Classes are *intentionally* less powerful than *attrs*. There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, [equality customization](custom-comparison), a solution to the [`__init_subclass__` problem](init-subclass), or {doc}`extensibility ` in general -- it permeates throughout all APIs. On the other hand, Data Classes currently do not offer any significant feature that *attrs* doesn't already have. - We are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated. This includes stepping through generated methods using a debugger, cell rewriting to make bare `super()` calls work, or making {func}`functools.cached_property` work on slotted classes. - *attrs* supports all mainstream Python versions including PyPy. - *attrs* doesn't force type annotations on you if you don't like them. - But since it **also** supports typing, it's the best way to embrace type hints *gradually*, too. - While Data Classes are implementing features from *attrs* every now and then, their presence is dependent on the Python version, not the package version. For example, support for `__slots__` has only been added in Python 3.10, but it doesn’t do cell rewriting and therefore doesn’t support bare calls to `super()`. This may or may not be fixed in later Python releases, but handling all these differences is especially painful for PyPI packages that support multiple Python versions. And of course, this includes possible implementation bugs. - *attrs* can and will move faster. We are not bound to any release schedules and we have a clear deprecation policy. One of the [reasons](https://peps.python.org/pep-0557/#why-not-just-use-attrs) to not vendor *attrs* in the standard library was to not impede *attrs*'s future development. One way to think about *attrs* vs Data Classes is that *attrs* is a fully-fledged toolkit to write powerful classes while Data Classes are an easy way to get a class with some attributes. Basically what *attrs* was in 2015. ## … Pydantic? Pydantic is first and foremost a *data validation & type coercion library*. As such, it is a capable complement to class building libraries like *attrs* (or Data Classes!) for parsing and validating untrusted data. However, as convenient as it might be, using it for your business or domain layer [is problematic in several ways](https://threeofwands.com/why-i-use-attrs-instead-of-pydantic/): Is it really necessary to re-validate all your objects while reading them from a trusted database? Should the shape of your web API really apply design pressure on your business objects and therefore business code? In the parlance of [*Form, Command, and Model Validation*](https://verraes.net/2015/02/form-command-model-validation/), Pydantic is the right tool for *Commands*. [*Separation of concerns*](https://en.wikipedia.org/wiki/Separation_of_concerns) feels tedious at times, but it's one of those things that you get to appreciate once you've shot your own foot often enough and seen the results of allowing design pressure from the edges of your system, like ORMs or web APIs. *attrs* emphatically does **not** try to be a validation library, but a toolkit to write well-behaved classes like you would write yourself. If you'd like a powerful library for structuring, unstructuring, and validating data, have a look at [*cattrs*](https://catt.rs/) which is an official member of the *attrs* family. One of its core tenets is that it doesn't couple your classes to external factors. ## … namedtuples? {obj}`collections.namedtuple`s are tuples with names, not classes.[^history] Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited. However, that convenience comes at a price. The most obvious difference between `namedtuple`s and *attrs*-based classes is that the latter are type-sensitive: ```{doctest} >>> import attrs >>> C1 = attrs.make_class("C1", ["a"]) >>> C2 = attrs.make_class("C2", ["a"]) >>> i1 = C1(1) >>> i2 = C2(1) >>> i1.a == i2.a True >>> i1 == i2 False ``` …while a `namedtuple` is *intentionally* [behaving like a tuple](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences) which means the type of a tuple is *ignored*: ```{doctest} >>> from collections import namedtuple >>> NT1 = namedtuple("NT1", "a") >>> NT2 = namedtuple("NT2", "b") >>> t1 = NT1(1) >>> t2 = NT2(1) >>> t1 == t2 == (1,) True ``` Other often surprising behaviors include: - Since they are a subclass of tuples, `namedtuple`s have a length and are both iterable and indexable. That's not what you'd expect from a class and is likely to shadow subtle typo bugs. - Iterability also implies that it's easy to accidentally unpack a `namedtuple` which leads to hard-to-find bugs.[^iter] - `namedtuple`s have their methods *on your instances* whether you like it or not.[^pollution] - `namedtuple`s are *always* immutable. Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation? default values?), you have to implement {meth}`__new__() ` which is a particularly hacky and error-prone requirement for a very common problem.[^immutable] - To attach methods to a `namedtuple` you have to subclass it. And if you follow the standard library documentation's recommendation of: ``` class Point(namedtuple('Point', ['x', 'y'])): # ... ``` you end up with a class that has *two* `Point`s in its {attr}`__mro__ `: `[, , , ]`. That's not only confusing, it also has very practical consequences: for example if you create documentation that includes class hierarchies like [*Sphinx*'s autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) with `show-inheritance`. Again: common problem, hacky solution with confusing fallout. All these things make `namedtuple`s a particularly poor choice for public APIs because all your objects are irrevocably tainted. With *attrs* your users won't notice a difference because it creates regular, well-behaved classes. :::{admonition} Summary If you want a *tuple with names*, by all means: go for a `namedtuple`.[^perf] But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand. Other than that, *attrs* also adds nifty features like validators, converters, and (mutable!) default values. ::: [^history]: The word is that `namedtuple`s were added to the Python standard library as a way to make tuples in return values more readable. And indeed that is something you see throughout the standard library. Looking at what the makers of `namedtuple`s use it for themselves is a good guideline for deciding on your own use cases. [^pollution]: *attrs* only adds a single attribute: `__attrs_attrs__` for introspection. All helpers are functions in the `attr` package. Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice. [^iter]: {func}`attrs.astuple` can be used to get that behavior in *attrs* on *explicit demand*. [^immutable]: *attrs* offers *optional* immutability through the `frozen` keyword. [^perf]: Although *attrs* would serve you just as well! Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases *attrs* is even faster if you use `slots=True` (which is generally a good idea anyway). ## … tuples? ### Readability What makes more sense while debugging: ``` Point(x=1, y=2) ``` or: ``` (1, 2) ``` ? Let's add even more ambiguity: ``` Customer(id=42, reseller=23, first_name="Jane", last_name="John") ``` or: ``` (42, 23, "Jane", "John") ``` ? Why would you want to write `customer[2]` instead of `customer.first_name`? Don't get me started when you add nesting. If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly. Using proper classes with names and types makes program code much more readable and [comprehensible](https://arxiv.org/pdf/1304.5257.pdf). Especially when trying to grok a new piece of software or returning to old code after several months. ### Extendability Imagine you have a function that takes or returns a tuple. Especially if you use tuple unpacking (eg. `x, y = get_point()`), adding additional data means that you have to change the invocation of that function *everywhere*. Adding an attribute to a class concerns only those who actually care about that attribute. ## … dicts? Dictionaries are not for fixed fields. If you have a dict, it maps something to something else. You should be able to add and remove values. *attrs* lets you be specific about those expectations; a dictionary does not. It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class. In other words: if your dict has a fixed and known set of keys, it is an object, not a hash. So if you never iterate over the keys of a dict, you should use a proper class. ## … hand-written classes? While we're fans of all things artisanal, writing the same nine methods again and again doesn't qualify. I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested. To bring it into perspective, the equivalent of ```{doctest} >>> @attrs.define ... class SmartClass: ... a = attrs.field() ... b = attrs.field() >>> SmartClass(1, 2) SmartClass(a=1, b=2) ``` is roughly ```{doctest} >>> class ArtisanalClass: ... def __init__(self, a, b): ... self.a = a ... self.b = b ... ... def __repr__(self): ... return f"ArtisanalClass(a={self.a}, b={self.b})" ... ... def __eq__(self, other): ... if other.__class__ is self.__class__: ... return (self.a, self.b) == (other.a, other.b) ... else: ... return NotImplemented ... ... def __ne__(self, other): ... result = self.__eq__(other) ... if result is NotImplemented: ... return NotImplemented ... else: ... return not result ... ... def __lt__(self, other): ... if other.__class__ is self.__class__: ... return (self.a, self.b) < (other.a, other.b) ... else: ... return NotImplemented ... ... def __le__(self, other): ... if other.__class__ is self.__class__: ... return (self.a, self.b) <= (other.a, other.b) ... else: ... return NotImplemented ... ... def __gt__(self, other): ... if other.__class__ is self.__class__: ... return (self.a, self.b) > (other.a, other.b) ... else: ... return NotImplemented ... ... def __ge__(self, other): ... if other.__class__ is self.__class__: ... return (self.a, self.b) >= (other.a, other.b) ... else: ... return NotImplemented ... ... def __hash__(self): ... return hash((self.__class__, self.a, self.b)) >>> ArtisanalClass(a=1, b=2) ArtisanalClass(a=1, b=2) ``` That's quite a mouthful and it doesn't even use any of *attrs*'s more advanced features like validators or default values. Also: no tests whatsoever. And who will guarantee you, that you don't accidentally flip the `<` in your tenth implementation of `__gt__`? It also should be noted that *attrs* is not an all-or-nothing solution. You can freely choose which features you want and disable those that you want more control over: ```{doctest} >>> @attrs.define ... class SmartClass: ... a: int ... b: int ... ... def __repr__(self): ... return f"" >>> SmartClass(1, 2) ``` :::{admonition} Summary If you don't care and like typing, we're not gonna stop you. However it takes a lot of bias and determined rationalization to claim that *attrs* raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes. In any case, if you ever get sick of the repetitiveness and the drowning of important code in a sea of boilerplate, *attrs* will be waiting for you. ::: python-attrs-attrs-bd2446d/pyproject.toml000066400000000000000000000203021476453530700206630ustar00rootroot00000000000000# SPDX-License-Identifier: MIT [build-system] requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme>=23.2.0"] build-backend = "hatchling.build" [project] name = "attrs" authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }] license = "MIT" license-files = ["LICENSE"] requires-python = ">=3.8" description = "Classes Without Boilerplate" keywords = ["class", "attribute", "boilerplate"] classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Typing :: Typed", ] dependencies = [] dynamic = ["version", "readme"] [project.optional-dependencies] tests-mypy = [ 'pytest-mypy-plugins; platform_python_implementation == "CPython" and python_version >= "3.10"', # Since the mypy error messages keep changing, we have to keep updating this # pin. 'mypy>=1.11.1; platform_python_implementation == "CPython" and python_version >= "3.10"', ] tests = [ # For regression test to ensure cloudpickle compat doesn't break. 'cloudpickle; platform_python_implementation == "CPython"', "hypothesis", "pympler", # 4.3.0 dropped last use of `convert` "pytest>=4.3.0", "pytest-xdist[psutil]", "attrs[tests-mypy]", ] cov = [ "attrs[tests]", # Ensure coverage is new enough for `source_pkgs`. "coverage[toml]>=5.3", ] benchmark = ["pytest-codspeed", "pytest-xdist[psutil]", "attrs[tests]"] docs = [ "cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", ] dev = ["attrs[tests]", "pre-commit-uv"] [project.urls] Documentation = "https://www.attrs.org/" Changelog = "https://www.attrs.org/en/stable/changelog.html" GitHub = "https://github.com/python-attrs/attrs" Funding = "https://github.com/sponsors/hynek" Tidelift = "https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi" [tool.hatch.version] source = "vcs" raw-options = { local_scheme = "no-local-version" } [tool.hatch.build.targets.wheel] packages = ["src/attr", "src/attrs"] [tool.hatch.metadata.hooks.fancy-pypi-readme] content-type = "text/markdown" # PyPI doesn't support the tag. [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = """

attrs

""" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "README.md" start-after = "" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = """ ## Release Information """ [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "CHANGELOG.md" pattern = "\n(###.+?\n)## " [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = """ --- [Full changelog →](https://www.attrs.org/en/stable/changelog.html) """ # Point sponsor image URLs to versions. [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] pattern = 'docs\/_static\/sponsors' replacement = 'https://www.attrs.org/en/$HFPR_VERSION/_static/sponsors' [[tool.sponcon.sponsors]] title = "Variomedia AG" url = "https://www.variomedia.de/" img = "Variomedia.svg" [[tool.sponcon.sponsors]] title = "Tidelift" url = "https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek" img = "Tidelift.svg" [[tool.sponcon.sponsors]] title = "Klaviyo" url = "https://klaviyo.com/" img = "Klaviyo.svg" [[tool.sponcon.sponsors]] title = "Privacy Solutions" url = "https://privacy-solutions.org/" img = "Privacy-Solutions.svg" [[tool.sponcon.sponsors]] title = "emsys renewables" url = "https://www.emsys-renewables.com/" img = "emsys-renewables.svg" [[tool.sponcon.sponsors]] title = "FilePreviews" url = "https://filepreviews.io/" img = "FilePreviews.svg" [[tool.sponcon.sponsors]] title = "Polar" url = "https://polar.sh/" img = "Polar.svg" [tool.pytest.ini_options] addopts = ["-ra", "--strict-markers", "--strict-config"] xfail_strict = true testpaths = "tests" filterwarnings = ["once::Warning", "ignore:::pympler[.*]"] [tool.coverage.run] parallel = true branch = true source_pkgs = ["attr", "attrs"] [tool.coverage.paths] source = ["src", ".tox/py*/**/site-packages"] [tool.coverage.report] show_missing = true skip_covered = true exclude_lines = [ "pragma: no cover", # PyPy is unacceptably slow under coverage. "if PYPY:", # not meant to be executed ': \.\.\.$', '^ +\.\.\.$', ] [tool.interrogate] omit-covered-files = true verbose = 2 fail-under = 100 whitelist-regex = ["test_.*"] [tool.check-wheel-contents] toplevel = ["attr", "attrs"] [tool.ruff] src = ["src", "tests", "conftest.py", "docs"] line-length = 79 [tool.ruff.lint] select = ["ALL"] ignore = [ "A001", # shadowing is fine "A002", # shadowing is fine "A003", # shadowing is fine "ANN", # Mypy is better at this "ARG", # unused arguments are normal when implementing interfaces "C901", # we're complex software "COM", # ruff format takes care of our commas "D", # We prefer our own docstring style. "E501", # leave line-length enforcement to ruff format "ERA001", # we need to keep around some notes "FBT", # we don't hate bool args around here "FIX", # Yes, we want XXX as a marker. "ISC001", # conflicts with ruff format "N", # we need more naming freedom "PD", # we're not pandas "PLR0912", # we're complex software "PLR0913", # yes, many arguments, but most have defaults "PLR0915", # we're complex software "PLR2004", # numbers are sometimes fine "PLW0603", # sometimes we need globals "S307", # eval FTW "SLF001", # private members are accessed by friendly functions "TC", # TYPE_CHECKING blocks break autodocs "TD", # we don't follow other people's todo style "TRY301", # I'm sorry, but this makes not sense for us. "UP031", # format() is slow as molasses; % and f'' FTW. ] [tool.ruff.lint.per-file-ignores] "bench/**" = [ "INP001", # Benchmarks don't have to be importable. ] "**/test_*" = [ "B015", # pointless comparison in tests aren't pointless "B017", # pytest.raises(Exception) is fine "B018", # pointless expressions in tests aren't pointless "DTZ", # datetime best practices don't matter in tests "EM", # no need for exception msg hygiene in tests "PLE0309", # hash doesn't have to return anything in tests "PLR0124", # pointless comparison in tests aren't pointless "PT011", # broad is fine "PT012", # sometimes we need more than a single stmt "RUF012", # we don't do ClassVar annotations in tests "S", # security concerns don't matter in tests "SIM201", # sometimes we need to check `not ==` "SIM202", # sometimes we need to check `not ==` "SIM300", # Yoda rocks in asserts "TRY", # exception best practices don't matter in tests ] "src/*/*.pyi" = ["ALL"] # TODO "tests/test_annotations.py" = ["FA100"] "tests/typing_example.py" = [ "E741", # ambiguous variable names don't matter in type checks "B018", # useless expressions aren't useless in type checks "B015", # pointless comparison in type checks aren't pointless "UP037", # we test some older syntaxes on purpose ] [tool.ruff.lint.isort] lines-between-types = 1 lines-after-imports = 2 [tool.towncrier] name = "attrs" directory = "changelog.d" filename = "CHANGELOG.md" start_string = "\n" template = "changelog.d/towncrier_template.md.jinja" title_format = "" issue_format = "[#{issue}](https://github.com/python-attrs/attrs/issues/{issue})" underlines = ["", "", ""] [[tool.towncrier.section]] path = "" [[tool.towncrier.type]] directory = "breaking" name = "Backwards-incompatible Changes" showcontent = true [[tool.towncrier.type]] directory = "deprecation" name = "Deprecations" showcontent = true [[tool.towncrier.type]] directory = "change" name = "Changes" showcontent = true [tool.mypy] pretty = true disallow_untyped_defs = true check_untyped_defs = true python-attrs-attrs-bd2446d/src/000077500000000000000000000000001476453530700165415ustar00rootroot00000000000000python-attrs-attrs-bd2446d/src/attr/000077500000000000000000000000001476453530700175135ustar00rootroot00000000000000python-attrs-attrs-bd2446d/src/attr/__init__.py000066400000000000000000000040111476453530700216200ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Classes Without Boilerplate """ from functools import partial from typing import Callable, Literal, Protocol from . import converters, exceptions, filters, setters, validators from ._cmp import cmp_using from ._config import get_run_validators, set_run_validators from ._funcs import asdict, assoc, astuple, has, resolve_types from ._make import ( NOTHING, Attribute, Converter, Factory, _Nothing, attrib, attrs, evolve, fields, fields_dict, make_class, validate, ) from ._next_gen import define, field, frozen, mutable from ._version_info import VersionInfo s = attributes = attrs ib = attr = attrib dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) class AttrsInstance(Protocol): pass NothingType = Literal[_Nothing.NOTHING] __all__ = [ "NOTHING", "Attribute", "AttrsInstance", "Converter", "Factory", "NothingType", "asdict", "assoc", "astuple", "attr", "attrib", "attributes", "attrs", "cmp_using", "converters", "define", "evolve", "exceptions", "field", "fields", "fields_dict", "filters", "frozen", "get_run_validators", "has", "ib", "make_class", "mutable", "resolve_types", "s", "set_run_validators", "setters", "validate", "validators", ] def _make_getattr(mod_name: str) -> Callable: """ Create a metadata proxy for packaging information that uses *mod_name* in its warnings and errors. """ def __getattr__(name: str) -> str: if name not in ("__version__", "__version_info__"): msg = f"module {mod_name} has no attribute {name}" raise AttributeError(msg) from importlib.metadata import metadata meta = metadata("attrs") if name == "__version_info__": return VersionInfo._from_version_string(meta["version"]) return meta["version"] return __getattr__ __getattr__ = _make_getattr(__name__) python-attrs-attrs-bd2446d/src/attr/__init__.pyi000066400000000000000000000260211476453530700217760ustar00rootroot00000000000000import enum import sys from typing import ( Any, Callable, Generic, Literal, Mapping, Protocol, Sequence, TypeVar, overload, ) # `import X as X` is required to make these public from . import converters as converters from . import exceptions as exceptions from . import filters as filters from . import setters as setters from . import validators as validators from ._cmp import cmp_using as cmp_using from ._typing_compat import AttrsInstance_ from ._version_info import VersionInfo from attrs import ( define as define, field as field, mutable as mutable, frozen as frozen, _EqOrderType, _ValidatorType, _ConverterType, _ReprArgType, _OnSetAttrType, _OnSetAttrArgType, _FieldTransformer, _ValidatorArgType, ) if sys.version_info >= (3, 10): from typing import TypeGuard, TypeAlias else: from typing_extensions import TypeGuard, TypeAlias if sys.version_info >= (3, 11): from typing import dataclass_transform else: from typing_extensions import dataclass_transform __version__: str __version_info__: VersionInfo __title__: str __description__: str __url__: str __uri__: str __author__: str __email__: str __license__: str __copyright__: str _T = TypeVar("_T") _C = TypeVar("_C", bound=type) _FilterType = Callable[["Attribute[_T]", _T], bool] # We subclass this here to keep the protocol's qualified name clean. class AttrsInstance(AttrsInstance_, Protocol): pass _A = TypeVar("_A", bound=type[AttrsInstance]) class _Nothing(enum.Enum): NOTHING = enum.auto() NOTHING = _Nothing.NOTHING NothingType: TypeAlias = Literal[_Nothing.NOTHING] # NOTE: Factory lies about its return type to make this possible: # `x: List[int] # = Factory(list)` # Work around mypy issue #4554 in the common case by using an overload. @overload def Factory(factory: Callable[[], _T]) -> _T: ... @overload def Factory( factory: Callable[[Any], _T], takes_self: Literal[True], ) -> _T: ... @overload def Factory( factory: Callable[[], _T], takes_self: Literal[False], ) -> _T: ... In = TypeVar("In") Out = TypeVar("Out") class Converter(Generic[In, Out]): @overload def __init__(self, converter: Callable[[In], Out]) -> None: ... @overload def __init__( self, converter: Callable[[In, AttrsInstance, Attribute], Out], *, takes_self: Literal[True], takes_field: Literal[True], ) -> None: ... @overload def __init__( self, converter: Callable[[In, Attribute], Out], *, takes_field: Literal[True], ) -> None: ... @overload def __init__( self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True], ) -> None: ... class Attribute(Generic[_T]): name: str default: _T | None validator: _ValidatorType[_T] | None repr: _ReprArgType cmp: _EqOrderType eq: _EqOrderType order: _EqOrderType hash: bool | None init: bool converter: Converter | None metadata: dict[Any, Any] type: type[_T] | None kw_only: bool on_setattr: _OnSetAttrType alias: str | None def evolve(self, **changes: Any) -> "Attribute[Any]": ... # NOTE: We had several choices for the annotation to use for type arg: # 1) Type[_T] # - Pros: Handles simple cases correctly # - Cons: Might produce less informative errors in the case of conflicting # TypeVars e.g. `attr.ib(default='bad', type=int)` # 2) Callable[..., _T] # - Pros: Better error messages than #1 for conflicting TypeVars # - Cons: Terrible error messages for validator checks. # e.g. attr.ib(type=int, validator=validate_str) # -> error: Cannot infer function type argument # 3) type (and do all of the work in the mypy plugin) # - Pros: Simple here, and we could customize the plugin with our own errors. # - Cons: Would need to write mypy plugin code to handle all the cases. # We chose option #1. # `attr` lies about its return type to make the following possible: # attr() -> Any # attr(8) -> int # attr(validator=) -> Whatever the callable expects. # This makes this type of assignments possible: # x: int = attr(8) # # This form catches explicit None or no default but with no other arguments # returns Any. @overload def attrib( default: None = ..., validator: None = ..., repr: _ReprArgType = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., type: None = ..., converter: None = ..., factory: None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the # other arguments. @overload def attrib( default: None = ..., validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., type: type[_T] | None = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., ) -> _T: ... # This form catches an explicit default argument. @overload def attrib( default: _T, validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., type: type[_T] | None = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @overload def attrib( default: _T | None = ..., validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., type: object = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., ) -> Any: ... @overload @dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: _C, these: dict[str, Any] | None = ..., repr_ns: str | None = ..., repr: bool = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., auto_detect: bool = ..., collect_by_mro: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., unsafe_hash: bool | None = ..., ) -> _C: ... @overload @dataclass_transform(order_default=True, field_specifiers=(attrib, field)) def attrs( maybe_cls: None = ..., these: dict[str, Any] | None = ..., repr_ns: str | None = ..., repr: bool = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., auto_detect: bool = ..., collect_by_mro: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., unsafe_hash: bool | None = ..., ) -> Callable[[_C], _C]: ... def fields(cls: type[AttrsInstance]) -> Any: ... def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ... def validate(inst: AttrsInstance) -> None: ... def resolve_types( cls: _A, globalns: dict[str, Any] | None = ..., localns: dict[str, Any] | None = ..., attribs: list[Attribute[Any]] | None = ..., include_extras: bool = ..., ) -> _A: ... # TODO: add support for returning a proper attrs class from the mypy plugin # we use Any instead of _CountingAttr so that e.g. `make_class('Foo', # [attr.ib()])` is valid def make_class( name: str, attrs: list[str] | tuple[str, ...] | dict[str, Any], bases: tuple[type, ...] = ..., class_body: dict[str, Any] | None = ..., repr_ns: str | None = ..., repr: bool = ..., cmp: _EqOrderType | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., collect_by_mro: bool = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., ) -> type: ... # _funcs -- # TODO: add support for returning TypedDict from the mypy plugin # FIXME: asdict/astuple do not honor their factory args. Waiting on one of # these: # https://github.com/python/mypy/issues/4236 # https://github.com/python/typing/issues/253 # XXX: remember to fix attrs.asdict/astuple too! def asdict( inst: AttrsInstance, recurse: bool = ..., filter: _FilterType[Any] | None = ..., dict_factory: type[Mapping[Any, Any]] = ..., retain_collection_types: bool = ..., value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ..., tuple_keys: bool | None = ..., ) -> dict[str, Any]: ... # TODO: add support for returning NamedTuple from the mypy plugin def astuple( inst: AttrsInstance, recurse: bool = ..., filter: _FilterType[Any] | None = ..., tuple_factory: type[Sequence[Any]] = ..., retain_collection_types: bool = ..., ) -> tuple[Any, ...]: ... def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ... def assoc(inst: _T, **changes: Any) -> _T: ... def evolve(inst: _T, **changes: Any) -> _T: ... # _config -- def set_run_validators(run: bool) -> None: ... def get_run_validators() -> bool: ... # aliases -- s = attributes = attrs ib = attr = attrib dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) python-attrs-attrs-bd2446d/src/attr/_cmp.py000066400000000000000000000100251476453530700210010ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import functools import types from ._make import __ne__ _operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} def cmp_using( eq=None, lt=None, le=None, gt=None, ge=None, require_same_type=True, class_name="Comparable", ): """ Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, and ``cmp`` arguments to customize field comparison. The resulting class will have a full set of ordering methods if at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. Args: eq (typing.Callable | None): Callable used to evaluate equality of two objects. lt (typing.Callable | None): Callable used to evaluate whether one object is less than another object. le (typing.Callable | None): Callable used to evaluate whether one object is less than or equal to another object. gt (typing.Callable | None): Callable used to evaluate whether one object is greater than another object. ge (typing.Callable | None): Callable used to evaluate whether one object is greater than or equal to another object. require_same_type (bool): When `True`, equality and ordering methods will return `NotImplemented` if objects are not of the same type. class_name (str | None): Name of class. Defaults to "Comparable". See `comparison` for more details. .. versionadded:: 21.1.0 """ body = { "__slots__": ["value"], "__init__": _make_init(), "_requirements": [], "_is_comparable_to": _is_comparable_to, } # Add operations. num_order_functions = 0 has_eq_function = False if eq is not None: has_eq_function = True body["__eq__"] = _make_operator("eq", eq) body["__ne__"] = __ne__ if lt is not None: num_order_functions += 1 body["__lt__"] = _make_operator("lt", lt) if le is not None: num_order_functions += 1 body["__le__"] = _make_operator("le", le) if gt is not None: num_order_functions += 1 body["__gt__"] = _make_operator("gt", gt) if ge is not None: num_order_functions += 1 body["__ge__"] = _make_operator("ge", ge) type_ = types.new_class( class_name, (object,), {}, lambda ns: ns.update(body) ) # Add same type requirement. if require_same_type: type_._requirements.append(_check_same_type) # Add total ordering if at least one operation was defined. if 0 < num_order_functions < 4: if not has_eq_function: # functools.total_ordering requires __eq__ to be defined, # so raise early error here to keep a nice stack. msg = "eq must be define is order to complete ordering from lt, le, gt, ge." raise ValueError(msg) type_ = functools.total_ordering(type_) return type_ def _make_init(): """ Create __init__ method. """ def __init__(self, value): """ Initialize object with *value*. """ self.value = value return __init__ def _make_operator(name, func): """ Create operator method. """ def method(self, other): if not self._is_comparable_to(other): return NotImplemented result = func(self.value, other.value) if result is NotImplemented: return NotImplemented return result method.__name__ = f"__{name}__" method.__doc__ = ( f"Return a {_operation_names[name]} b. Computed by attrs." ) return method def _is_comparable_to(self, other): """ Check whether `other` is comparable to `self`. """ return all(func(self, other) for func in self._requirements) def _check_same_type(self, other): """ Return True if *self* and *other* are of the same type, False otherwise. """ return other.value.__class__ is self.value.__class__ python-attrs-attrs-bd2446d/src/attr/_cmp.pyi000066400000000000000000000005601476453530700211550ustar00rootroot00000000000000from typing import Any, Callable _CompareWithType = Callable[[Any, Any], bool] def cmp_using( eq: _CompareWithType | None = ..., lt: _CompareWithType | None = ..., le: _CompareWithType | None = ..., gt: _CompareWithType | None = ..., ge: _CompareWithType | None = ..., require_same_type: bool = ..., class_name: str = ..., ) -> type: ... python-attrs-attrs-bd2446d/src/attr/_compat.py000066400000000000000000000052201476453530700215060ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import inspect import platform import sys import threading from collections.abc import Mapping, Sequence # noqa: F401 from typing import _GenericAlias PYPY = platform.python_implementation() == "PyPy" PY_3_9_PLUS = sys.version_info[:2] >= (3, 9) PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) PY_3_11_PLUS = sys.version_info[:2] >= (3, 11) PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) PY_3_14_PLUS = sys.version_info[:2] >= (3, 14) if PY_3_14_PLUS: # pragma: no cover import annotationlib _get_annotations = annotationlib.get_annotations else: def _get_annotations(cls): """ Get annotations for *cls*. """ return cls.__dict__.get("__annotations__", {}) class _AnnotationExtractor: """ Extract type annotations from a callable, returning None whenever there is none. """ __slots__ = ["sig"] def __init__(self, callable): try: self.sig = inspect.signature(callable) except (ValueError, TypeError): # inspect failed self.sig = None def get_first_param_type(self): """ Return the type annotation of the first argument if it's not empty. """ if not self.sig: return None params = list(self.sig.parameters.values()) if params and params[0].annotation is not inspect.Parameter.empty: return params[0].annotation return None def get_return_type(self): """ Return the return type if it's not empty. """ if ( self.sig and self.sig.return_annotation is not inspect.Signature.empty ): return self.sig.return_annotation return None # Thread-local global to track attrs instances which are already being repr'd. # This is needed because there is no other (thread-safe) way to pass info # about the instances that are already being repr'd through the call stack # in order to ensure we don't perform infinite recursion. # # For instance, if an instance contains a dict which contains that instance, # we need to know that we're already repr'ing the outside instance from within # the dict's repr() call. # # This lives here rather than in _make.py so that the functions in _make.py # don't have a direct reference to the thread-local in their globals dict. # If they have such a reference, it breaks cloudpickle. repr_context = threading.local() def get_generic_base(cl): """If this is a generic class (A[str]), return the generic base for it.""" if cl.__class__ is _GenericAlias: return cl.__origin__ return None python-attrs-attrs-bd2446d/src/attr/_config.py000066400000000000000000000015131476453530700214710ustar00rootroot00000000000000# SPDX-License-Identifier: MIT __all__ = ["get_run_validators", "set_run_validators"] _run_validators = True def set_run_validators(run): """ Set whether or not validators are run. By default, they are run. .. deprecated:: 21.3.0 It will not be removed, but it also will not be moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` instead. """ if not isinstance(run, bool): msg = "'run' must be bool." raise TypeError(msg) global _run_validators _run_validators = run def get_run_validators(): """ Return whether or not validators are run. .. deprecated:: 21.3.0 It will not be removed, but it also will not be moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` instead. """ return _run_validators python-attrs-attrs-bd2446d/src/attr/_funcs.py000066400000000000000000000367561476453530700213630ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import copy from ._compat import PY_3_9_PLUS, get_generic_base from ._make import _OBJ_SETATTR, NOTHING, fields from .exceptions import AttrsAttributeNotFoundError def asdict( inst, recurse=True, filter=None, dict_factory=dict, retain_collection_types=False, value_serializer=None, ): """ Return the *attrs* attribute values of *inst* as a dict. Optionally recurse into other *attrs*-decorated classes. Args: inst: Instance of an *attrs*-decorated class. recurse (bool): Recurse into classes that are also *attrs*-decorated. filter (~typing.Callable): A callable whose return code determines whether an attribute or element is included (`True`) or dropped (`False`). Is called with the `attrs.Attribute` as the first argument and the value as the second argument. dict_factory (~typing.Callable): A callable to produce dictionaries from. For example, to produce ordered dictionaries instead of normal Python dictionaries, pass in ``collections.OrderedDict``. retain_collection_types (bool): Do not convert to `list` when encountering an attribute whose type is `tuple` or `set`. Only meaningful if *recurse* is `True`. value_serializer (typing.Callable | None): A hook that is called for every attribute or dict key/value. It receives the current instance, field and value and must return the (updated) value. The hook is run *after* the optional *filter* has been applied. Returns: Return type of *dict_factory*. Raises: attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. versionadded:: 16.0.0 *dict_factory* .. versionadded:: 16.1.0 *retain_collection_types* .. versionadded:: 20.3.0 *value_serializer* .. versionadded:: 21.3.0 If a dict has a collection for a key, it is serialized as a tuple. """ attrs = fields(inst.__class__) rv = dict_factory() for a in attrs: v = getattr(inst, a.name) if filter is not None and not filter(a, v): continue if value_serializer is not None: v = value_serializer(inst, a, v) if recurse is True: if has(v.__class__): rv[a.name] = asdict( v, recurse=True, filter=filter, dict_factory=dict_factory, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ) elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain_collection_types is True else list items = [ _asdict_anything( i, is_key=False, filter=filter, dict_factory=dict_factory, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ) for i in v ] try: rv[a.name] = cf(items) except TypeError: if not issubclass(cf, tuple): raise # Workaround for TypeError: cf.__new__() missing 1 required # positional argument (which appears, for a namedturle) rv[a.name] = cf(*items) elif isinstance(v, dict): df = dict_factory rv[a.name] = df( ( _asdict_anything( kk, is_key=True, filter=filter, dict_factory=df, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ), _asdict_anything( vv, is_key=False, filter=filter, dict_factory=df, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ), ) for kk, vv in v.items() ) else: rv[a.name] = v else: rv[a.name] = v return rv def _asdict_anything( val, is_key, filter, dict_factory, retain_collection_types, value_serializer, ): """ ``asdict`` only works on attrs instances, this works on anything. """ if getattr(val.__class__, "__attrs_attrs__", None) is not None: # Attrs class. rv = asdict( val, recurse=True, filter=filter, dict_factory=dict_factory, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ) elif isinstance(val, (tuple, list, set, frozenset)): if retain_collection_types is True: cf = val.__class__ elif is_key: cf = tuple else: cf = list rv = cf( [ _asdict_anything( i, is_key=False, filter=filter, dict_factory=dict_factory, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ) for i in val ] ) elif isinstance(val, dict): df = dict_factory rv = df( ( _asdict_anything( kk, is_key=True, filter=filter, dict_factory=df, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ), _asdict_anything( vv, is_key=False, filter=filter, dict_factory=df, retain_collection_types=retain_collection_types, value_serializer=value_serializer, ), ) for kk, vv in val.items() ) else: rv = val if value_serializer is not None: rv = value_serializer(None, None, rv) return rv def astuple( inst, recurse=True, filter=None, tuple_factory=tuple, retain_collection_types=False, ): """ Return the *attrs* attribute values of *inst* as a tuple. Optionally recurse into other *attrs*-decorated classes. Args: inst: Instance of an *attrs*-decorated class. recurse (bool): Recurse into classes that are also *attrs*-decorated. filter (~typing.Callable): A callable whose return code determines whether an attribute or element is included (`True`) or dropped (`False`). Is called with the `attrs.Attribute` as the first argument and the value as the second argument. tuple_factory (~typing.Callable): A callable to produce tuples from. For example, to produce lists instead of tuples. retain_collection_types (bool): Do not convert to `list` or `dict` when encountering an attribute which type is `tuple`, `dict` or `set`. Only meaningful if *recurse* is `True`. Returns: Return type of *tuple_factory* Raises: attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. versionadded:: 16.2.0 """ attrs = fields(inst.__class__) rv = [] retain = retain_collection_types # Very long. :/ for a in attrs: v = getattr(inst, a.name) if filter is not None and not filter(a, v): continue if recurse is True: if has(v.__class__): rv.append( astuple( v, recurse=True, filter=filter, tuple_factory=tuple_factory, retain_collection_types=retain, ) ) elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain is True else list items = [ ( astuple( j, recurse=True, filter=filter, tuple_factory=tuple_factory, retain_collection_types=retain, ) if has(j.__class__) else j ) for j in v ] try: rv.append(cf(items)) except TypeError: if not issubclass(cf, tuple): raise # Workaround for TypeError: cf.__new__() missing 1 required # positional argument (which appears, for a namedturle) rv.append(cf(*items)) elif isinstance(v, dict): df = v.__class__ if retain is True else dict rv.append( df( ( ( astuple( kk, tuple_factory=tuple_factory, retain_collection_types=retain, ) if has(kk.__class__) else kk ), ( astuple( vv, tuple_factory=tuple_factory, retain_collection_types=retain, ) if has(vv.__class__) else vv ), ) for kk, vv in v.items() ) ) else: rv.append(v) else: rv.append(v) return rv if tuple_factory is list else tuple_factory(rv) def has(cls): """ Check whether *cls* is a class with *attrs* attributes. Args: cls (type): Class to introspect. Raises: TypeError: If *cls* is not a class. Returns: bool: """ attrs = getattr(cls, "__attrs_attrs__", None) if attrs is not None: return True # No attrs, maybe it's a specialized generic (A[str])? generic_base = get_generic_base(cls) if generic_base is not None: generic_attrs = getattr(generic_base, "__attrs_attrs__", None) if generic_attrs is not None: # Stick it on here for speed next time. cls.__attrs_attrs__ = generic_attrs return generic_attrs is not None return False def assoc(inst, **changes): """ Copy *inst* and apply *changes*. This is different from `evolve` that applies the changes to the arguments that create the new instance. `evolve`'s behavior is preferable, but there are `edge cases`_ where it doesn't work. Therefore `assoc` is deprecated, but will not be removed. .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 Args: inst: Instance of a class with *attrs* attributes. changes: Keyword changes in the new copy. Returns: A copy of inst with *changes* incorporated. Raises: attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't be found on *cls*. attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. deprecated:: 17.1.0 Use `attrs.evolve` instead if you can. This function will not be removed du to the slightly different approach compared to `attrs.evolve`, though. """ new = copy.copy(inst) attrs = fields(inst.__class__) for k, v in changes.items(): a = getattr(attrs, k, NOTHING) if a is NOTHING: msg = f"{k} is not an attrs attribute on {new.__class__}." raise AttrsAttributeNotFoundError(msg) _OBJ_SETATTR(new, k, v) return new def resolve_types( cls, globalns=None, localns=None, attribs=None, include_extras=True ): """ Resolve any strings and forward annotations in type annotations. This is only required if you need concrete types in :class:`Attribute`'s *type* field. In other words, you don't need to resolve your types if you only use them for static type checking. With no arguments, names will be looked up in the module in which the class was created. If this is not what you want, for example, if the name only exists inside a method, you may pass *globalns* or *localns* to specify other dictionaries in which to look up these names. See the docs of `typing.get_type_hints` for more details. Args: cls (type): Class to resolve. globalns (dict | None): Dictionary containing global variables. localns (dict | None): Dictionary containing local variables. attribs (list | None): List of attribs for the given class. This is necessary when calling from inside a ``field_transformer`` since *cls* is not an *attrs* class yet. include_extras (bool): Resolve more accurately, if possible. Pass ``include_extras`` to ``typing.get_hints``, if supported by the typing module. On supported Python versions (3.9+), this resolves the types more accurately. Raises: TypeError: If *cls* is not a class. attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class and you didn't pass any attribs. NameError: If types cannot be resolved because of missing variables. Returns: *cls* so you can use this function also as a class decorator. Please note that you have to apply it **after** `attrs.define`. That means the decorator has to come in the line **before** `attrs.define`. .. versionadded:: 20.1.0 .. versionadded:: 21.1.0 *attribs* .. versionadded:: 23.1.0 *include_extras* """ # Since calling get_type_hints is expensive we cache whether we've # done it already. if getattr(cls, "__attrs_types_resolved__", None) != cls: import typing kwargs = {"globalns": globalns, "localns": localns} if PY_3_9_PLUS: kwargs["include_extras"] = include_extras hints = typing.get_type_hints(cls, **kwargs) for field in fields(cls) if attribs is None else attribs: if field.name in hints: # Since fields have been frozen we must work around it. _OBJ_SETATTR(field, "type", hints[field.name]) # We store the class we resolved so that subclasses know they haven't # been resolved. cls.__attrs_types_resolved__ = cls # Return the class so you can use it as a decorator too. return cls python-attrs-attrs-bd2446d/src/attr/_make.py000066400000000000000000002746301476453530700211550ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from __future__ import annotations import abc import contextlib import copy import enum import inspect import itertools import linecache import sys import types import unicodedata from collections.abc import Callable, Mapping from functools import cached_property from typing import Any, NamedTuple, TypeVar # We need to import _compat itself in addition to the _compat members to avoid # having the thread-local in the globals here. from . import _compat, _config, setters from ._compat import ( PY_3_10_PLUS, PY_3_11_PLUS, PY_3_13_PLUS, _AnnotationExtractor, _get_annotations, get_generic_base, ) from .exceptions import ( DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError, UnannotatedAttributeError, ) # This is used at least twice, so cache it here. _OBJ_SETATTR = object.__setattr__ _INIT_FACTORY_PAT = "__attr_factory_%s" _CLASSVAR_PREFIXES = ( "typing.ClassVar", "t.ClassVar", "ClassVar", "typing_extensions.ClassVar", ) # we don't use a double-underscore prefix because that triggers # name mangling when trying to create a slot for the field # (when slots=True) _HASH_CACHE_FIELD = "_attrs_cached_hash" _EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) # Unique object for unequivocal getattr() defaults. _SENTINEL = object() _DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) class _Nothing(enum.Enum): """ Sentinel to indicate the lack of a value when `None` is ambiguous. If extending attrs, you can use ``typing.Literal[NOTHING]`` to show that a value may be ``NOTHING``. .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. """ NOTHING = enum.auto() def __repr__(self): return "NOTHING" def __bool__(self): return False NOTHING = _Nothing.NOTHING """ Sentinel to indicate the lack of a value when `None` is ambiguous. When using in 3rd party code, use `attrs.NothingType` for type annotations. """ class _CacheHashWrapper(int): """ An integer subclass that pickles / copies as None This is used for non-slots classes with ``cache_hash=True``, to avoid serializing a potentially (even likely) invalid hash value. Since `None` is the default value for uncalculated hashes, whenever this is copied, the copy's value for the hash should automatically reset. See GH #613 for more details. """ def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 return _none_constructor, _args def attrib( default=NOTHING, validator=None, repr=True, cmp=None, hash=None, init=True, metadata=None, type=None, converter=None, factory=None, kw_only=False, eq=None, order=None, on_setattr=None, alias=None, ): """ Create a new field / attribute on a class. Identical to `attrs.field`, except it's not keyword-only. Consider using `attrs.field` in new code (``attr.ib`` will *never* go away, though). .. warning:: Does **nothing** unless the class is also decorated with `attr.s` (or similar)! .. versionadded:: 15.2.0 *convert* .. versionadded:: 16.3.0 *metadata* .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. .. versionchanged:: 17.1.0 *hash* is `None` and therefore mirrors *eq* by default. .. versionadded:: 17.3.0 *type* .. deprecated:: 17.4.0 *convert* .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated *convert* to achieve consistency with other noun-based arguments. .. versionadded:: 18.1.0 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. .. versionadded:: 18.2.0 *kw_only* .. versionchanged:: 19.2.0 *convert* keyword argument removed. .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. .. versionadded:: 19.2.0 *eq* and *order* .. versionadded:: 20.1.0 *on_setattr* .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 .. versionchanged:: 21.1.0 *eq*, *order*, and *cmp* also accept a custom callable .. versionchanged:: 21.1.0 *cmp* undeprecated .. versionadded:: 22.2.0 *alias* """ eq, eq_key, order, order_key = _determine_attrib_eq_order( cmp, eq, order, True ) if hash is not None and hash is not True and hash is not False: msg = "Invalid value for hash. Must be True, False, or None." raise TypeError(msg) if factory is not None: if default is not NOTHING: msg = ( "The `default` and `factory` arguments are mutually exclusive." ) raise ValueError(msg) if not callable(factory): msg = "The `factory` argument must be a callable." raise ValueError(msg) default = Factory(factory) if metadata is None: metadata = {} # Apply syntactic sugar by auto-wrapping. if isinstance(on_setattr, (list, tuple)): on_setattr = setters.pipe(*on_setattr) if validator and isinstance(validator, (list, tuple)): validator = and_(*validator) if converter and isinstance(converter, (list, tuple)): converter = pipe(*converter) return _CountingAttr( default=default, validator=validator, repr=repr, cmp=None, hash=hash, init=init, converter=converter, metadata=metadata, type=type, kw_only=kw_only, eq=eq, eq_key=eq_key, order=order, order_key=order_key, on_setattr=on_setattr, alias=alias, ) def _compile_and_eval( script: str, globs: dict[str, Any] | None, locs: Mapping[str, object] | None = None, filename: str = "", ) -> None: """ Evaluate the script with the given global (globs) and local (locs) variables. """ bytecode = compile(script, filename, "exec") eval(bytecode, globs, locs) def _linecache_and_compile( script: str, filename: str, globs: dict[str, Any] | None, locals: Mapping[str, object] | None = None, ) -> dict[str, Any]: """ Cache the script with _linecache_, compile it and return the _locals_. """ locs = {} if locals is None else locals # In order of debuggers like PDB being able to step through the code, # we add a fake linecache entry. count = 1 base_filename = filename while True: linecache_tuple = ( len(script), None, script.splitlines(True), filename, ) old_val = linecache.cache.setdefault(filename, linecache_tuple) if old_val == linecache_tuple: break filename = f"{base_filename[:-1]}-{count}>" count += 1 _compile_and_eval(script, globs, locs, filename) return locs def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type: """ Create a tuple subclass to hold `Attribute`s for an `attrs` class. The subclass is a bare tuple with properties for names. class MyClassAttributes(tuple): __slots__ = () x = property(itemgetter(0)) """ attr_class_name = f"{cls_name}Attributes" body = {} for i, attr_name in enumerate(attr_names): def getter(self, i=i): return self[i] body[attr_name] = property(getter) return type(attr_class_name, (tuple,), body) # Tuple class for extracted attributes from a class definition. # `base_attrs` is a subset of `attrs`. class _Attributes(NamedTuple): attrs: type base_attrs: list[Attribute] base_attrs_map: dict[str, type] def _is_class_var(annot): """ Check whether *annot* is a typing.ClassVar. The string comparison hack is used to avoid evaluating all string annotations which would put attrs-based classes at a performance disadvantage compared to plain old classes. """ annot = str(annot) # Annotation can be quoted. if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): annot = annot[1:-1] return annot.startswith(_CLASSVAR_PREFIXES) def _has_own_attribute(cls, attrib_name): """ Check whether *cls* defines *attrib_name* (and doesn't just inherit it). """ return attrib_name in cls.__dict__ def _collect_base_attrs( cls, taken_attr_names ) -> tuple[list[Attribute], dict[str, type]]: """ Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. """ base_attrs = [] base_attr_map = {} # A dictionary of base attrs to their classes. # Traverse the MRO and collect attributes. for base_cls in reversed(cls.__mro__[1:-1]): for a in getattr(base_cls, "__attrs_attrs__", []): if a.inherited or a.name in taken_attr_names: continue a = a.evolve(inherited=True) # noqa: PLW2901 base_attrs.append(a) base_attr_map[a.name] = base_cls # For each name, only keep the freshest definition i.e. the furthest at the # back. base_attr_map is fine because it gets overwritten with every new # instance. filtered = [] seen = set() for a in reversed(base_attrs): if a.name in seen: continue filtered.insert(0, a) seen.add(a.name) return filtered, base_attr_map def _collect_base_attrs_broken(cls, taken_attr_names): """ Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. N.B. *taken_attr_names* will be mutated. Adhere to the old incorrect behavior. Notably it collects from the front and considers inherited attributes which leads to the buggy behavior reported in #428. """ base_attrs = [] base_attr_map = {} # A dictionary of base attrs to their classes. # Traverse the MRO and collect attributes. for base_cls in cls.__mro__[1:-1]: for a in getattr(base_cls, "__attrs_attrs__", []): if a.name in taken_attr_names: continue a = a.evolve(inherited=True) # noqa: PLW2901 taken_attr_names.add(a.name) base_attrs.append(a) base_attr_map[a.name] = base_cls return base_attrs, base_attr_map def _transform_attrs( cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer ) -> _Attributes: """ Transform all `_CountingAttr`s on a class into `Attribute`s. If *these* is passed, use that and don't look for them on the class. If *collect_by_mro* is True, collect them in the correct MRO order, otherwise use the old -- incorrect -- order. See #428. Return an `_Attributes`. """ cd = cls.__dict__ anns = _get_annotations(cls) if these is not None: ca_list = list(these.items()) elif auto_attribs is True: ca_names = { name for name, attr in cd.items() if attr.__class__ is _CountingAttr } ca_list = [] annot_names = set() for attr_name, type in anns.items(): if _is_class_var(type): continue annot_names.add(attr_name) a = cd.get(attr_name, NOTHING) if a.__class__ is not _CountingAttr: a = attrib(a) ca_list.append((attr_name, a)) unannotated = ca_names - annot_names if unannotated: raise UnannotatedAttributeError( "The following `attr.ib`s lack a type annotation: " + ", ".join( sorted(unannotated, key=lambda n: cd.get(n).counter) ) + "." ) else: ca_list = sorted( ( (name, attr) for name, attr in cd.items() if attr.__class__ is _CountingAttr ), key=lambda e: e[1].counter, ) fca = Attribute.from_counting_attr own_attrs = [ fca(attr_name, ca, anns.get(attr_name)) for attr_name, ca in ca_list ] if collect_by_mro: base_attrs, base_attr_map = _collect_base_attrs( cls, {a.name for a in own_attrs} ) else: base_attrs, base_attr_map = _collect_base_attrs_broken( cls, {a.name for a in own_attrs} ) if kw_only: own_attrs = [a.evolve(kw_only=True) for a in own_attrs] base_attrs = [a.evolve(kw_only=True) for a in base_attrs] attrs = base_attrs + own_attrs if field_transformer is not None: attrs = tuple(field_transformer(cls, attrs)) # Check attr order after executing the field_transformer. # Mandatory vs non-mandatory attr order only matters when they are part of # the __init__ signature and when they aren't kw_only (which are moved to # the end and can be mandatory or non-mandatory in any order, as they will # be specified as keyword args anyway). Check the order of those attrs: had_default = False for a in (a for a in attrs if a.init is not False and a.kw_only is False): if had_default is True and a.default is NOTHING: msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" raise ValueError(msg) if had_default is False and a.default is not NOTHING: had_default = True # Resolve default field alias after executing field_transformer. # This allows field_transformer to differentiate between explicit vs # default aliases and supply their own defaults. for a in attrs: if not a.alias: # Evolve is very slow, so we hold our nose and do it dirty. _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name)) # Create AttrsClass *after* applying the field_transformer since it may # add or remove attributes! attr_names = [a.name for a in attrs] AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map) def _make_cached_property_getattr(cached_properties, original_getattr, cls): lines = [ # Wrapped to get `__class__` into closure cell for super() # (It will be replaced with the newly constructed class after construction). "def wrapper(_cls):", " __class__ = _cls", " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", " func = cached_properties.get(item)", " if func is not None:", " result = func(self)", " _setter = _cached_setattr_get(self)", " _setter(item, result)", " return result", ] if original_getattr is not None: lines.append( " return original_getattr(self, item)", ) else: lines.extend( [ " try:", " return super().__getattribute__(item)", " except AttributeError:", " if not hasattr(super(), '__getattr__'):", " raise", " return super().__getattr__(item)", " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", " raise AttributeError(original_error)", ] ) lines.extend( [ " return __getattr__", "__getattr__ = wrapper(_cls)", ] ) unique_filename = _generate_unique_filename(cls, "getattr") glob = { "cached_properties": cached_properties, "_cached_setattr_get": _OBJ_SETATTR.__get__, "original_getattr": original_getattr, } return _linecache_and_compile( "\n".join(lines), unique_filename, glob, locals={"_cls": cls} )["__getattr__"] def _frozen_setattrs(self, name, value): """ Attached to frozen classes as __setattr__. """ if isinstance(self, BaseException) and name in ( "__cause__", "__context__", "__traceback__", "__suppress_context__", "__notes__", ): BaseException.__setattr__(self, name, value) return raise FrozenInstanceError def _frozen_delattrs(self, name): """ Attached to frozen classes as __delattr__. """ if isinstance(self, BaseException) and name in ("__notes__",): BaseException.__delattr__(self, name) return raise FrozenInstanceError def evolve(*args, **changes): """ Create a new instance, based on the first positional argument with *changes* applied. .. tip:: On Python 3.13 and later, you can also use `copy.replace` instead. Args: inst: Instance of a class with *attrs* attributes. *inst* must be passed as a positional argument. changes: Keyword changes in the new copy. Returns: A copy of inst with *changes* incorporated. Raises: TypeError: If *attr_name* couldn't be found in the class ``__init__``. attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. .. versionadded:: 17.1.0 .. deprecated:: 23.1.0 It is now deprecated to pass the instance using the keyword argument *inst*. It will raise a warning until at least April 2024, after which it will become an error. Always pass the instance as a positional argument. .. versionchanged:: 24.1.0 *inst* can't be passed as a keyword argument anymore. """ try: (inst,) = args except ValueError: msg = ( f"evolve() takes 1 positional argument, but {len(args)} were given" ) raise TypeError(msg) from None cls = inst.__class__ attrs = fields(cls) for a in attrs: if not a.init: continue attr_name = a.name # To deal with private attributes. init_name = a.alias if init_name not in changes: changes[init_name] = getattr(inst, attr_name) return cls(**changes) class _ClassBuilder: """ Iteratively build *one* class. """ __slots__ = ( "_add_method_dunders", "_attr_names", "_attrs", "_base_attr_map", "_base_names", "_cache_hash", "_cls", "_cls_dict", "_delete_attribs", "_frozen", "_has_custom_setattr", "_has_post_init", "_has_pre_init", "_is_exc", "_on_setattr", "_pre_init_has_args", "_repr_added", "_script_snippets", "_slots", "_weakref_slot", "_wrote_own_setattr", ) def __init__( self, cls: type, these, slots, frozen, weakref_slot, getstate_setstate, auto_attribs, kw_only, cache_hash, is_exc, collect_by_mro, on_setattr, has_custom_setattr, field_transformer, ): attrs, base_attrs, base_map = _transform_attrs( cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer, ) self._cls = cls self._cls_dict = dict(cls.__dict__) if slots else {} self._attrs = attrs self._base_names = {a.name for a in base_attrs} self._base_attr_map = base_map self._attr_names = tuple(a.name for a in attrs) self._slots = slots self._frozen = frozen self._weakref_slot = weakref_slot self._cache_hash = cache_hash self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) self._pre_init_has_args = False if self._has_pre_init: # Check if the pre init method has more arguments than just `self` # We want to pass arguments if pre init expects arguments pre_init_func = cls.__attrs_pre_init__ pre_init_signature = inspect.signature(pre_init_func) self._pre_init_has_args = len(pre_init_signature.parameters) > 1 self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) self._delete_attribs = not bool(these) self._is_exc = is_exc self._on_setattr = on_setattr self._has_custom_setattr = has_custom_setattr self._wrote_own_setattr = False self._cls_dict["__attrs_attrs__"] = self._attrs if frozen: self._cls_dict["__setattr__"] = _frozen_setattrs self._cls_dict["__delattr__"] = _frozen_delattrs self._wrote_own_setattr = True elif on_setattr in ( _DEFAULT_ON_SETATTR, setters.validate, setters.convert, ): has_validator = has_converter = False for a in attrs: if a.validator is not None: has_validator = True if a.converter is not None: has_converter = True if has_validator and has_converter: break if ( ( on_setattr == _DEFAULT_ON_SETATTR and not (has_validator or has_converter) ) or (on_setattr == setters.validate and not has_validator) or (on_setattr == setters.convert and not has_converter) ): # If class-level on_setattr is set to convert + validate, but # there's no field to convert or validate, pretend like there's # no on_setattr. self._on_setattr = None if getstate_setstate: ( self._cls_dict["__getstate__"], self._cls_dict["__setstate__"], ) = self._make_getstate_setstate() # tuples of script, globs, hook self._script_snippets: list[ tuple[str, dict, Callable[[dict, dict], Any]] ] = [] self._repr_added = False # We want to only do this check once; in 99.9% of cases these # exist. if not hasattr(self._cls, "__module__") or not hasattr( self._cls, "__qualname__" ): self._add_method_dunders = self._add_method_dunders_safe else: self._add_method_dunders = self._add_method_dunders_unsafe def __repr__(self): return f"<_ClassBuilder(cls={self._cls.__name__})>" def _eval_snippets(self) -> None: """ Evaluate any registered snippets in one go. """ script = "\n".join([snippet[0] for snippet in self._script_snippets]) globs = {} for _, snippet_globs, _ in self._script_snippets: globs.update(snippet_globs) locs = _linecache_and_compile( script, _generate_unique_filename(self._cls, "methods"), globs, ) for _, _, hook in self._script_snippets: hook(self._cls_dict, locs) def build_class(self): """ Finalize class based on the accumulated configuration. Builder cannot be used after calling this method. """ self._eval_snippets() if self._slots is True: cls = self._create_slots_class() else: cls = self._patch_original_class() if PY_3_10_PLUS: cls = abc.update_abstractmethods(cls) # The method gets only called if it's not inherited from a base class. # _has_own_attribute does NOT work properly for classmethods. if ( getattr(cls, "__attrs_init_subclass__", None) and "__attrs_init_subclass__" not in cls.__dict__ ): cls.__attrs_init_subclass__() return cls def _patch_original_class(self): """ Apply accumulated methods and return the class. """ cls = self._cls base_names = self._base_names # Clean class of attribute definitions (`attr.ib()`s). if self._delete_attribs: for name in self._attr_names: if ( name not in base_names and getattr(cls, name, _SENTINEL) is not _SENTINEL ): # An AttributeError can happen if a base class defines a # class variable and we want to set an attribute with the # same name by using only a type annotation. with contextlib.suppress(AttributeError): delattr(cls, name) # Attach our dunder methods. for name, value in self._cls_dict.items(): setattr(cls, name, value) # If we've inherited an attrs __setattr__ and don't write our own, # reset it to object's. if not self._wrote_own_setattr and getattr( cls, "__attrs_own_setattr__", False ): cls.__attrs_own_setattr__ = False if not self._has_custom_setattr: cls.__setattr__ = _OBJ_SETATTR return cls def _create_slots_class(self): """ Build and return a new class with a `__slots__` attribute. """ cd = { k: v for k, v in self._cls_dict.items() if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") } # If our class doesn't have its own implementation of __setattr__ # (either from the user or by us), check the bases, if one of them has # an attrs-made __setattr__, that needs to be reset. We don't walk the # MRO because we only care about our immediate base classes. # XXX: This can be confused by subclassing a slotted attrs class with # XXX: a non-attrs class and subclass the resulting class with an attrs # XXX: class. See `test_slotted_confused` for details. For now that's # XXX: OK with us. if not self._wrote_own_setattr: cd["__attrs_own_setattr__"] = False if not self._has_custom_setattr: for base_cls in self._cls.__bases__: if base_cls.__dict__.get("__attrs_own_setattr__", False): cd["__setattr__"] = _OBJ_SETATTR break # Traverse the MRO to collect existing slots # and check for an existing __weakref__. existing_slots = {} weakref_inherited = False for base_cls in self._cls.__mro__[1:-1]: if base_cls.__dict__.get("__weakref__", None) is not None: weakref_inherited = True existing_slots.update( { name: getattr(base_cls, name) for name in getattr(base_cls, "__slots__", []) } ) base_names = set(self._base_names) names = self._attr_names if ( self._weakref_slot and "__weakref__" not in getattr(self._cls, "__slots__", ()) and "__weakref__" not in names and not weakref_inherited ): names += ("__weakref__",) cached_properties = { name: cached_prop.func for name, cached_prop in cd.items() if isinstance(cached_prop, cached_property) } # Collect methods with a `__class__` reference that are shadowed in the new class. # To know to update them. additional_closure_functions_to_update = [] if cached_properties: class_annotations = _get_annotations(self._cls) for name, func in cached_properties.items(): # Add cached properties to names for slotting. names += (name,) # Clear out function from class to avoid clashing. del cd[name] additional_closure_functions_to_update.append(func) annotation = inspect.signature(func).return_annotation if annotation is not inspect.Parameter.empty: class_annotations[name] = annotation original_getattr = cd.get("__getattr__") if original_getattr is not None: additional_closure_functions_to_update.append(original_getattr) cd["__getattr__"] = _make_cached_property_getattr( cached_properties, original_getattr, self._cls ) # We only add the names of attributes that aren't inherited. # Setting __slots__ to inherited attributes wastes memory. slot_names = [name for name in names if name not in base_names] # There are slots for attributes from current class # that are defined in parent classes. # As their descriptors may be overridden by a child class, # we collect them here and update the class dict reused_slots = { slot: slot_descriptor for slot, slot_descriptor in existing_slots.items() if slot in slot_names } slot_names = [name for name in slot_names if name not in reused_slots] cd.update(reused_slots) if self._cache_hash: slot_names.append(_HASH_CACHE_FIELD) cd["__slots__"] = tuple(slot_names) cd["__qualname__"] = self._cls.__qualname__ # Create new class based on old class and our methods. cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) # The following is a fix for # . # If a method mentions `__class__` or uses the no-arg super(), the # compiler will bake a reference to the class in the method itself # as `method.__closure__`. Since we replace the class with a # clone, we rewrite these references so it keeps working. for item in itertools.chain( cls.__dict__.values(), additional_closure_functions_to_update ): if isinstance(item, (classmethod, staticmethod)): # Class- and staticmethods hide their functions inside. # These might need to be rewritten as well. closure_cells = getattr(item.__func__, "__closure__", None) elif isinstance(item, property): # Workaround for property `super()` shortcut (PY3-only). # There is no universal way for other descriptors. closure_cells = getattr(item.fget, "__closure__", None) else: closure_cells = getattr(item, "__closure__", None) if not closure_cells: # Catch None or the empty list. continue for cell in closure_cells: try: match = cell.cell_contents is self._cls except ValueError: # noqa: PERF203 # ValueError: Cell is empty pass else: if match: cell.cell_contents = cls return cls def add_repr(self, ns): script, globs = _make_repr_script(self._attrs, ns) def _attach_repr(cls_dict, globs): cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"]) self._script_snippets.append((script, globs, _attach_repr)) self._repr_added = True return self def add_str(self): if not self._repr_added: msg = "__str__ can only be generated if a __repr__ exists." raise ValueError(msg) def __str__(self): return self.__repr__() self._cls_dict["__str__"] = self._add_method_dunders(__str__) return self def _make_getstate_setstate(self): """ Create custom __setstate__ and __getstate__ methods. """ # __weakref__ is not writable. state_attr_names = tuple( an for an in self._attr_names if an != "__weakref__" ) def slots_getstate(self): """ Automatically created by attrs. """ return {name: getattr(self, name) for name in state_attr_names} hash_caching_enabled = self._cache_hash def slots_setstate(self, state): """ Automatically created by attrs. """ __bound_setattr = _OBJ_SETATTR.__get__(self) if isinstance(state, tuple): # Backward compatibility with attrs instances pickled with # attrs versions before v22.2.0 which stored tuples. for name, value in zip(state_attr_names, state): __bound_setattr(name, value) else: for name in state_attr_names: if name in state: __bound_setattr(name, state[name]) # The hash code cache is not included when the object is # serialized, but it still needs to be initialized to None to # indicate that the first call to __hash__ should be a cache # miss. if hash_caching_enabled: __bound_setattr(_HASH_CACHE_FIELD, None) return slots_getstate, slots_setstate def make_unhashable(self): self._cls_dict["__hash__"] = None return self def add_hash(self): script, globs = _make_hash_script( self._cls, self._attrs, frozen=self._frozen, cache_hash=self._cache_hash, ) def attach_hash(cls_dict: dict, locs: dict) -> None: cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"]) self._script_snippets.append((script, globs, attach_hash)) return self def add_init(self): script, globs, annotations = _make_init_script( self._cls, self._attrs, self._has_pre_init, self._pre_init_has_args, self._has_post_init, self._frozen, self._slots, self._cache_hash, self._base_attr_map, self._is_exc, self._on_setattr, attrs_init=False, ) def _attach_init(cls_dict, globs): init = globs["__init__"] init.__annotations__ = annotations cls_dict["__init__"] = self._add_method_dunders(init) self._script_snippets.append((script, globs, _attach_init)) return self def add_replace(self): self._cls_dict["__replace__"] = self._add_method_dunders( lambda self, **changes: evolve(self, **changes) ) return self def add_match_args(self): self._cls_dict["__match_args__"] = tuple( field.name for field in self._attrs if field.init and not field.kw_only ) def add_attrs_init(self): script, globs, annotations = _make_init_script( self._cls, self._attrs, self._has_pre_init, self._pre_init_has_args, self._has_post_init, self._frozen, self._slots, self._cache_hash, self._base_attr_map, self._is_exc, self._on_setattr, attrs_init=True, ) def _attach_attrs_init(cls_dict, globs): init = globs["__attrs_init__"] init.__annotations__ = annotations cls_dict["__attrs_init__"] = self._add_method_dunders(init) self._script_snippets.append((script, globs, _attach_attrs_init)) return self def add_eq(self): cd = self._cls_dict script, globs = _make_eq_script(self._attrs) def _attach_eq(cls_dict, globs): cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"]) self._script_snippets.append((script, globs, _attach_eq)) cd["__ne__"] = __ne__ return self def add_order(self): cd = self._cls_dict cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( self._add_method_dunders(meth) for meth in _make_order(self._cls, self._attrs) ) return self def add_setattr(self): sa_attrs = {} for a in self._attrs: on_setattr = a.on_setattr or self._on_setattr if on_setattr and on_setattr is not setters.NO_OP: sa_attrs[a.name] = a, on_setattr if not sa_attrs: return self if self._has_custom_setattr: # We need to write a __setattr__ but there already is one! msg = "Can't combine custom __setattr__ with on_setattr hooks." raise ValueError(msg) # docstring comes from _add_method_dunders def __setattr__(self, name, val): try: a, hook = sa_attrs[name] except KeyError: nval = val else: nval = hook(self, a, val) _OBJ_SETATTR(self, name, nval) self._cls_dict["__attrs_own_setattr__"] = True self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) self._wrote_own_setattr = True return self def _add_method_dunders_unsafe(self, method: Callable) -> Callable: """ Add __module__ and __qualname__ to a *method*. """ method.__module__ = self._cls.__module__ method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" method.__doc__ = ( f"Method generated by attrs for class {self._cls.__qualname__}." ) return method def _add_method_dunders_safe(self, method: Callable) -> Callable: """ Add __module__ and __qualname__ to a *method* if possible. """ with contextlib.suppress(AttributeError): method.__module__ = self._cls.__module__ with contextlib.suppress(AttributeError): method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" with contextlib.suppress(AttributeError): method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}." return method def _determine_attrs_eq_order(cmp, eq, order, default_eq): """ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective values of eq and order. If *eq* is None, set it to *default_eq*. """ if cmp is not None and any((eq is not None, order is not None)): msg = "Don't mix `cmp` with `eq' and `order`." raise ValueError(msg) # cmp takes precedence due to bw-compatibility. if cmp is not None: return cmp, cmp # If left None, equality is set to the specified default and ordering # mirrors equality. if eq is None: eq = default_eq if order is None: order = eq if eq is False and order is True: msg = "`order` can only be True if `eq` is True too." raise ValueError(msg) return eq, order def _determine_attrib_eq_order(cmp, eq, order, default_eq): """ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective values of eq and order. If *eq* is None, set it to *default_eq*. """ if cmp is not None and any((eq is not None, order is not None)): msg = "Don't mix `cmp` with `eq' and `order`." raise ValueError(msg) def decide_callable_or_boolean(value): """ Decide whether a key function is used. """ if callable(value): value, key = True, value else: key = None return value, key # cmp takes precedence due to bw-compatibility. if cmp is not None: cmp, cmp_key = decide_callable_or_boolean(cmp) return cmp, cmp_key, cmp, cmp_key # If left None, equality is set to the specified default and ordering # mirrors equality. if eq is None: eq, eq_key = default_eq, None else: eq, eq_key = decide_callable_or_boolean(eq) if order is None: order, order_key = eq, eq_key else: order, order_key = decide_callable_or_boolean(order) if eq is False and order is True: msg = "`order` can only be True if `eq` is True too." raise ValueError(msg) return eq, eq_key, order, order_key def _determine_whether_to_implement( cls, flag, auto_detect, dunders, default=True ): """ Check whether we should implement a set of methods for *cls*. *flag* is the argument passed into @attr.s like 'init', *auto_detect* the same as passed into @attr.s and *dunders* is a tuple of attribute names whose presence signal that the user has implemented it themselves. Return *default* if no reason for either for or against is found. """ if flag is True or flag is False: return flag if flag is None and auto_detect is False: return default # Logically, flag is None and auto_detect is True here. for dunder in dunders: if _has_own_attribute(cls, dunder): return False return default def attrs( maybe_cls=None, these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True, unsafe_hash=None, ): r""" A class decorator that adds :term:`dunder methods` according to the specified attributes using `attr.ib` or the *these* argument. Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will *never* go away, though). Args: repr_ns (str): When using nested classes, there was no way in Python 2 to automatically detect that. This argument allows to set a custom name for a more meaningful ``repr`` output. This argument is pointless in Python 3 and is therefore deprecated. .. caution:: Refer to `attrs.define` for the rest of the parameters, but note that they can have different defaults. Notably, leaving *on_setattr* as `None` will **not** add any hooks. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* .. versionadded:: 16.3.0 *str* .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. .. versionchanged:: 17.1.0 *hash* supports `None` as value which is also the default now. .. versionadded:: 17.3.0 *auto_attribs* .. versionchanged:: 18.1.0 If *these* is passed, no attributes are deleted from the class body. .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. .. versionadded:: 18.2.0 *weakref_slot* .. deprecated:: 18.2.0 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a `DeprecationWarning` if the classes compared are subclasses of each other. ``__eq`` and ``__ne__`` never tried to compared subclasses to each other. .. versionchanged:: 19.2.0 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider subclasses comparable anymore. .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. .. versionadded:: 19.2.0 *eq* and *order* .. versionadded:: 20.1.0 *auto_detect* .. versionadded:: 20.1.0 *collect_by_mro* .. versionadded:: 20.1.0 *getstate_setstate* .. versionadded:: 20.1.0 *on_setattr* .. versionadded:: 20.3.0 *field_transformer* .. versionchanged:: 21.1.0 ``init=False`` injects ``__attrs_init__`` .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` .. versionchanged:: 21.1.0 *cmp* undeprecated .. versionadded:: 21.3.0 *match_args* .. versionadded:: 22.2.0 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). .. deprecated:: 24.1.0 *repr_ns* .. versionchanged:: 24.1.0 Instances are not compared as tuples of attributes anymore, but using a big ``and`` condition. This is faster and has more correct behavior for uncomparable values like `math.nan`. .. versionadded:: 24.1.0 If a class has an *inherited* classmethod called ``__attrs_init_subclass__``, it is executed after the class is created. .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. """ if repr_ns is not None: import warnings warnings.warn( DeprecationWarning( "The `repr_ns` argument is deprecated and will be removed in or after August 2025." ), stacklevel=2, ) eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) # unsafe_hash takes precedence due to PEP 681. if unsafe_hash is not None: hash = unsafe_hash if isinstance(on_setattr, (list, tuple)): on_setattr = setters.pipe(*on_setattr) def wrap(cls): is_frozen = frozen or _has_frozen_base_class(cls) is_exc = auto_exc is True and issubclass(cls, BaseException) has_own_setattr = auto_detect and _has_own_attribute( cls, "__setattr__" ) if has_own_setattr and is_frozen: msg = "Can't freeze a class with a custom __setattr__." raise ValueError(msg) builder = _ClassBuilder( cls, these, slots, is_frozen, weakref_slot, _determine_whether_to_implement( cls, getstate_setstate, auto_detect, ("__getstate__", "__setstate__"), default=slots, ), auto_attribs, kw_only, cache_hash, is_exc, collect_by_mro, on_setattr, has_own_setattr, field_transformer, ) if _determine_whether_to_implement( cls, repr, auto_detect, ("__repr__",) ): builder.add_repr(repr_ns) if str is True: builder.add_str() eq = _determine_whether_to_implement( cls, eq_, auto_detect, ("__eq__", "__ne__") ) if not is_exc and eq is True: builder.add_eq() if not is_exc and _determine_whether_to_implement( cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") ): builder.add_order() if not frozen: builder.add_setattr() nonlocal hash if ( hash is None and auto_detect is True and _has_own_attribute(cls, "__hash__") ): hash = False if hash is not True and hash is not False and hash is not None: # Can't use `hash in` because 1 == True for example. msg = "Invalid value for hash. Must be True, False, or None." raise TypeError(msg) if hash is False or (hash is None and eq is False) or is_exc: # Don't do anything. Should fall back to __object__'s __hash__ # which is by id. if cache_hash: msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." raise TypeError(msg) elif hash is True or ( hash is None and eq is True and is_frozen is True ): # Build a __hash__ if told so, or if it's safe. builder.add_hash() else: # Raise TypeError on attempts to hash. if cache_hash: msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." raise TypeError(msg) builder.make_unhashable() if _determine_whether_to_implement( cls, init, auto_detect, ("__init__",) ): builder.add_init() else: builder.add_attrs_init() if cache_hash: msg = "Invalid value for cache_hash. To use hash caching, init must be True." raise TypeError(msg) if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"): builder.add_replace() if ( PY_3_10_PLUS and match_args and not _has_own_attribute(cls, "__match_args__") ): builder.add_match_args() return builder.build_class() # maybe_cls's type depends on the usage of the decorator. It's a class # if it's used as `@attrs` but `None` if used as `@attrs()`. if maybe_cls is None: return wrap return wrap(maybe_cls) _attrs = attrs """ Internal alias so we can use it in functions that take an argument called *attrs*. """ def _has_frozen_base_class(cls): """ Check whether *cls* has a frozen ancestor by looking at its __setattr__. """ return cls.__setattr__ is _frozen_setattrs def _generate_unique_filename(cls: type, func_name: str) -> str: """ Create a "filename" suitable for a function being generated. """ return ( f"" ) def _make_hash_script( cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool ) -> tuple[str, dict]: attrs = tuple( a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) ) tab = " " type_hash = hash(_generate_unique_filename(cls, "hash")) # If eq is custom generated, we need to include the functions in globs globs = {} hash_def = "def __hash__(self" hash_func = "hash((" closing_braces = "))" if not cache_hash: hash_def += "):" else: hash_def += ", *" hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" hash_func = "_cache_wrapper(" + hash_func closing_braces += ")" method_lines = [hash_def] def append_hash_computation_lines(prefix, indent): """ Generate the code for actually computing the hash code. Below this will either be returned directly or used to compute a value which is then cached, depending on the value of cache_hash """ method_lines.extend( [ indent + prefix + hash_func, indent + f" {type_hash},", ] ) for a in attrs: if a.eq_key: cmp_name = f"_{a.name}_key" globs[cmp_name] = a.eq_key method_lines.append( indent + f" {cmp_name}(self.{a.name})," ) else: method_lines.append(indent + f" self.{a.name},") method_lines.append(indent + " " + closing_braces) if cache_hash: method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") if frozen: append_hash_computation_lines( f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 ) method_lines.append(tab * 2 + ")") # close __setattr__ else: append_hash_computation_lines( f"self.{_HASH_CACHE_FIELD} = ", tab * 2 ) method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") else: append_hash_computation_lines("return ", tab) script = "\n".join(method_lines) return script, globs def _add_hash(cls: type, attrs: list[Attribute]): """ Add a hash method to *cls*. """ script, globs = _make_hash_script( cls, attrs, frozen=False, cache_hash=False ) _compile_and_eval( script, globs, filename=_generate_unique_filename(cls, "__hash__") ) cls.__hash__ = globs["__hash__"] return cls def __ne__(self, other): """ Check equality and either forward a NotImplemented or return the result negated. """ result = self.__eq__(other) if result is NotImplemented: return NotImplemented return not result def _make_eq_script(attrs: list) -> tuple[str, dict]: """ Create __eq__ method for *cls* with *attrs*. """ attrs = [a for a in attrs if a.eq] lines = [ "def __eq__(self, other):", " if other.__class__ is not self.__class__:", " return NotImplemented", ] globs = {} if attrs: lines.append(" return (") for a in attrs: if a.eq_key: cmp_name = f"_{a.name}_key" # Add the key function to the global namespace # of the evaluated function. globs[cmp_name] = a.eq_key lines.append( f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})" ) else: lines.append(f" self.{a.name} == other.{a.name}") if a is not attrs[-1]: lines[-1] = f"{lines[-1]} and" lines.append(" )") else: lines.append(" return True") script = "\n".join(lines) return script, globs def _make_order(cls, attrs): """ Create ordering methods for *cls* with *attrs*. """ attrs = [a for a in attrs if a.order] def attrs_to_tuple(obj): """ Save us some typing. """ return tuple( key(value) if key else value for value, key in ( (getattr(obj, a.name), a.order_key) for a in attrs ) ) def __lt__(self, other): """ Automatically created by attrs. """ if other.__class__ is self.__class__: return attrs_to_tuple(self) < attrs_to_tuple(other) return NotImplemented def __le__(self, other): """ Automatically created by attrs. """ if other.__class__ is self.__class__: return attrs_to_tuple(self) <= attrs_to_tuple(other) return NotImplemented def __gt__(self, other): """ Automatically created by attrs. """ if other.__class__ is self.__class__: return attrs_to_tuple(self) > attrs_to_tuple(other) return NotImplemented def __ge__(self, other): """ Automatically created by attrs. """ if other.__class__ is self.__class__: return attrs_to_tuple(self) >= attrs_to_tuple(other) return NotImplemented return __lt__, __le__, __gt__, __ge__ def _add_eq(cls, attrs=None): """ Add equality methods to *cls* with *attrs*. """ if attrs is None: attrs = cls.__attrs_attrs__ script, globs = _make_eq_script(attrs) _compile_and_eval( script, globs, filename=_generate_unique_filename(cls, "__eq__") ) cls.__eq__ = globs["__eq__"] cls.__ne__ = __ne__ return cls def _make_repr_script(attrs, ns) -> tuple[str, dict]: """ Create the source and globs for a __repr__ and return it. """ # Figure out which attributes to include, and which function to use to # format them. The a.repr value can be either bool or a custom # callable. attr_names_with_reprs = tuple( (a.name, (repr if a.repr is True else a.repr), a.init) for a in attrs if a.repr is not False ) globs = { name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr } globs["_compat"] = _compat globs["AttributeError"] = AttributeError globs["NOTHING"] = NOTHING attribute_fragments = [] for name, r, i in attr_names_with_reprs: accessor = ( "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' ) fragment = ( "%s={%s!r}" % (name, accessor) if r == repr else "%s={%s_repr(%s)}" % (name, name, accessor) ) attribute_fragments.append(fragment) repr_fragment = ", ".join(attribute_fragments) if ns is None: cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' else: cls_name_fragment = ns + ".{self.__class__.__name__}" lines = [ "def __repr__(self):", " try:", " already_repring = _compat.repr_context.already_repring", " except AttributeError:", " already_repring = {id(self),}", " _compat.repr_context.already_repring = already_repring", " else:", " if id(self) in already_repring:", " return '...'", " else:", " already_repring.add(id(self))", " try:", f" return f'{cls_name_fragment}({repr_fragment})'", " finally:", " already_repring.remove(id(self))", ] return "\n".join(lines), globs def _add_repr(cls, ns=None, attrs=None): """ Add a repr method to *cls*. """ if attrs is None: attrs = cls.__attrs_attrs__ script, globs = _make_repr_script(attrs, ns) _compile_and_eval( script, globs, filename=_generate_unique_filename(cls, "__repr__") ) cls.__repr__ = globs["__repr__"] return cls def fields(cls): """ Return the tuple of *attrs* attributes for a class. The tuple also allows accessing the fields by their names (see below for examples). Args: cls (type): Class to introspect. Raises: TypeError: If *cls* is not a class. attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. Returns: tuple (with name accessors) of `attrs.Attribute` .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields by name. .. versionchanged:: 23.1.0 Add support for generic classes. """ generic_base = get_generic_base(cls) if generic_base is None and not isinstance(cls, type): msg = "Passed object must be a class." raise TypeError(msg) attrs = getattr(cls, "__attrs_attrs__", None) if attrs is None: if generic_base is not None: attrs = getattr(generic_base, "__attrs_attrs__", None) if attrs is not None: # Even though this is global state, stick it on here to speed # it up. We rely on `cls` being cached for this to be # efficient. cls.__attrs_attrs__ = attrs return attrs msg = f"{cls!r} is not an attrs-decorated class." raise NotAnAttrsClassError(msg) return attrs def fields_dict(cls): """ Return an ordered dictionary of *attrs* attributes for a class, whose keys are the attribute names. Args: cls (type): Class to introspect. Raises: TypeError: If *cls* is not a class. attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs* class. Returns: dict[str, attrs.Attribute]: Dict of attribute name to definition .. versionadded:: 18.1.0 """ if not isinstance(cls, type): msg = "Passed object must be a class." raise TypeError(msg) attrs = getattr(cls, "__attrs_attrs__", None) if attrs is None: msg = f"{cls!r} is not an attrs-decorated class." raise NotAnAttrsClassError(msg) return {a.name: a for a in attrs} def validate(inst): """ Validate all attributes on *inst* that have a validator. Leaves all exceptions through. Args: inst: Instance of a class with *attrs* attributes. """ if _config._run_validators is False: return for a in fields(inst.__class__): v = a.validator if v is not None: v(inst, a, getattr(inst, a.name)) def _is_slot_attr(a_name, base_attr_map): """ Check if the attribute name comes from a slot class. """ cls = base_attr_map.get(a_name) return cls and "__slots__" in cls.__dict__ def _make_init_script( cls, attrs, pre_init, pre_init_has_args, post_init, frozen, slots, cache_hash, base_attr_map, is_exc, cls_on_setattr, attrs_init, ) -> tuple[str, dict, dict]: has_cls_on_setattr = ( cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP ) if frozen and has_cls_on_setattr: msg = "Frozen classes can't use on_setattr." raise ValueError(msg) needs_cached_setattr = cache_hash or frozen filtered_attrs = [] attr_dict = {} for a in attrs: if not a.init and a.default is NOTHING: continue filtered_attrs.append(a) attr_dict[a.name] = a if a.on_setattr is not None: if frozen is True: msg = "Frozen classes can't use on_setattr." raise ValueError(msg) needs_cached_setattr = True elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: needs_cached_setattr = True script, globs, annotations = _attrs_to_init_script( filtered_attrs, frozen, slots, pre_init, pre_init_has_args, post_init, cache_hash, base_attr_map, is_exc, needs_cached_setattr, has_cls_on_setattr, "__attrs_init__" if attrs_init else "__init__", ) if cls.__module__ in sys.modules: # This makes typing.get_type_hints(CLS.__init__) resolve string types. globs.update(sys.modules[cls.__module__].__dict__) globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) if needs_cached_setattr: # Save the lookup overhead in __init__ if we need to circumvent # setattr hooks. globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ return script, globs, annotations def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str: """ Use the cached object.setattr to set *attr_name* to *value_var*. """ return f"_setattr('{attr_name}', {value_var})" def _setattr_with_converter( attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter ) -> str: """ Use the cached object.setattr to set *attr_name* to *value_var*, but run its converter first. """ return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})" def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str: """ Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise relegate to _setattr. """ if has_on_setattr: return _setattr(attr_name, value, True) return f"self.{attr_name} = {value}" def _assign_with_converter( attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter ) -> str: """ Unless *attr_name* has an on_setattr hook, use normal assignment after conversion. Otherwise relegate to _setattr_with_converter. """ if has_on_setattr: return _setattr_with_converter(attr_name, value_var, True, converter) return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}" def _determine_setters( frozen: bool, slots: bool, base_attr_map: dict[str, type] ): """ Determine the correct setter functions based on whether a class is frozen and/or slotted. """ if frozen is True: if slots is True: return (), _setattr, _setattr_with_converter # Dict frozen classes assign directly to __dict__. # But only if the attribute doesn't come from an ancestor slot # class. # Note _inst_dict will be used again below if cache_hash is True def fmt_setter( attr_name: str, value_var: str, has_on_setattr: bool ) -> str: if _is_slot_attr(attr_name, base_attr_map): return _setattr(attr_name, value_var, has_on_setattr) return f"_inst_dict['{attr_name}'] = {value_var}" def fmt_setter_with_converter( attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter, ) -> str: if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): return _setattr_with_converter( attr_name, value_var, has_on_setattr, converter ) return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}" return ( ("_inst_dict = self.__dict__",), fmt_setter, fmt_setter_with_converter, ) # Not frozen -- we can just assign directly. return (), _assign, _assign_with_converter def _attrs_to_init_script( attrs: list[Attribute], is_frozen: bool, is_slotted: bool, call_pre_init: bool, pre_init_has_args: bool, call_post_init: bool, does_cache_hash: bool, base_attr_map: dict[str, type], is_exc: bool, needs_cached_setattr: bool, has_cls_on_setattr: bool, method_name: str, ) -> tuple[str, dict, dict]: """ Return a script of an initializer for *attrs*, a dict of globals, and annotations for the initializer. The globals are required by the generated script. """ lines = ["self.__attrs_pre_init__()"] if call_pre_init else [] if needs_cached_setattr: lines.append( # Circumvent the __setattr__ descriptor to save one lookup per # assignment. Note _setattr will be used again below if # does_cache_hash is True. "_setattr = _cached_setattr_get(self)" ) extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( is_frozen, is_slotted, base_attr_map ) lines.extend(extra_lines) args = [] kw_only_args = [] attrs_to_validate = [] # This is a dictionary of names to validator and converter callables. # Injecting this into __init__ globals lets us avoid lookups. names_for_globals = {} annotations = {"return": None} for a in attrs: if a.validator: attrs_to_validate.append(a) attr_name = a.name has_on_setattr = a.on_setattr is not None or ( a.on_setattr is not setters.NO_OP and has_cls_on_setattr ) # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not # explicitly provided arg_name = a.alias has_factory = isinstance(a.default, Factory) maybe_self = "self" if has_factory and a.default.takes_self else "" if a.converter is not None and not isinstance(a.converter, Converter): converter = Converter(a.converter) else: converter = a.converter if a.init is False: if has_factory: init_factory_name = _INIT_FACTORY_PAT % (a.name,) if converter is not None: lines.append( fmt_setter_with_converter( attr_name, init_factory_name + f"({maybe_self})", has_on_setattr, converter, ) ) names_for_globals[converter._get_global_name(a.name)] = ( converter.converter ) else: lines.append( fmt_setter( attr_name, init_factory_name + f"({maybe_self})", has_on_setattr, ) ) names_for_globals[init_factory_name] = a.default.factory elif converter is not None: lines.append( fmt_setter_with_converter( attr_name, f"attr_dict['{attr_name}'].default", has_on_setattr, converter, ) ) names_for_globals[converter._get_global_name(a.name)] = ( converter.converter ) else: lines.append( fmt_setter( attr_name, f"attr_dict['{attr_name}'].default", has_on_setattr, ) ) elif a.default is not NOTHING and not has_factory: arg = f"{arg_name}=attr_dict['{attr_name}'].default" if a.kw_only: kw_only_args.append(arg) else: args.append(arg) if converter is not None: lines.append( fmt_setter_with_converter( attr_name, arg_name, has_on_setattr, converter ) ) names_for_globals[converter._get_global_name(a.name)] = ( converter.converter ) else: lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) elif has_factory: arg = f"{arg_name}=NOTHING" if a.kw_only: kw_only_args.append(arg) else: args.append(arg) lines.append(f"if {arg_name} is not NOTHING:") init_factory_name = _INIT_FACTORY_PAT % (a.name,) if converter is not None: lines.append( " " + fmt_setter_with_converter( attr_name, arg_name, has_on_setattr, converter ) ) lines.append("else:") lines.append( " " + fmt_setter_with_converter( attr_name, init_factory_name + "(" + maybe_self + ")", has_on_setattr, converter, ) ) names_for_globals[converter._get_global_name(a.name)] = ( converter.converter ) else: lines.append( " " + fmt_setter(attr_name, arg_name, has_on_setattr) ) lines.append("else:") lines.append( " " + fmt_setter( attr_name, init_factory_name + "(" + maybe_self + ")", has_on_setattr, ) ) names_for_globals[init_factory_name] = a.default.factory else: if a.kw_only: kw_only_args.append(arg_name) else: args.append(arg_name) if converter is not None: lines.append( fmt_setter_with_converter( attr_name, arg_name, has_on_setattr, converter ) ) names_for_globals[converter._get_global_name(a.name)] = ( converter.converter ) else: lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) if a.init is True: if a.type is not None and converter is None: annotations[arg_name] = a.type elif converter is not None and converter._first_param_type: # Use the type from the converter if present. annotations[arg_name] = converter._first_param_type if attrs_to_validate: # we can skip this if there are no validators. names_for_globals["_config"] = _config lines.append("if _config._run_validators is True:") for a in attrs_to_validate: val_name = "__attr_validator_" + a.name attr_name = "__attr_" + a.name lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") names_for_globals[val_name] = a.validator names_for_globals[attr_name] = a if call_post_init: lines.append("self.__attrs_post_init__()") # Because this is set only after __attrs_post_init__ is called, a crash # will result if post-init tries to access the hash code. This seemed # preferable to setting this beforehand, in which case alteration to field # values during post-init combined with post-init accessing the hash code # would result in silent bugs. if does_cache_hash: if is_frozen: if is_slotted: init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)" else: init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None" else: init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None" lines.append(init_hash_cache) # For exceptions we rely on BaseException.__init__ for proper # initialization. if is_exc: vals = ",".join(f"self.{a.name}" for a in attrs if a.init) lines.append(f"BaseException.__init__(self, {vals})") args = ", ".join(args) pre_init_args = args if kw_only_args: # leading comma & kw_only args args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}" pre_init_kw_only_args = ", ".join( [ f"{kw_arg_name}={kw_arg_name}" # We need to remove the defaults from the kw_only_args. for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args) ] ) pre_init_args += ", " if pre_init_args else "" pre_init_args += pre_init_kw_only_args if call_pre_init and pre_init_has_args: # If pre init method has arguments, pass same arguments as `__init__`. lines[0] = f"self.__attrs_pre_init__({pre_init_args})" # Python <3.12 doesn't allow backslashes in f-strings. NL = "\n " return ( f"""def {method_name}(self, {args}): {NL.join(lines) if lines else "pass"} """, names_for_globals, annotations, ) def _default_init_alias_for(name: str) -> str: """ The default __init__ parameter name for a field. This performs private-name adjustment via leading-unscore stripping, and is the default value of Attribute.alias if not provided. """ return name.lstrip("_") class Attribute: """ *Read-only* representation of an attribute. .. warning:: You should never instantiate this class yourself. The class has *all* arguments of `attr.ib` (except for ``factory`` which is only syntactic sugar for ``default=Factory(...)`` plus the following: - ``name`` (`str`): The name of the attribute. - ``alias`` (`str`): The __init__ parameter name of the attribute, after any explicit overrides and default private-attribute-name handling. - ``inherited`` (`bool`): Whether or not that attribute has been inherited from a base class. - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables that are used for comparing and ordering objects by this attribute, respectively. These are set by passing a callable to `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also :ref:`comparison customization `. Instances of this class are frequently used for introspection purposes like: - `fields` returns a tuple of them. - Validators get them passed as the first argument. - The :ref:`field transformer ` hook receives a list of them. - The ``alias`` property exposes the __init__ parameter name of the field, with any overrides and default private-attribute handling applied. .. versionadded:: 20.1.0 *inherited* .. versionadded:: 20.1.0 *on_setattr* .. versionchanged:: 20.2.0 *inherited* is not taken into account for equality checks and hashing anymore. .. versionadded:: 21.1.0 *eq_key* and *order_key* .. versionadded:: 22.2.0 *alias* For the full version history of the fields, see `attr.ib`. """ # These slots must NOT be reordered because we use them later for # instantiation. __slots__ = ( # noqa: RUF023 "name", "default", "validator", "repr", "eq", "eq_key", "order", "order_key", "hash", "init", "metadata", "type", "converter", "kw_only", "inherited", "on_setattr", "alias", ) def __init__( self, name, default, validator, repr, cmp, # XXX: unused, remove along with other cmp code. hash, init, inherited, metadata=None, type=None, converter=None, kw_only=False, eq=None, eq_key=None, order=None, order_key=None, on_setattr=None, alias=None, ): eq, eq_key, order, order_key = _determine_attrib_eq_order( cmp, eq_key or eq, order_key or order, True ) # Cache this descriptor here to speed things up later. bound_setattr = _OBJ_SETATTR.__get__(self) # Despite the big red warning, people *do* instantiate `Attribute` # themselves. bound_setattr("name", name) bound_setattr("default", default) bound_setattr("validator", validator) bound_setattr("repr", repr) bound_setattr("eq", eq) bound_setattr("eq_key", eq_key) bound_setattr("order", order) bound_setattr("order_key", order_key) bound_setattr("hash", hash) bound_setattr("init", init) bound_setattr("converter", converter) bound_setattr( "metadata", ( types.MappingProxyType(dict(metadata)) # Shallow copy if metadata else _EMPTY_METADATA_SINGLETON ), ) bound_setattr("type", type) bound_setattr("kw_only", kw_only) bound_setattr("inherited", inherited) bound_setattr("on_setattr", on_setattr) bound_setattr("alias", alias) def __setattr__(self, name, value): raise FrozenInstanceError @classmethod def from_counting_attr(cls, name: str, ca: _CountingAttr, type=None): # type holds the annotated value. deal with conflicts: if type is None: type = ca.type elif ca.type is not None: msg = f"Type annotation and type argument cannot both be present for '{name}'." raise ValueError(msg) return cls( name, ca._default, ca._validator, ca.repr, None, ca.hash, ca.init, False, ca.metadata, type, ca.converter, ca.kw_only, ca.eq, ca.eq_key, ca.order, ca.order_key, ca.on_setattr, ca.alias, ) # Don't use attrs.evolve since fields(Attribute) doesn't work def evolve(self, **changes): """ Copy *self* and apply *changes*. This works similarly to `attrs.evolve` but that function does not work with :class:`attrs.Attribute`. It is mainly meant to be used for `transform-fields`. .. versionadded:: 20.3.0 """ new = copy.copy(self) new._setattrs(changes.items()) return new # Don't use _add_pickle since fields(Attribute) doesn't work def __getstate__(self): """ Play nice with pickle. """ return tuple( getattr(self, name) if name != "metadata" else dict(self.metadata) for name in self.__slots__ ) def __setstate__(self, state): """ Play nice with pickle. """ self._setattrs(zip(self.__slots__, state)) def _setattrs(self, name_values_pairs): bound_setattr = _OBJ_SETATTR.__get__(self) for name, value in name_values_pairs: if name != "metadata": bound_setattr(name, value) else: bound_setattr( name, ( types.MappingProxyType(dict(value)) if value else _EMPTY_METADATA_SINGLETON ), ) _a = [ Attribute( name=name, default=NOTHING, validator=None, repr=True, cmp=None, eq=True, order=False, hash=(name != "metadata"), init=True, inherited=False, alias=_default_init_alias_for(name), ) for name in Attribute.__slots__ ] Attribute = _add_hash( _add_eq( _add_repr(Attribute, attrs=_a), attrs=[a for a in _a if a.name != "inherited"], ), attrs=[a for a in _a if a.hash and a.name != "inherited"], ) class _CountingAttr: """ Intermediate representation of attributes that uses a counter to preserve the order in which the attributes have been defined. *Internal* data structure of the attrs library. Running into is most likely the result of a bug like a forgotten `@attr.s` decorator. """ __slots__ = ( "_default", "_validator", "alias", "converter", "counter", "eq", "eq_key", "hash", "init", "kw_only", "metadata", "on_setattr", "order", "order_key", "repr", "type", ) __attrs_attrs__ = ( *tuple( Attribute( name=name, alias=_default_init_alias_for(name), default=NOTHING, validator=None, repr=True, cmp=None, hash=True, init=True, kw_only=False, eq=True, eq_key=None, order=False, order_key=None, inherited=False, on_setattr=None, ) for name in ( "counter", "_default", "repr", "eq", "order", "hash", "init", "on_setattr", "alias", ) ), Attribute( name="metadata", alias="metadata", default=None, validator=None, repr=True, cmp=None, hash=False, init=True, kw_only=False, eq=True, eq_key=None, order=False, order_key=None, inherited=False, on_setattr=None, ), ) cls_counter = 0 def __init__( self, default, validator, repr, cmp, hash, init, converter, metadata, type, kw_only, eq, eq_key, order, order_key, on_setattr, alias, ): _CountingAttr.cls_counter += 1 self.counter = _CountingAttr.cls_counter self._default = default self._validator = validator self.converter = converter self.repr = repr self.eq = eq self.eq_key = eq_key self.order = order self.order_key = order_key self.hash = hash self.init = init self.metadata = metadata self.type = type self.kw_only = kw_only self.on_setattr = on_setattr self.alias = alias def validator(self, meth): """ Decorator that adds *meth* to the list of validators. Returns *meth* unchanged. .. versionadded:: 17.1.0 """ if self._validator is None: self._validator = meth else: self._validator = and_(self._validator, meth) return meth def default(self, meth): """ Decorator that allows to set the default for an attribute. Returns *meth* unchanged. Raises: DefaultAlreadySetError: If default has been set before. .. versionadded:: 17.1.0 """ if self._default is not NOTHING: raise DefaultAlreadySetError self._default = Factory(meth, takes_self=True) return meth _CountingAttr = _add_eq(_add_repr(_CountingAttr)) class Factory: """ Stores a factory callable. If passed as the default value to `attrs.field`, the factory is used to generate a new value. Args: factory (typing.Callable): A callable that takes either none or exactly one mandatory positional argument depending on *takes_self*. takes_self (bool): Pass the partially initialized instance that is being initialized as a positional argument. .. versionadded:: 17.1.0 *takes_self* """ __slots__ = ("factory", "takes_self") def __init__(self, factory, takes_self=False): self.factory = factory self.takes_self = takes_self def __getstate__(self): """ Play nice with pickle. """ return tuple(getattr(self, name) for name in self.__slots__) def __setstate__(self, state): """ Play nice with pickle. """ for name, value in zip(self.__slots__, state): setattr(self, name, value) _f = [ Attribute( name=name, default=NOTHING, validator=None, repr=True, cmp=None, eq=True, order=False, hash=True, init=True, inherited=False, ) for name in Factory.__slots__ ] Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) class Converter: """ Stores a converter callable. Allows for the wrapped converter to take additional arguments. The arguments are passed in the order they are documented. Args: converter (Callable): A callable that converts the passed value. takes_self (bool): Pass the partially initialized instance that is being initialized as a positional argument. (default: `False`) takes_field (bool): Pass the field definition (an :class:`Attribute`) into the converter as a positional argument. (default: `False`) .. versionadded:: 24.1.0 """ __slots__ = ( "__call__", "_first_param_type", "_global_name", "converter", "takes_field", "takes_self", ) def __init__(self, converter, *, takes_self=False, takes_field=False): self.converter = converter self.takes_self = takes_self self.takes_field = takes_field ex = _AnnotationExtractor(converter) self._first_param_type = ex.get_first_param_type() if not (self.takes_self or self.takes_field): self.__call__ = lambda value, _, __: self.converter(value) elif self.takes_self and not self.takes_field: self.__call__ = lambda value, instance, __: self.converter( value, instance ) elif not self.takes_self and self.takes_field: self.__call__ = lambda value, __, field: self.converter( value, field ) else: self.__call__ = lambda value, instance, field: self.converter( value, instance, field ) rt = ex.get_return_type() if rt is not None: self.__call__.__annotations__["return"] = rt @staticmethod def _get_global_name(attr_name: str) -> str: """ Return the name that a converter for an attribute name *attr_name* would have. """ return f"__attr_converter_{attr_name}" def _fmt_converter_call(self, attr_name: str, value_var: str) -> str: """ Return a string that calls the converter for an attribute name *attr_name* and the value in variable named *value_var* according to `self.takes_self` and `self.takes_field`. """ if not (self.takes_self or self.takes_field): return f"{self._get_global_name(attr_name)}({value_var})" if self.takes_self and self.takes_field: return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])" if self.takes_self: return f"{self._get_global_name(attr_name)}({value_var}, self)" return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])" def __getstate__(self): """ Return a dict containing only converter and takes_self -- the rest gets computed when loading. """ return { "converter": self.converter, "takes_self": self.takes_self, "takes_field": self.takes_field, } def __setstate__(self, state): """ Load instance from state. """ self.__init__(**state) _f = [ Attribute( name=name, default=NOTHING, validator=None, repr=True, cmp=None, eq=True, order=False, hash=True, init=True, inherited=False, ) for name in ("converter", "takes_self", "takes_field") ] Converter = _add_hash( _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f ) def make_class( name, attrs, bases=(object,), class_body=None, **attributes_arguments ): r""" A quick way to create a new class called *name* with *attrs*. .. note:: ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define` which means that it doesn't come with some of the improved defaults. For example, if you want the same ``on_setattr`` behavior as in `attrs.define`, you have to pass the hooks yourself: ``make_class(..., on_setattr=setters.pipe(setters.convert, setters.validate)`` .. warning:: It is *your* duty to ensure that the class name and the attribute names are valid identifiers. ``make_class()`` will *not* validate them for you. Args: name (str): The name for the new class. attrs (list | dict): A list of names or a dictionary of mappings of names to `attr.ib`\ s / `attrs.field`\ s. The order is deduced from the order of the names or attributes inside *attrs*. Otherwise the order of the definition of the attributes is used. bases (tuple[type, ...]): Classes that the new class will subclass. class_body (dict): An optional dictionary of class attributes for the new class. attributes_arguments: Passed unmodified to `attr.s`. Returns: type: A new class with *attrs*. .. versionadded:: 17.1.0 *bases* .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. .. versionchanged:: 23.2.0 *class_body* .. versionchanged:: 25.2.0 Class names can now be unicode. """ # Class identifiers are converted into the normal form NFKC while parsing name = unicodedata.normalize("NFKC", name) if isinstance(attrs, dict): cls_dict = attrs elif isinstance(attrs, (list, tuple)): cls_dict = {a: attrib() for a in attrs} else: msg = "attrs argument must be a dict or a list." raise TypeError(msg) pre_init = cls_dict.pop("__attrs_pre_init__", None) post_init = cls_dict.pop("__attrs_post_init__", None) user_init = cls_dict.pop("__init__", None) body = {} if class_body is not None: body.update(class_body) if pre_init is not None: body["__attrs_pre_init__"] = pre_init if post_init is not None: body["__attrs_post_init__"] = post_init if user_init is not None: body["__init__"] = user_init type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) # For pickling to work, the __module__ variable needs to be set to the # frame where the class is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython). with contextlib.suppress(AttributeError, ValueError): type_.__module__ = sys._getframe(1).f_globals.get( "__name__", "__main__" ) # We do it here for proper warnings with meaningful stacklevel. cmp = attributes_arguments.pop("cmp", None) ( attributes_arguments["eq"], attributes_arguments["order"], ) = _determine_attrs_eq_order( cmp, attributes_arguments.get("eq"), attributes_arguments.get("order"), True, ) cls = _attrs(these=cls_dict, **attributes_arguments)(type_) # Only add type annotations now or "_attrs()" will complain: cls.__annotations__ = { k: v.type for k, v in cls_dict.items() if v.type is not None } return cls # These are required by within this module so we define them here and merely # import into .validators / .converters. @attrs(slots=True, unsafe_hash=True) class _AndValidator: """ Compose many validators to a single one. """ _validators = attrib() def __call__(self, inst, attr, value): for v in self._validators: v(inst, attr, value) def and_(*validators): """ A validator that composes multiple validators into one. When called on a value, it runs all wrapped validators. Args: validators (~collections.abc.Iterable[typing.Callable]): Arbitrary number of validators. .. versionadded:: 17.1.0 """ vals = [] for validator in validators: vals.extend( validator._validators if isinstance(validator, _AndValidator) else [validator] ) return _AndValidator(tuple(vals)) def pipe(*converters): """ A converter that composes multiple converters into one. When called on a value, it runs all wrapped converters, returning the *last* value. Type annotations will be inferred from the wrapped converters', if they have any. converters (~collections.abc.Iterable[typing.Callable]): Arbitrary number of converters. .. versionadded:: 20.1.0 """ return_instance = any(isinstance(c, Converter) for c in converters) if return_instance: def pipe_converter(val, inst, field): for c in converters: val = ( c(val, inst, field) if isinstance(c, Converter) else c(val) ) return val else: def pipe_converter(val): for c in converters: val = c(val) return val if not converters: # If the converter list is empty, pipe_converter is the identity. A = TypeVar("A") pipe_converter.__annotations__.update({"val": A, "return": A}) else: # Get parameter type from first converter. t = _AnnotationExtractor(converters[0]).get_first_param_type() if t: pipe_converter.__annotations__["val"] = t last = converters[-1] if not PY_3_11_PLUS and isinstance(last, Converter): last = last.__call__ # Get return type from last converter. rt = _AnnotationExtractor(last).get_return_type() if rt: pipe_converter.__annotations__["return"] = rt if return_instance: return Converter(pipe_converter, takes_self=True, takes_field=True) return pipe_converter python-attrs-attrs-bd2446d/src/attr/_next_gen.py000066400000000000000000000575131476453530700220460ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ These are keyword-only APIs that call `attr.s` and `attr.ib` with different default values. """ from functools import partial from . import setters from ._funcs import asdict as _asdict from ._funcs import astuple as _astuple from ._make import ( _DEFAULT_ON_SETATTR, NOTHING, _frozen_setattrs, attrib, attrs, ) from .exceptions import UnannotatedAttributeError def define( maybe_cls=None, *, these=None, repr=None, unsafe_hash=None, hash=None, init=None, slots=True, frozen=False, weakref_slot=True, str=False, auto_attribs=None, kw_only=False, cache_hash=False, auto_exc=True, eq=None, order=False, auto_detect=True, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True, ): r""" A class decorator that adds :term:`dunder methods` according to :term:`fields ` specified using :doc:`type annotations `, `field()` calls, or the *these* argument. Since *attrs* patches or replaces an existing class, you cannot use `object.__init_subclass__` with *attrs* classes, because it runs too early. As a replacement, you can define ``__attrs_init_subclass__`` on your class. It will be called by *attrs* classes that subclass it after they're created. See also :ref:`init-subclass`. Args: slots (bool): Create a :term:`slotted class ` that's more memory-efficient. Slotted classes are generally superior to the default dict classes, but have some gotchas you should know about, so we encourage you to read the :term:`glossary entry `. auto_detect (bool): Instead of setting the *init*, *repr*, *eq*, and *hash* arguments explicitly, assume they are set to True **unless any** of the involved methods for one of the arguments is implemented in the *current* class (meaning, it is *not* inherited from some base class). So, for example by implementing ``__eq__`` on a class yourself, *attrs* will deduce ``eq=False`` and will create *neither* ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible ``__ne__`` by default, so it *should* be enough to only implement ``__eq__`` in most cases). Passing True or False` to *init*, *repr*, *eq*, or *hash* overrides whatever *auto_detect* would determine. auto_exc (bool): If the class subclasses `BaseException` (which implicitly includes any subclass of any exception), the following happens to behave like a well-behaved Python exception class: - the values for *eq*, *order*, and *hash* are ignored and the instances compare and hash by the instance's ids [#]_ , - all attributes that are either passed into ``__init__`` or have a default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. .. [#] Note that *attrs* will *not* remove existing implementations of ``__hash__`` or the equality methods. It just won't add own ones. on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): A callable that is run whenever the user attempts to set an attribute (either by assignment like ``i.x = 42`` or by using `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments as validators: the instance, the attribute that is being modified, and the new value. If no exception is raised, the attribute is set to the return value of the callable. If a list of callables is passed, they're automatically wrapped in an `attrs.setters.pipe`. If left None, the default behavior is to run converters and validators whenever an attribute is set. init (bool): Create a ``__init__`` method that initializes the *attrs* attributes. Leading underscores are stripped for the argument name, unless an alias is set on the attribute. .. seealso:: `init` shows advanced ways to customize the generated ``__init__`` method, including executing code before and after. repr(bool): Create a ``__repr__`` method with a human readable representation of *attrs* attributes. str (bool): Create a ``__str__`` method that is identical to ``__repr__``. This is usually not necessary except for `Exception`\ s. eq (bool | None): If True or None (default), add ``__eq__`` and ``__ne__`` methods that check two instances for equality. .. seealso:: `comparison` describes how to customize the comparison behavior going as far comparing NumPy arrays. order (bool | None): If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` methods that behave like *eq* above and allow instances to be ordered. They compare the instances as if they were tuples of their *attrs* attributes if and only if the types of both classes are *identical*. If `None` mirror value of *eq*. .. seealso:: `comparison` unsafe_hash (bool | None): If None (default), the ``__hash__`` method is generated according how *eq* and *frozen* are set. 1. If *both* are True, *attrs* will generate a ``__hash__`` for you. 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to None, marking it unhashable (which it is). 3. If *eq* is False, ``__hash__`` will be left untouched meaning the ``__hash__`` method of the base class will be used. If the base class is `object`, this means it will fall back to id-based hashing. Although not recommended, you can decide for yourself and force *attrs* to create one (for example, if the class is immutable even though you didn't freeze it programmatically) by passing True or not. Both of these cases are rather special and should be used carefully. .. seealso:: - Our documentation on `hashing`, - Python's documentation on `object.__hash__`, - and the `GitHub issue that led to the default \ behavior `_ for more details. hash (bool | None): Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence. cache_hash (bool): Ensure that the object's hash code is computed only once and stored on the object. If this is set to True, hashing must be either explicitly or implicitly enabled for this class. If the hash code is cached, avoid any reassignments of fields involved in hash code computation or mutations of the objects those fields point to after object creation. If such changes occur, the behavior of the object's hash code is undefined. frozen (bool): Make instances immutable after initialization. If someone attempts to modify a frozen instance, `attrs.exceptions.FrozenInstanceError` is raised. .. note:: 1. This is achieved by installing a custom ``__setattr__`` method on your class, so you can't implement your own. 2. True immutability is impossible in Python. 3. This *does* have a minor a runtime performance `impact ` when initializing new instances. In other words: ``__init__`` is slightly slower with ``frozen=True``. 4. If a class is frozen, you cannot modify ``self`` in ``__attrs_post_init__`` or a self-written ``__init__``. You can circumvent that limitation by using ``object.__setattr__(self, "attribute_name", value)``. 5. Subclasses of a frozen class are frozen too. kw_only (bool): Make all attributes keyword-only in the generated ``__init__`` (if *init* is False, this parameter is ignored). weakref_slot (bool): Make instances weak-referenceable. This has no effect unless *slots* is True. field_transformer (~typing.Callable | None): A function that is called with the original class object and all fields right before *attrs* finalizes the class. You can use this, for example, to automatically add converters or validators to fields based on their types. .. seealso:: `transform-fields` match_args (bool): If True (default), set ``__match_args__`` on the class to support :pep:`634` (*Structural Pattern Matching*). It is a tuple of all non-keyword-only ``__init__`` parameter names on Python 3.10 and later. Ignored on older Python versions. collect_by_mro (bool): If True, *attrs* collects attributes from base classes correctly according to the `method resolution order `_. If False, *attrs* will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. See also `issue #428 `_. getstate_setstate (bool | None): .. note:: This is usually only interesting for slotted classes and you should probably just set *auto_detect* to True. If True, ``__getstate__`` and ``__setstate__`` are generated and attached to the class. This is necessary for slotted classes to be pickleable. If left None, it's True by default for slotted classes and False for dict classes. If *auto_detect* is True, and *getstate_setstate* is left None, and **either** ``__getstate__`` or ``__setstate__`` is detected directly on the class (meaning: not inherited), it is set to False (this is usually what you want). auto_attribs (bool | None): If True, look at type annotations to determine which attributes to use, like `dataclasses`. If False, it will only look for explicit :func:`field` class attributes, like classic *attrs*. If left None, it will guess: 1. If any attributes are annotated and no unannotated `attrs.field`\ s are found, it assumes *auto_attribs=True*. 2. Otherwise it assumes *auto_attribs=False* and tries to collect `attrs.field`\ s. If *attrs* decides to look at type annotations, **all** fields **must** be annotated. If *attrs* encounters a field that is set to a :func:`field` / `attr.ib` but lacks a type annotation, an `attrs.exceptions.UnannotatedAttributeError` is raised. Use ``field_name: typing.Any = field(...)`` if you don't want to set a type. .. warning:: For features that use the attribute name to create decorators (for example, :ref:`validators `), you still *must* assign :func:`field` / `attr.ib` to them. Otherwise Python will either not find the name or try to use the default value to call, for example, ``validator`` on it. Attributes annotated as `typing.ClassVar`, and attributes that are neither annotated nor set to an `field()` are **ignored**. these (dict[str, object]): A dictionary of name to the (private) return value of `field()` mappings. This is useful to avoid the definition of your attributes within the class body because you can't (for example, if you want to add ``__repr__`` methods to Django models) or don't want to. If *these* is not `None`, *attrs* will *not* search the class body for attributes and will *not* remove any attributes from it. The order is deduced from the order of the attributes inside *these*. Arguably, this is a rather obscure feature. .. versionadded:: 20.1.0 .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. .. versionadded:: 22.2.0 *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). .. versionchanged:: 24.1.0 Instances are not compared as tuples of attributes anymore, but using a big ``and`` condition. This is faster and has more correct behavior for uncomparable values like `math.nan`. .. versionadded:: 24.1.0 If a class has an *inherited* classmethod called ``__attrs_init_subclass__``, it is executed after the class is created. .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. .. versionadded:: 24.3.0 Unless already present, a ``__replace__`` method is automatically created for `copy.replace` (Python 3.13+ only). .. note:: The main differences to the classic `attr.s` are: - Automatically detect whether or not *auto_attribs* should be `True` (c.f. *auto_attribs* parameter). - Converters and validators run when attributes are set by default -- if *frozen* is `False`. - *slots=True* Usually, this has only upsides and few visible effects in everyday programming. But it *can* lead to some surprising behaviors, so please make sure to read :term:`slotted classes`. - *auto_exc=True* - *auto_detect=True* - *order=False* - Some options that were only relevant on Python 2 or were kept around for backwards-compatibility have been removed. """ def do_it(cls, auto_attribs): return attrs( maybe_cls=cls, these=these, repr=repr, hash=hash, unsafe_hash=unsafe_hash, init=init, slots=slots, frozen=frozen, weakref_slot=weakref_slot, str=str, auto_attribs=auto_attribs, kw_only=kw_only, cache_hash=cache_hash, auto_exc=auto_exc, eq=eq, order=order, auto_detect=auto_detect, collect_by_mro=True, getstate_setstate=getstate_setstate, on_setattr=on_setattr, field_transformer=field_transformer, match_args=match_args, ) def wrap(cls): """ Making this a wrapper ensures this code runs during class creation. We also ensure that frozen-ness of classes is inherited. """ nonlocal frozen, on_setattr had_on_setattr = on_setattr not in (None, setters.NO_OP) # By default, mutable classes convert & validate on setattr. if frozen is False and on_setattr is None: on_setattr = _DEFAULT_ON_SETATTR # However, if we subclass a frozen class, we inherit the immutability # and disable on_setattr. for base_cls in cls.__bases__: if base_cls.__setattr__ is _frozen_setattrs: if had_on_setattr: msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." raise ValueError(msg) on_setattr = setters.NO_OP break if auto_attribs is not None: return do_it(cls, auto_attribs) try: return do_it(cls, True) except UnannotatedAttributeError: return do_it(cls, False) # maybe_cls's type depends on the usage of the decorator. It's a class # if it's used as `@attrs` but `None` if used as `@attrs()`. if maybe_cls is None: return wrap return wrap(maybe_cls) mutable = define frozen = partial(define, frozen=True, on_setattr=None) def field( *, default=NOTHING, validator=None, repr=True, hash=None, init=True, metadata=None, type=None, converter=None, factory=None, kw_only=False, eq=None, order=None, on_setattr=None, alias=None, ): """ Create a new :term:`field` / :term:`attribute` on a class. .. warning:: Does **nothing** unless the class is also decorated with `attrs.define` (or similar)! Args: default: A value that is used if an *attrs*-generated ``__init__`` is used and no value is passed while instantiating or the attribute is excluded using ``init=False``. If the value is an instance of `attrs.Factory`, its callable will be used to construct a new value (useful for mutable data types like lists or dicts). If a default is not set (or set manually to `attrs.NOTHING`), a value *must* be supplied when instantiating; otherwise a `TypeError` will be raised. .. seealso:: `defaults` factory (~typing.Callable): Syntactic sugar for ``default=attr.Factory(factory)``. validator (~typing.Callable | list[~typing.Callable]): Callable that is called by *attrs*-generated ``__init__`` methods after the instance has been initialized. They receive the initialized instance, the :func:`~attrs.Attribute`, and the passed value. The return value is *not* inspected so the validator has to throw an exception itself. If a `list` is passed, its items are treated as validators and must all pass. Validators can be globally disabled and re-enabled using `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. The validator can also be set using decorator notation as shown below. .. seealso:: :ref:`validators` repr (bool | ~typing.Callable): Include this attribute in the generated ``__repr__`` method. If True, include the attribute; if False, omit it. By default, the built-in ``repr()`` function is used. To override how the attribute value is formatted, pass a ``callable`` that takes a single value and returns a string. Note that the resulting string is used as-is, which means it will be used directly *instead* of calling ``repr()`` (the default). eq (bool | ~typing.Callable): If True (default), include this attribute in the generated ``__eq__`` and ``__ne__`` methods that check two instances for equality. To override how the attribute value is compared, pass a callable that takes a single value and returns the value to be compared. .. seealso:: `comparison` order (bool | ~typing.Callable): If True (default), include this attributes in the generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To override how the attribute value is ordered, pass a callable that takes a single value and returns the value to be ordered. .. seealso:: `comparison` hash (bool | None): Include this attribute in the generated ``__hash__`` method. If None (default), mirror *eq*'s value. This is the correct behavior according the Python spec. Setting this value to anything else than None is *discouraged*. .. seealso:: `hashing` init (bool): Include this attribute in the generated ``__init__`` method. It is possible to set this to False and set a default value. In that case this attributed is unconditionally initialized with the specified default value or factory. .. seealso:: `init` converter (typing.Callable | Converter): A callable that is called by *attrs*-generated ``__init__`` methods to convert attribute's value to the desired format. If a vanilla callable is passed, it is given the passed-in value as the only positional argument. It is possible to receive additional arguments by wrapping the callable in a `Converter`. Either way, the returned value will be used as the new value of the attribute. The value is converted before being passed to the validator, if any. .. seealso:: :ref:`converters` metadata (dict | None): An arbitrary mapping, to be used by third-party code. .. seealso:: `extending-metadata`. type (type): The type of the attribute. Nowadays, the preferred method to specify the type is using a variable annotation (see :pep:`526`). This argument is provided for backwards-compatibility and for usage with `make_class`. Regardless of the approach used, the type will be stored on ``Attribute.type``. Please note that *attrs* doesn't do anything with this metadata by itself. You can use it as part of your own code or for `static type checking `. kw_only (bool): Make this attribute keyword-only in the generated ``__init__`` (if ``init`` is False, this parameter is ignored). on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): Allows to overwrite the *on_setattr* setting from `attr.s`. If left None, the *on_setattr* value from `attr.s` is used. Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this attribute -- regardless of the setting in `define()`. alias (str | None): Override this attribute's parameter name in the generated ``__init__`` method. If left None, default to ``name`` stripped of leading underscores. See `private-attributes`. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 *eq*, *order*, and *cmp* also accept a custom callable .. versionadded:: 22.2.0 *alias* .. versionadded:: 23.1.0 The *type* parameter has been re-added; mostly for `attrs.make_class`. Please note that type checkers ignore this metadata. .. seealso:: `attr.ib` """ return attrib( default=default, validator=validator, repr=repr, hash=hash, init=init, metadata=metadata, type=type, converter=converter, factory=factory, kw_only=kw_only, eq=eq, order=order, on_setattr=on_setattr, alias=alias, ) def asdict(inst, *, recurse=True, filter=None, value_serializer=None): """ Same as `attr.asdict`, except that collections types are always retained and dict is always used as *dict_factory*. .. versionadded:: 21.3.0 """ return _asdict( inst=inst, recurse=recurse, filter=filter, value_serializer=value_serializer, retain_collection_types=True, ) def astuple(inst, *, recurse=True, filter=None): """ Same as `attr.astuple`, except that collections types are always retained and `tuple` is always used as the *tuple_factory*. .. versionadded:: 21.3.0 """ return _astuple( inst=inst, recurse=recurse, filter=filter, retain_collection_types=True ) python-attrs-attrs-bd2446d/src/attr/_typing_compat.pyi000066400000000000000000000007251476453530700232560ustar00rootroot00000000000000from typing import Any, ClassVar, Protocol # MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. MYPY = False if MYPY: # A protocol to be able to statically accept an attrs class. class AttrsInstance_(Protocol): __attrs_attrs__: ClassVar[Any] else: # For type checkers without plug-in support use an empty protocol that # will (hopefully) be combined into a union. class AttrsInstance_(Protocol): pass python-attrs-attrs-bd2446d/src/attr/_version_info.py000066400000000000000000000041111476453530700227210ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from functools import total_ordering from ._funcs import astuple from ._make import attrib, attrs @total_ordering @attrs(eq=False, order=False, slots=True, frozen=True) class VersionInfo: """ A version object that can be compared to tuple of length 1--4: >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) True >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) True >>> vi = attr.VersionInfo(19, 2, 0, "final") >>> vi < (19, 1, 1) False >>> vi < (19,) False >>> vi == (19, 2,) True >>> vi == (19, 2, 1) False .. versionadded:: 19.2 """ year = attrib(type=int) minor = attrib(type=int) micro = attrib(type=int) releaselevel = attrib(type=str) @classmethod def _from_version_string(cls, s): """ Parse *s* and return a _VersionInfo. """ v = s.split(".") if len(v) == 3: v.append("final") return cls( year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] ) def _ensure_tuple(self, other): """ Ensure *other* is a tuple of a valid length. Returns a possibly transformed *other* and ourselves as a tuple of the same length as *other*. """ if self.__class__ is other.__class__: other = astuple(other) if not isinstance(other, tuple): raise NotImplementedError if not (1 <= len(other) <= 4): raise NotImplementedError return astuple(self)[: len(other)], other def __eq__(self, other): try: us, them = self._ensure_tuple(other) except NotImplementedError: return NotImplemented return us == them def __lt__(self, other): try: us, them = self._ensure_tuple(other) except NotImplementedError: return NotImplemented # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't # have to do anything special with releaselevel for now. return us < them python-attrs-attrs-bd2446d/src/attr/_version_info.pyi000066400000000000000000000003211476453530700230710ustar00rootroot00000000000000class VersionInfo: @property def year(self) -> int: ... @property def minor(self) -> int: ... @property def micro(self) -> int: ... @property def releaselevel(self) -> str: ... python-attrs-attrs-bd2446d/src/attr/converters.py000066400000000000000000000074251476453530700222670ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Commonly useful converters. """ import typing from ._compat import _AnnotationExtractor from ._make import NOTHING, Converter, Factory, pipe __all__ = [ "default_if_none", "optional", "pipe", "to_bool", ] def optional(converter): """ A converter that allows an attribute to be optional. An optional attribute is one which can be set to `None`. Type annotations will be inferred from the wrapped converter's, if it has any. Args: converter (typing.Callable): the converter that is used for non-`None` values. .. versionadded:: 17.1.0 """ if isinstance(converter, Converter): def optional_converter(val, inst, field): if val is None: return None return converter(val, inst, field) else: def optional_converter(val): if val is None: return None return converter(val) xtr = _AnnotationExtractor(converter) t = xtr.get_first_param_type() if t: optional_converter.__annotations__["val"] = typing.Optional[t] rt = xtr.get_return_type() if rt: optional_converter.__annotations__["return"] = typing.Optional[rt] if isinstance(converter, Converter): return Converter(optional_converter, takes_self=True, takes_field=True) return optional_converter def default_if_none(default=NOTHING, factory=None): """ A converter that allows to replace `None` values by *default* or the result of *factory*. Args: default: Value to be used if `None` is passed. Passing an instance of `attrs.Factory` is supported, however the ``takes_self`` option is *not*. factory (typing.Callable): A callable that takes no parameters whose result is used if `None` is passed. Raises: TypeError: If **neither** *default* or *factory* is passed. TypeError: If **both** *default* and *factory* are passed. ValueError: If an instance of `attrs.Factory` is passed with ``takes_self=True``. .. versionadded:: 18.2.0 """ if default is NOTHING and factory is None: msg = "Must pass either `default` or `factory`." raise TypeError(msg) if default is not NOTHING and factory is not None: msg = "Must pass either `default` or `factory` but not both." raise TypeError(msg) if factory is not None: default = Factory(factory) if isinstance(default, Factory): if default.takes_self: msg = "`takes_self` is not supported by default_if_none." raise ValueError(msg) def default_if_none_converter(val): if val is not None: return val return default.factory() else: def default_if_none_converter(val): if val is not None: return val return default return default_if_none_converter def to_bool(val): """ Convert "boolean" strings (for example, from environment variables) to real booleans. Values mapping to `True`: - ``True`` - ``"true"`` / ``"t"`` - ``"yes"`` / ``"y"`` - ``"on"`` - ``"1"`` - ``1`` Values mapping to `False`: - ``False`` - ``"false"`` / ``"f"`` - ``"no"`` / ``"n"`` - ``"off"`` - ``"0"`` - ``0`` Raises: ValueError: For any other value. .. versionadded:: 21.3.0 """ if isinstance(val, str): val = val.lower() if val in (True, "true", "t", "yes", "y", "on", "1", 1): return True if val in (False, "false", "f", "no", "n", "off", "0", 0): return False msg = f"Cannot convert value to bool: {val!r}" raise ValueError(msg) python-attrs-attrs-bd2446d/src/attr/converters.pyi000066400000000000000000000012031476453530700224240ustar00rootroot00000000000000from typing import Callable, Any, overload from attrs import _ConverterType, _CallableConverterType @overload def pipe(*validators: _CallableConverterType) -> _CallableConverterType: ... @overload def pipe(*validators: _ConverterType) -> _ConverterType: ... @overload def optional(converter: _CallableConverterType) -> _CallableConverterType: ... @overload def optional(converter: _ConverterType) -> _ConverterType: ... @overload def default_if_none(default: Any) -> _CallableConverterType: ... @overload def default_if_none( *, factory: Callable[[], Any] ) -> _CallableConverterType: ... def to_bool(val: str | int | bool) -> bool: ... python-attrs-attrs-bd2446d/src/attr/exceptions.py000066400000000000000000000036711476453530700222550ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from __future__ import annotations from typing import ClassVar class FrozenError(AttributeError): """ A frozen/immutable instance or attribute have been attempted to be modified. It mirrors the behavior of ``namedtuples`` by using the same error message and subclassing `AttributeError`. .. versionadded:: 20.1.0 """ msg = "can't set attribute" args: ClassVar[tuple[str]] = [msg] class FrozenInstanceError(FrozenError): """ A frozen instance has been attempted to be modified. .. versionadded:: 16.1.0 """ class FrozenAttributeError(FrozenError): """ A frozen attribute has been attempted to be modified. .. versionadded:: 20.1.0 """ class AttrsAttributeNotFoundError(ValueError): """ An *attrs* function couldn't find an attribute that the user asked for. .. versionadded:: 16.2.0 """ class NotAnAttrsClassError(ValueError): """ A non-*attrs* class has been passed into an *attrs* function. .. versionadded:: 16.2.0 """ class DefaultAlreadySetError(RuntimeError): """ A default has been set when defining the field and is attempted to be reset using the decorator. .. versionadded:: 17.1.0 """ class UnannotatedAttributeError(RuntimeError): """ A class with ``auto_attribs=True`` has a field without a type annotation. .. versionadded:: 17.3.0 """ class PythonTooOldError(RuntimeError): """ It was attempted to use an *attrs* feature that requires a newer Python version. .. versionadded:: 18.2.0 """ class NotCallableError(TypeError): """ A field requiring a callable has been set with a value that is not callable. .. versionadded:: 19.2.0 """ def __init__(self, msg, value): super(TypeError, self).__init__(msg, value) self.msg = msg self.value = value def __str__(self): return str(self.msg) python-attrs-attrs-bd2446d/src/attr/exceptions.pyi000066400000000000000000000010331476453530700224140ustar00rootroot00000000000000from typing import Any class FrozenError(AttributeError): msg: str = ... class FrozenInstanceError(FrozenError): ... class FrozenAttributeError(FrozenError): ... class AttrsAttributeNotFoundError(ValueError): ... class NotAnAttrsClassError(ValueError): ... class DefaultAlreadySetError(RuntimeError): ... class UnannotatedAttributeError(RuntimeError): ... class PythonTooOldError(RuntimeError): ... class NotCallableError(TypeError): msg: str = ... value: Any = ... def __init__(self, msg: str, value: Any) -> None: ... python-attrs-attrs-bd2446d/src/attr/filters.py000066400000000000000000000034031476453530700215350ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Commonly useful filters for `attrs.asdict` and `attrs.astuple`. """ from ._make import Attribute def _split_what(what): """ Returns a tuple of `frozenset`s of classes and attributes. """ return ( frozenset(cls for cls in what if isinstance(cls, type)), frozenset(cls for cls in what if isinstance(cls, str)), frozenset(cls for cls in what if isinstance(cls, Attribute)), ) def include(*what): """ Create a filter that only allows *what*. Args: what (list[type, str, attrs.Attribute]): What to include. Can be a type, a name, or an attribute. Returns: Callable: A callable that can be passed to `attrs.asdict`'s and `attrs.astuple`'s *filter* argument. .. versionchanged:: 23.1.0 Accept strings with field names. """ cls, names, attrs = _split_what(what) def include_(attribute, value): return ( value.__class__ in cls or attribute.name in names or attribute in attrs ) return include_ def exclude(*what): """ Create a filter that does **not** allow *what*. Args: what (list[type, str, attrs.Attribute]): What to exclude. Can be a type, a name, or an attribute. Returns: Callable: A callable that can be passed to `attrs.asdict`'s and `attrs.astuple`'s *filter* argument. .. versionchanged:: 23.3.0 Accept field name string as input argument """ cls, names, attrs = _split_what(what) def exclude_(attribute, value): return not ( value.__class__ in cls or attribute.name in names or attribute in attrs ) return exclude_ python-attrs-attrs-bd2446d/src/attr/filters.pyi000066400000000000000000000003201476453530700217010ustar00rootroot00000000000000from typing import Any from . import Attribute, _FilterType def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... python-attrs-attrs-bd2446d/src/attr/py.typed000066400000000000000000000000001476453530700212000ustar00rootroot00000000000000python-attrs-attrs-bd2446d/src/attr/setters.py000066400000000000000000000031211476453530700215530ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Commonly used hooks for on_setattr. """ from . import _config from .exceptions import FrozenAttributeError def pipe(*setters): """ Run all *setters* and return the return value of the last one. .. versionadded:: 20.1.0 """ def wrapped_pipe(instance, attrib, new_value): rv = new_value for setter in setters: rv = setter(instance, attrib, rv) return rv return wrapped_pipe def frozen(_, __, ___): """ Prevent an attribute to be modified. .. versionadded:: 20.1.0 """ raise FrozenAttributeError def validate(instance, attrib, new_value): """ Run *attrib*'s validator on *new_value* if it has one. .. versionadded:: 20.1.0 """ if _config._run_validators is False: return new_value v = attrib.validator if not v: return new_value v(instance, attrib, new_value) return new_value def convert(instance, attrib, new_value): """ Run *attrib*'s converter -- if it has one -- on *new_value* and return the result. .. versionadded:: 20.1.0 """ c = attrib.converter if c: # This can be removed once we drop 3.8 and use attrs.Converter instead. from ._make import Converter if not isinstance(c, Converter): return c(new_value) return c(new_value, instance, attrib) return new_value # Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. # Sphinx's autodata stopped working, so the docstring is inlined in the API # docs. NO_OP = object() python-attrs-attrs-bd2446d/src/attr/setters.pyi000066400000000000000000000011101476453530700217200ustar00rootroot00000000000000from typing import Any, NewType, NoReturn, TypeVar from . import Attribute from attrs import _OnSetAttrType _T = TypeVar("_T") def frozen( instance: Any, attribute: Attribute[Any], new_value: Any ) -> NoReturn: ... def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... # convert is allowed to return Any, because they can be chained using pipe. def convert( instance: Any, attribute: Attribute[Any], new_value: Any ) -> Any: ... _NoOpType = NewType("_NoOpType", object) NO_OP: _NoOpType python-attrs-attrs-bd2446d/src/attr/validators.py000066400000000000000000000471161476453530700222460ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Commonly useful validators. """ import operator import re from contextlib import contextmanager from re import Pattern from ._config import get_run_validators, set_run_validators from ._make import _AndValidator, and_, attrib, attrs from .converters import default_if_none from .exceptions import NotCallableError __all__ = [ "and_", "deep_iterable", "deep_mapping", "disabled", "ge", "get_disabled", "gt", "in_", "instance_of", "is_callable", "le", "lt", "matches_re", "max_len", "min_len", "not_", "optional", "or_", "set_disabled", ] def set_disabled(disabled): """ Globally disable or enable running validators. By default, they are run. Args: disabled (bool): If `True`, disable running all validators. .. warning:: This function is not thread-safe! .. versionadded:: 21.3.0 """ set_run_validators(not disabled) def get_disabled(): """ Return a bool indicating whether validators are currently disabled or not. Returns: bool:`True` if validators are currently disabled. .. versionadded:: 21.3.0 """ return not get_run_validators() @contextmanager def disabled(): """ Context manager that disables running validators within its context. .. warning:: This context manager is not thread-safe! .. versionadded:: 21.3.0 """ set_run_validators(False) try: yield finally: set_run_validators(True) @attrs(repr=False, slots=True, unsafe_hash=True) class _InstanceOfValidator: type = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not isinstance(value, self.type): msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." raise TypeError( msg, attr, self.type, value, ) def __repr__(self): return f"" def instance_of(type): """ A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using `isinstance` therefore it's also valid to pass a tuple of types). Args: type (type | tuple[type]): The type to check for. Raises: TypeError: With a human readable error message, the attribute (of type `attrs.Attribute`), the expected type, and the value it got. """ return _InstanceOfValidator(type) @attrs(repr=False, frozen=True, slots=True) class _MatchesReValidator: pattern = attrib() match_func = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not self.match_func(value): msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" raise ValueError( msg, attr, self.pattern, value, ) def __repr__(self): return f"" def matches_re(regex, flags=0, func=None): r""" A validator that raises `ValueError` if the initializer is called with a string that doesn't match *regex*. Args: regex (str, re.Pattern): A regex string or precompiled pattern to match against flags (int): Flags that will be passed to the underlying re function (default 0) func (typing.Callable): Which underlying `re` function to call. Valid options are `re.fullmatch`, `re.search`, and `re.match`; the default `None` means `re.fullmatch`. For performance reasons, the pattern is always precompiled using `re.compile`. .. versionadded:: 19.2.0 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. """ valid_funcs = (re.fullmatch, None, re.search, re.match) if func not in valid_funcs: msg = "'func' must be one of {}.".format( ", ".join( sorted((e and e.__name__) or "None" for e in set(valid_funcs)) ) ) raise ValueError(msg) if isinstance(regex, Pattern): if flags: msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" raise TypeError(msg) pattern = regex else: pattern = re.compile(regex, flags) if func is re.match: match_func = pattern.match elif func is re.search: match_func = pattern.search else: match_func = pattern.fullmatch return _MatchesReValidator(pattern, match_func) @attrs(repr=False, slots=True, unsafe_hash=True) class _OptionalValidator: validator = attrib() def __call__(self, inst, attr, value): if value is None: return self.validator(inst, attr, value) def __repr__(self): return f"" def optional(validator): """ A validator that makes an attribute optional. An optional attribute is one which can be set to `None` in addition to satisfying the requirements of the sub-validator. Args: validator (typing.Callable | tuple[typing.Callable] | list[typing.Callable]): A validator (or validators) that is used for non-`None` values. .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. """ if isinstance(validator, (list, tuple)): return _OptionalValidator(_AndValidator(validator)) return _OptionalValidator(validator) @attrs(repr=False, slots=True, unsafe_hash=True) class _InValidator: options = attrib() _original_options = attrib(hash=False) def __call__(self, inst, attr, value): try: in_options = value in self.options except TypeError: # e.g. `1 in "abc"` in_options = False if not in_options: msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})" raise ValueError( msg, attr, self._original_options, value, ) def __repr__(self): return f"" def in_(options): """ A validator that raises a `ValueError` if the initializer is called with a value that does not belong in the *options* provided. The check is performed using ``value in options``, so *options* has to support that operation. To keep the validator hashable, dicts, lists, and sets are transparently transformed into a `tuple`. Args: options: Allowed options. Raises: ValueError: With a human readable error message, the attribute (of type `attrs.Attribute`), the expected options, and the value it got. .. versionadded:: 17.1.0 .. versionchanged:: 22.1.0 The ValueError was incomplete until now and only contained the human readable error message. Now it contains all the information that has been promised since 17.1.0. .. versionchanged:: 24.1.0 *options* that are a list, dict, or a set are now transformed into a tuple to keep the validator hashable. """ repr_options = options if isinstance(options, (list, dict, set)): options = tuple(options) return _InValidator(options, repr_options) @attrs(repr=False, slots=False, unsafe_hash=True) class _IsCallableValidator: def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not callable(value): message = ( "'{name}' must be callable " "(got {value!r} that is a {actual!r})." ) raise NotCallableError( msg=message.format( name=attr.name, value=value, actual=value.__class__ ), value=value, ) def __repr__(self): return "" def is_callable(): """ A validator that raises a `attrs.exceptions.NotCallableError` if the initializer is called with a value for this particular attribute that is not callable. .. versionadded:: 19.1.0 Raises: attrs.exceptions.NotCallableError: With a human readable error message containing the attribute (`attrs.Attribute`) name, and the value it got. """ return _IsCallableValidator() @attrs(repr=False, slots=True, unsafe_hash=True) class _DeepIterable: member_validator = attrib(validator=is_callable()) iterable_validator = attrib( default=None, validator=optional(is_callable()) ) def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if self.iterable_validator is not None: self.iterable_validator(inst, attr, value) for member in value: self.member_validator(inst, attr, member) def __repr__(self): iterable_identifier = ( "" if self.iterable_validator is None else f" {self.iterable_validator!r}" ) return ( f"" ) def deep_iterable(member_validator, iterable_validator=None): """ A validator that performs deep validation of an iterable. Args: member_validator: Validator to apply to iterable members. iterable_validator: Validator to apply to iterable itself (optional). Raises TypeError: if any sub-validators fail .. versionadded:: 19.1.0 """ if isinstance(member_validator, (list, tuple)): member_validator = and_(*member_validator) return _DeepIterable(member_validator, iterable_validator) @attrs(repr=False, slots=True, unsafe_hash=True) class _DeepMapping: key_validator = attrib(validator=is_callable()) value_validator = attrib(validator=is_callable()) mapping_validator = attrib(default=None, validator=optional(is_callable())) def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if self.mapping_validator is not None: self.mapping_validator(inst, attr, value) for key in value: self.key_validator(inst, attr, key) self.value_validator(inst, attr, value[key]) def __repr__(self): return f"" def deep_mapping(key_validator, value_validator, mapping_validator=None): """ A validator that performs deep validation of a dictionary. Args: key_validator: Validator to apply to dictionary keys. value_validator: Validator to apply to dictionary values. mapping_validator: Validator to apply to top-level mapping attribute (optional). .. versionadded:: 19.1.0 Raises: TypeError: if any sub-validators fail """ return _DeepMapping(key_validator, value_validator, mapping_validator) @attrs(repr=False, frozen=True, slots=True) class _NumberValidator: bound = attrib() compare_op = attrib() compare_func = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not self.compare_func(value, self.bound): msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" raise ValueError(msg) def __repr__(self): return f"" def lt(val): """ A validator that raises `ValueError` if the initializer is called with a number larger or equal to *val*. The validator uses `operator.lt` to compare the values. Args: val: Exclusive upper bound for values. .. versionadded:: 21.3.0 """ return _NumberValidator(val, "<", operator.lt) def le(val): """ A validator that raises `ValueError` if the initializer is called with a number greater than *val*. The validator uses `operator.le` to compare the values. Args: val: Inclusive upper bound for values. .. versionadded:: 21.3.0 """ return _NumberValidator(val, "<=", operator.le) def ge(val): """ A validator that raises `ValueError` if the initializer is called with a number smaller than *val*. The validator uses `operator.ge` to compare the values. Args: val: Inclusive lower bound for values .. versionadded:: 21.3.0 """ return _NumberValidator(val, ">=", operator.ge) def gt(val): """ A validator that raises `ValueError` if the initializer is called with a number smaller or equal to *val*. The validator uses `operator.ge` to compare the values. Args: val: Exclusive lower bound for values .. versionadded:: 21.3.0 """ return _NumberValidator(val, ">", operator.gt) @attrs(repr=False, frozen=True, slots=True) class _MaxLengthValidator: max_length = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if len(value) > self.max_length: msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" raise ValueError(msg) def __repr__(self): return f"" def max_len(length): """ A validator that raises `ValueError` if the initializer is called with a string or iterable that is longer than *length*. Args: length (int): Maximum length of the string or iterable .. versionadded:: 21.3.0 """ return _MaxLengthValidator(length) @attrs(repr=False, frozen=True, slots=True) class _MinLengthValidator: min_length = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if len(value) < self.min_length: msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" raise ValueError(msg) def __repr__(self): return f"" def min_len(length): """ A validator that raises `ValueError` if the initializer is called with a string or iterable that is shorter than *length*. Args: length (int): Minimum length of the string or iterable .. versionadded:: 22.1.0 """ return _MinLengthValidator(length) @attrs(repr=False, slots=True, unsafe_hash=True) class _SubclassOfValidator: type = attrib() def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. """ if not issubclass(value, self.type): msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." raise TypeError( msg, attr, self.type, value, ) def __repr__(self): return f"" def _subclass_of(type): """ A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using `issubclass` therefore it's also valid to pass a tuple of types). Args: type (type | tuple[type, ...]): The type(s) to check for. Raises: TypeError: With a human readable error message, the attribute (of type `attrs.Attribute`), the expected type, and the value it got. """ return _SubclassOfValidator(type) @attrs(repr=False, slots=True, unsafe_hash=True) class _NotValidator: validator = attrib() msg = attrib( converter=default_if_none( "not_ validator child '{validator!r}' " "did not raise a captured error" ) ) exc_types = attrib( validator=deep_iterable( member_validator=_subclass_of(Exception), iterable_validator=instance_of(tuple), ), ) def __call__(self, inst, attr, value): try: self.validator(inst, attr, value) except self.exc_types: pass # suppress error to invert validity else: raise ValueError( self.msg.format( validator=self.validator, exc_types=self.exc_types, ), attr, self.validator, value, self.exc_types, ) def __repr__(self): return f"" def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): """ A validator that wraps and logically 'inverts' the validator passed to it. It will raise a `ValueError` if the provided validator *doesn't* raise a `ValueError` or `TypeError` (by default), and will suppress the exception if the provided validator *does*. Intended to be used with existing validators to compose logic without needing to create inverted variants, for example, ``not_(in_(...))``. Args: validator: A validator to be logically inverted. msg (str): Message to raise if validator fails. Formatted with keys ``exc_types`` and ``validator``. exc_types (tuple[type, ...]): Exception type(s) to capture. Other types raised by child validators will not be intercepted and pass through. Raises: ValueError: With a human readable error message, the attribute (of type `attrs.Attribute`), the validator that failed to raise an exception, the value it got, and the expected exception types. .. versionadded:: 22.2.0 """ try: exc_types = tuple(exc_types) except TypeError: exc_types = (exc_types,) return _NotValidator(validator, msg, exc_types) @attrs(repr=False, slots=True, unsafe_hash=True) class _OrValidator: validators = attrib() def __call__(self, inst, attr, value): for v in self.validators: try: v(inst, attr, value) except Exception: # noqa: BLE001, PERF203, S112 continue else: return msg = f"None of {self.validators!r} satisfied for value {value!r}" raise ValueError(msg) def __repr__(self): return f"" def or_(*validators): """ A validator that composes multiple validators into one. When called on a value, it runs all wrapped validators until one of them is satisfied. Args: validators (~collections.abc.Iterable[typing.Callable]): Arbitrary number of validators. Raises: ValueError: If no validator is satisfied. Raised with a human-readable error message listing all the wrapped validators and the value that failed all of them. .. versionadded:: 24.1.0 """ vals = [] for v in validators: vals.extend(v.validators if isinstance(v, _OrValidator) else [v]) return _OrValidator(tuple(vals)) python-attrs-attrs-bd2446d/src/attr/validators.pyi000066400000000000000000000050531476453530700224110ustar00rootroot00000000000000from types import UnionType from typing import ( Any, AnyStr, Callable, Container, ContextManager, Iterable, Mapping, Match, Pattern, TypeVar, overload, ) from attrs import _ValidatorType from attrs import _ValidatorArgType _T = TypeVar("_T") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") _I = TypeVar("_I", bound=Iterable) _K = TypeVar("_K") _V = TypeVar("_V") _M = TypeVar("_M", bound=Mapping) def set_disabled(run: bool) -> None: ... def get_disabled() -> bool: ... def disabled() -> ContextManager[None]: ... # To be more precise on instance_of use some overloads. # If there are more than 3 items in the tuple then we fall back to Any @overload def instance_of(type: type[_T]) -> _ValidatorType[_T]: ... @overload def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ... @overload def instance_of( type: tuple[type[_T1], type[_T2]], ) -> _ValidatorType[_T1 | _T2]: ... @overload def instance_of( type: tuple[type[_T1], type[_T2], type[_T3]], ) -> _ValidatorType[_T1 | _T2 | _T3]: ... @overload def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ... @overload def instance_of(type: UnionType) -> _ValidatorType[Any]: ... def optional( validator: ( _ValidatorType[_T] | list[_ValidatorType[_T]] | tuple[_ValidatorType[_T]] ), ) -> _ValidatorType[_T | None]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... def matches_re( regex: Pattern[AnyStr] | AnyStr, flags: int = ..., func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ..., ) -> _ValidatorType[AnyStr]: ... def deep_iterable( member_validator: _ValidatorArgType[_T], iterable_validator: _ValidatorType[_I] | None = ..., ) -> _ValidatorType[_I]: ... def deep_mapping( key_validator: _ValidatorType[_K], value_validator: _ValidatorType[_V], mapping_validator: _ValidatorType[_M] | None = ..., ) -> _ValidatorType[_M]: ... def is_callable() -> _ValidatorType[_T]: ... def lt(val: _T) -> _ValidatorType[_T]: ... def le(val: _T) -> _ValidatorType[_T]: ... def ge(val: _T) -> _ValidatorType[_T]: ... def gt(val: _T) -> _ValidatorType[_T]: ... def max_len(length: int) -> _ValidatorType[_T]: ... def min_len(length: int) -> _ValidatorType[_T]: ... def not_( validator: _ValidatorType[_T], *, msg: str | None = None, exc_types: type[Exception] | Iterable[type[Exception]] = ..., ) -> _ValidatorType[_T]: ... def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... python-attrs-attrs-bd2446d/src/attrs/000077500000000000000000000000001476453530700176765ustar00rootroot00000000000000python-attrs-attrs-bd2446d/src/attrs/__init__.py000066400000000000000000000021231476453530700220050ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr import ( NOTHING, Attribute, AttrsInstance, Converter, Factory, NothingType, _make_getattr, assoc, cmp_using, define, evolve, field, fields, fields_dict, frozen, has, make_class, mutable, resolve_types, validate, ) from attr._next_gen import asdict, astuple from . import converters, exceptions, filters, setters, validators __all__ = [ "NOTHING", "Attribute", "AttrsInstance", "Converter", "Factory", "NothingType", "__author__", "__copyright__", "__description__", "__doc__", "__email__", "__license__", "__title__", "__url__", "__version__", "__version_info__", "asdict", "assoc", "astuple", "cmp_using", "converters", "define", "evolve", "exceptions", "field", "fields", "fields_dict", "filters", "frozen", "has", "make_class", "mutable", "resolve_types", "setters", "validate", "validators", ] __getattr__ = _make_getattr(__name__) python-attrs-attrs-bd2446d/src/attrs/__init__.pyi000066400000000000000000000173731476453530700221730ustar00rootroot00000000000000import sys from typing import ( Any, Callable, Mapping, Sequence, overload, TypeVar, ) # Because we need to type our own stuff, we have to make everything from # attr explicitly public too. from attr import __author__ as __author__ from attr import __copyright__ as __copyright__ from attr import __description__ as __description__ from attr import __email__ as __email__ from attr import __license__ as __license__ from attr import __title__ as __title__ from attr import __url__ as __url__ from attr import __version__ as __version__ from attr import __version_info__ as __version_info__ from attr import assoc as assoc from attr import Attribute as Attribute from attr import AttrsInstance as AttrsInstance from attr import cmp_using as cmp_using from attr import converters as converters from attr import Converter as Converter from attr import evolve as evolve from attr import exceptions as exceptions from attr import Factory as Factory from attr import fields as fields from attr import fields_dict as fields_dict from attr import filters as filters from attr import has as has from attr import make_class as make_class from attr import NOTHING as NOTHING from attr import resolve_types as resolve_types from attr import setters as setters from attr import validate as validate from attr import validators as validators from attr import attrib, asdict as asdict, astuple as astuple from attr import NothingType as NothingType if sys.version_info >= (3, 11): from typing import dataclass_transform else: from typing_extensions import dataclass_transform _T = TypeVar("_T") _C = TypeVar("_C", bound=type) _EqOrderType = bool | Callable[[Any], Any] _ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] _CallableConverterType = Callable[[Any], Any] _ConverterType = _CallableConverterType | Converter[Any, Any] _ReprType = Callable[[Any], str] _ReprArgType = bool | _ReprType _OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] _OnSetAttrArgType = _OnSetAttrType | list[_OnSetAttrType] | setters._NoOpType _FieldTransformer = Callable[ [type, list["Attribute[Any]"]], list["Attribute[Any]"] ] # FIXME: in reality, if multiple validators are passed they must be in a list # or tuple, but those are invariant and so would prevent subtypes of # _ValidatorType from working when passed in a list or tuple. _ValidatorArgType = _ValidatorType[_T] | Sequence[_ValidatorType[_T]] @overload def field( *, default: None = ..., validator: None = ..., repr: _ReprArgType = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., converter: None = ..., factory: None = ..., kw_only: bool = ..., eq: bool | None = ..., order: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., type: type | None = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the # other arguments. @overload def field( *, default: None = ..., validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., type: type | None = ..., ) -> _T: ... # This form catches an explicit default argument. @overload def field( *, default: _T, validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., type: type | None = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @overload def field( *, default: _T | None = ..., validator: _ValidatorArgType[_T] | None = ..., repr: _ReprArgType = ..., hash: bool | None = ..., init: bool = ..., metadata: Mapping[Any, Any] | None = ..., converter: _ConverterType | list[_ConverterType] | tuple[_ConverterType] | None = ..., factory: Callable[[], _T] | None = ..., kw_only: bool = ..., eq: _EqOrderType | None = ..., order: _EqOrderType | None = ..., on_setattr: _OnSetAttrArgType | None = ..., alias: str | None = ..., type: type | None = ..., ) -> Any: ... @overload @dataclass_transform(field_specifiers=(attrib, field)) def define( maybe_cls: _C, *, these: dict[str, Any] | None = ..., repr: bool = ..., unsafe_hash: bool | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: bool | None = ..., order: bool | None = ..., auto_detect: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., ) -> _C: ... @overload @dataclass_transform(field_specifiers=(attrib, field)) def define( maybe_cls: None = ..., *, these: dict[str, Any] | None = ..., repr: bool = ..., unsafe_hash: bool | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: bool | None = ..., order: bool | None = ..., auto_detect: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., ) -> Callable[[_C], _C]: ... mutable = define @overload @dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) def frozen( maybe_cls: _C, *, these: dict[str, Any] | None = ..., repr: bool = ..., unsafe_hash: bool | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: bool | None = ..., order: bool | None = ..., auto_detect: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., ) -> _C: ... @overload @dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) def frozen( maybe_cls: None = ..., *, these: dict[str, Any] | None = ..., repr: bool = ..., unsafe_hash: bool | None = ..., hash: bool | None = ..., init: bool = ..., slots: bool = ..., frozen: bool = ..., weakref_slot: bool = ..., str: bool = ..., auto_attribs: bool = ..., kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., eq: bool | None = ..., order: bool | None = ..., auto_detect: bool = ..., getstate_setstate: bool | None = ..., on_setattr: _OnSetAttrArgType | None = ..., field_transformer: _FieldTransformer | None = ..., match_args: bool = ..., ) -> Callable[[_C], _C]: ... python-attrs-attrs-bd2446d/src/attrs/converters.py000066400000000000000000000001141476453530700224360ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr.converters import * # noqa: F403 python-attrs-attrs-bd2446d/src/attrs/exceptions.py000066400000000000000000000001141476453530700224250ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr.exceptions import * # noqa: F403 python-attrs-attrs-bd2446d/src/attrs/filters.py000066400000000000000000000001111476453530700217110ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr.filters import * # noqa: F403 python-attrs-attrs-bd2446d/src/attrs/py.typed000066400000000000000000000000001476453530700213630ustar00rootroot00000000000000python-attrs-attrs-bd2446d/src/attrs/setters.py000066400000000000000000000001111476453530700217320ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr.setters import * # noqa: F403 python-attrs-attrs-bd2446d/src/attrs/validators.py000066400000000000000000000001141476453530700224140ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr.validators import * # noqa: F403 python-attrs-attrs-bd2446d/tests/000077500000000000000000000000001476453530700171145ustar00rootroot00000000000000python-attrs-attrs-bd2446d/tests/__init__.py000066400000000000000000000000371476453530700212250ustar00rootroot00000000000000# SPDX-License-Identifier: MIT python-attrs-attrs-bd2446d/tests/attr_import_star.py000066400000000000000000000003711476453530700230640ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from attr import * # noqa: F403 # This is imported by test_import::test_from_attr_import_star; this must # be done indirectly because importing * is only allowed on module level, # so can't be done inside a test. python-attrs-attrs-bd2446d/tests/dataclass_transform_example.py000066400000000000000000000014641476453530700252400ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import attr import attrs @attr.define() class Define: a: str b: int reveal_type(Define.__init__) # noqa: F821 @attr.define() class DefineConverter: with_converter: int = attr.field(converter=int) reveal_type(DefineConverter.__init__) # noqa: F821 DefineConverter(with_converter=b"42") @attr.frozen() class Frozen: a: str d = Frozen("a") d.a = "new" reveal_type(d.a) # noqa: F821 @attr.define(frozen=True) class FrozenDefine: a: str d2 = FrozenDefine("a") d2.a = "new" reveal_type(d2.a) # noqa: F821 # Field-aliasing works @attrs.define class AliasedField: _a: int = attrs.field(alias="_a") af = AliasedField(42) reveal_type(af.__init__) # noqa: F821 # unsafe_hash is accepted @attrs.define(unsafe_hash=True) class Hashable: pass python-attrs-attrs-bd2446d/tests/strategies.py000066400000000000000000000140411476453530700216400ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Testing strategies for Hypothesis-based tests. """ import functools import keyword import string from collections import OrderedDict from hypothesis import strategies as st import attr from .utils import make_class optional_bool = st.one_of(st.none(), st.booleans()) def gen_attr_names(): """ Generate names for attributes, 'a'...'z', then 'aa'...'zz'. ~702 different attribute names should be enough in practice. Some short strings (such as 'as') are keywords, so we skip them. """ lc = string.ascii_lowercase yield from lc for outer in lc: for inner in lc: res = outer + inner if keyword.iskeyword(res): continue yield outer + inner def maybe_underscore_prefix(source): """ A generator to sometimes prepend an underscore. """ to_underscore = False for val in source: yield val if not to_underscore else "_" + val to_underscore = not to_underscore @st.composite def _create_hyp_nested_strategy(draw, simple_class_strategy): """ Create a recursive attrs class. Given a strategy for building (simpler) classes, create and return a strategy for building classes that have as an attribute: either just the simpler class, a list of simpler classes, a tuple of simpler classes, an ordered dict or a dict mapping the string "cls" to a simpler class. """ cls = draw(simple_class_strategy) factories = [ cls, lambda: [cls()], lambda: (cls(),), lambda: {"cls": cls()}, lambda: OrderedDict([("cls", cls())]), ] factory = draw(st.sampled_from(factories)) attrs = [*draw(list_of_attrs), attr.ib(default=attr.Factory(factory))] return make_class("HypClass", dict(zip(gen_attr_names(), attrs))) bare_attrs = st.builds(attr.ib, default=st.none()) int_attrs = st.integers().map(lambda i: attr.ib(default=i)) str_attrs = st.text().map(lambda s: attr.ib(default=s)) float_attrs = st.floats(allow_nan=False).map(lambda f: attr.ib(default=f)) dict_attrs = st.dictionaries(keys=st.text(), values=st.integers()).map( lambda d: attr.ib(default=d) ) simple_attrs_without_metadata = ( bare_attrs | int_attrs | str_attrs | float_attrs | dict_attrs ) @st.composite def simple_attrs_with_metadata(draw): """ Create a simple attribute with arbitrary metadata. """ c_attr = draw(simple_attrs) keys = st.booleans() | st.binary() | st.integers() | st.text() vals = st.booleans() | st.binary() | st.integers() | st.text() metadata = draw( st.dictionaries(keys=keys, values=vals, min_size=1, max_size=3) ) return attr.ib( default=c_attr._default, validator=c_attr._validator, repr=c_attr.repr, eq=c_attr.eq, order=c_attr.order, hash=c_attr.hash, init=c_attr.init, metadata=metadata, type=None, converter=c_attr.converter, ) simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata() # Python functions support up to 255 arguments. list_of_attrs = st.lists(simple_attrs, max_size=3) @st.composite def simple_classes( draw, slots=None, frozen=None, weakref_slot=None, private_attrs=None, cached_property=None, ): """ A strategy that generates classes with default non-attr attributes. For example, this strategy might generate a class such as: @attr.s(slots=True, frozen=True, weakref_slot=True) class HypClass: a = attr.ib(default=1) _b = attr.ib(default=None) c = attr.ib(default='text') _d = attr.ib(default=1.0) c = attr.ib(default={'t': 1}) By default, all combinations of slots, frozen, and weakref_slot classes will be generated. If `slots=True` is passed in, only slotted classes will be generated, and if `slots=False` is passed in, no slotted classes will be generated. The same applies to `frozen` and `weakref_slot`. By default, some attributes will be private (those prefixed with an underscore). If `private_attrs=True` is passed in, all attributes will be private, and if `private_attrs=False`, no attributes will be private. """ attrs = draw(list_of_attrs) frozen_flag = draw(st.booleans()) slots_flag = draw(st.booleans()) weakref_flag = draw(st.booleans()) if private_attrs is None: attr_names = maybe_underscore_prefix(gen_attr_names()) elif private_attrs is True: attr_names = ("_" + n for n in gen_attr_names()) elif private_attrs is False: attr_names = gen_attr_names() cls_dict = dict(zip(attr_names, attrs)) pre_init_flag = draw(st.booleans()) post_init_flag = draw(st.booleans()) init_flag = draw(st.booleans()) cached_property_flag = draw(st.booleans()) if pre_init_flag: def pre_init(self): pass cls_dict["__attrs_pre_init__"] = pre_init if post_init_flag: def post_init(self): pass cls_dict["__attrs_post_init__"] = post_init if not init_flag: def init(self, *args, **kwargs): self.__attrs_init__(*args, **kwargs) cls_dict["__init__"] = init bases = (object,) if cached_property or (cached_property is None and cached_property_flag): class BaseWithCachedProperty: @functools.cached_property def _cached_property(self) -> int: return 1 bases = (BaseWithCachedProperty,) return make_class( "HypClass", cls_dict, bases=bases, slots=slots_flag if slots is None else slots, frozen=frozen_flag if frozen is None else frozen, weakref_slot=weakref_flag if weakref_slot is None else weakref_slot, init=init_flag, ) # st.recursive works by taking a base strategy (in this case, simple_classes) # and a special function. This function receives a strategy, and returns # another strategy (building on top of the base strategy). nested_classes = st.recursive( simple_classes(), _create_hyp_nested_strategy, max_leaves=3 ) python-attrs-attrs-bd2446d/tests/test_3rd_party.py000066400000000000000000000012661476453530700224410ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for compatibility against other Python modules. """ import pytest from hypothesis import given from .strategies import simple_classes cloudpickle = pytest.importorskip("cloudpickle") class TestCloudpickleCompat: """ Tests for compatibility with ``cloudpickle``. """ @given(simple_classes(cached_property=False)) def test_repr(self, cls): """ attrs instances can be pickled and un-pickled with cloudpickle. """ inst = cls() # Exact values aren't a concern so long as neither direction # raises an exception. pkl = cloudpickle.dumps(inst) cloudpickle.loads(pkl) python-attrs-attrs-bd2446d/tests/test_abc.py000066400000000000000000000031461476453530700212560ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import abc import inspect import pytest import attrs from attr._compat import PY_3_10_PLUS, PY_3_12_PLUS @pytest.mark.skipif( not PY_3_10_PLUS, reason="abc.update_abstractmethods is 3.10+" ) class TestUpdateAbstractMethods: def test_abc_implementation(self, slots): """ If an attrs class implements an abstract method, it stops being abstract. """ class Ordered(abc.ABC): @abc.abstractmethod def __lt__(self, other): pass @abc.abstractmethod def __le__(self, other): pass @attrs.define(order=True, slots=slots) class Concrete(Ordered): x: int assert not inspect.isabstract(Concrete) assert Concrete(2) > Concrete(1) def test_remain_abstract(self, slots): """ If an attrs class inherits from an abstract class but doesn't implement abstract methods, it remains abstract. """ class A(abc.ABC): @abc.abstractmethod def foo(self): pass @attrs.define(slots=slots) class StillAbstract(A): pass assert inspect.isabstract(StillAbstract) expected_exception_message = ( "^Can't instantiate abstract class StillAbstract without an " "implementation for abstract method 'foo'$" if PY_3_12_PLUS else "class StillAbstract with abstract method foo" ) with pytest.raises(TypeError, match=expected_exception_message): StillAbstract() python-attrs-attrs-bd2446d/tests/test_annotations.py000066400000000000000000000440401476453530700230640ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for PEP-526 type annotations. """ import sys import types import typing import pytest import attr import attrs from attr._compat import PY_3_14_PLUS from attr._make import _is_class_var from attr.exceptions import UnannotatedAttributeError def assert_init_annotations(cls, **annotations): """ Assert cls.__init__ has the correct annotations. """ __tracebackhide__ = True annotations["return"] = type(None) assert annotations == typing.get_type_hints(cls.__init__) class TestAnnotations: """ Tests for types derived from variable annotations (PEP-526). """ def test_basic_annotations(self): """ Sets the `Attribute.type` attr from basic type annotations. """ @attr.resolve_types @attr.s class C: x: int = attr.ib() y = attr.ib(type=str) z = attr.ib() assert int is attr.fields(C).x.type assert str is attr.fields(C).y.type assert None is attr.fields(C).z.type assert_init_annotations(C, x=int, y=str) def test_catches_basic_type_conflict(self): """ Raises ValueError if type is specified both ways. """ with pytest.raises(ValueError) as e: @attr.s class C: x: int = attr.ib(type=int) assert ( "Type annotation and type argument cannot both be present for 'x'.", ) == e.value.args def test_typing_annotations(self): """ Sets the `Attribute.type` attr from typing annotations. """ @attr.resolve_types @attr.s class C: x: typing.List[int] = attr.ib() y = attr.ib(type=typing.Optional[str]) assert typing.List[int] is attr.fields(C).x.type assert typing.Optional[str] is attr.fields(C).y.type assert_init_annotations(C, x=typing.List[int], y=typing.Optional[str]) def test_only_attrs_annotations_collected(self): """ Annotations that aren't set to an attr.ib are ignored. """ @attr.resolve_types @attr.s class C: x: typing.List[int] = attr.ib() y: int assert 1 == len(attr.fields(C)) assert_init_annotations(C, x=typing.List[int]) @pytest.mark.skipif( sys.version_info[:2] < (3, 11), reason="Incompatible behavior on older Pythons", ) def test_auto_attribs(self, slots): """ If *auto_attribs* is True, bare annotations are collected too. Defaults work and class variables are ignored. """ @attr.s(auto_attribs=True, slots=slots) class C: cls_var: typing.ClassVar[int] = 23 a: int x: typing.List[int] = attrs.Factory(list) y: int = 2 z: int = attr.ib(default=3) foo: typing.Any = None i = C(42) assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i) attr_names = {a.name for a in C.__attrs_attrs__} assert "a" in attr_names # just double check that the set works assert "cls_var" not in attr_names attr.resolve_types(C) assert int is attr.fields(C).a.type assert attr.Factory(list) == attr.fields(C).x.default assert typing.List[int] is attr.fields(C).x.type assert int is attr.fields(C).y.type assert 2 == attr.fields(C).y.default assert int is attr.fields(C).z.type assert typing.Any == attr.fields(C).foo.type # Class body is clean. if slots is False: with pytest.raises(AttributeError): C.y assert 2 == i.y else: assert isinstance(C.y, types.MemberDescriptorType) i.y = 23 assert 23 == i.y assert_init_annotations( C, a=int, x=typing.List[int], y=int, z=int, foo=typing.Any, ) def test_auto_attribs_unannotated(self, slots): """ Unannotated `attr.ib`s raise an error. """ with pytest.raises(UnannotatedAttributeError) as e: @attr.s(slots=slots, auto_attribs=True) class C: v = attr.ib() x: int y = attr.ib() z: str assert ( "The following `attr.ib`s lack a type annotation: v, y.", ) == e.value.args def test_auto_attribs_subclassing(self, slots): """ Attributes from base classes are inherited, it doesn't matter if the subclass has annotations or not. Ref #291 """ @attr.resolve_types @attr.s(slots=slots, auto_attribs=True) class A: a: int = 1 @attr.resolve_types @attr.s(slots=slots, auto_attribs=True) class B(A): b: int = 2 @attr.resolve_types @attr.s(slots=slots, auto_attribs=True) class C(A): pass assert "B(a=1, b=2)" == repr(B()) assert "C(a=1)" == repr(C()) assert_init_annotations(A, a=int) assert_init_annotations(B, a=int, b=int) assert_init_annotations(C, a=int) def test_converter_annotations(self): """ An unannotated attribute with an annotated converter gets its annotation from the converter. """ def int2str(x: int) -> str: return str(x) @attr.s class A: a = attr.ib(converter=int2str) assert_init_annotations(A, a=int) def int2str_(x: int, y: str = ""): return str(x) @attr.s class A: a = attr.ib(converter=int2str_) assert_init_annotations(A, a=int) def test_converter_attrib_annotations(self): """ If a converter is provided, an explicit type annotation has no effect on an attribute's type annotation. """ def int2str(x: int) -> str: return str(x) @attr.s class A: a: str = attr.ib(converter=int2str) b = attr.ib(converter=int2str, type=str) assert_init_annotations(A, a=int, b=int) def test_non_introspectable_converter(self): """ A non-introspectable converter doesn't cause a crash. """ @attr.s class A: a = attr.ib(converter=print) def test_nullary_converter(self): """ A converter with no arguments doesn't cause a crash. """ def noop(): pass @attr.s class A: a = attr.ib(converter=noop) assert A.__init__.__annotations__ == {"return": None} def test_pipe(self): """ pipe() uses the input annotation of its first argument and the output annotation of its last argument. """ def int2str(x: int) -> str: return str(x) def strlen(y: str) -> int: return len(y) def identity(z): return z assert attr.converters.pipe(int2str).__annotations__ == { "val": int, "return": str, } assert attr.converters.pipe(int2str, strlen).__annotations__ == { "val": int, "return": int, } assert attr.converters.pipe(identity, strlen).__annotations__ == { "return": int } assert attr.converters.pipe(int2str, identity).__annotations__ == { "val": int } def int2str_(x: int, y: int = 0) -> str: return str(x) assert attr.converters.pipe(int2str_).__annotations__ == { "val": int, "return": str, } def test_pipe_empty(self): """ pipe() with no converters is annotated like the identity. """ p = attr.converters.pipe() assert "val" in p.__annotations__ t = p.__annotations__["val"] assert isinstance(t, typing.TypeVar) assert p.__annotations__ == {"val": t, "return": t} def test_pipe_non_introspectable(self): """ pipe() doesn't crash when passed a non-introspectable converter. """ assert attr.converters.pipe(print).__annotations__ == {} def test_pipe_nullary(self): """ pipe() doesn't crash when passed a nullary converter. """ def noop(): pass assert attr.converters.pipe(noop).__annotations__ == {} def test_optional(self): """ optional() uses the annotations of the converter it wraps. """ def int2str(x: int) -> str: return str(x) def int_identity(x: int): return x def strify(x) -> str: return str(x) def identity(x): return x assert attr.converters.optional(int2str).__annotations__ == { "val": typing.Optional[int], "return": typing.Optional[str], } assert attr.converters.optional(int_identity).__annotations__ == { "val": typing.Optional[int] } assert attr.converters.optional(strify).__annotations__ == { "return": typing.Optional[str] } assert attr.converters.optional(identity).__annotations__ == {} def int2str_(x: int, y: int = 0) -> str: return str(x) assert attr.converters.optional(int2str_).__annotations__ == { "val": typing.Optional[int], "return": typing.Optional[str], } def test_optional_non_introspectable(self): """ optional() doesn't crash when passed a non-introspectable converter. """ assert attr.converters.optional(print).__annotations__ == {} def test_optional_nullary(self): """ optional() doesn't crash when passed a nullary converter. """ def noop(): pass assert attr.converters.optional(noop).__annotations__ == {} @pytest.mark.skipif( sys.version_info[:2] < (3, 11), reason="Incompatible behavior on older Pythons", ) def test_annotations_strings(self, slots): """ String annotations are passed into __init__ as is. The strings keep changing between releases. """ import typing as t from typing import ClassVar @attr.s(auto_attribs=True, slots=slots) class C: cls_var1: "typing.ClassVar[int]" = 23 cls_var2: "ClassVar[int]" = 23 cls_var3: "t.ClassVar[int]" = 23 a: "int" x: "typing.List[int]" = attrs.Factory(list) y: "int" = 2 z: "int" = attr.ib(default=3) foo: "typing.Any" = None attr.resolve_types(C, locals(), globals()) assert_init_annotations( C, a=int, x=typing.List[int], y=int, z=int, foo=typing.Any, ) def test_typing_extensions_classvar(self, slots): """ If ClassVar is coming from typing_extensions, it is recognized too. """ @attr.s(auto_attribs=True, slots=slots) class C: cls_var: "typing_extensions.ClassVar" = 23 # noqa: F821 assert_init_annotations(C) def test_keyword_only_auto_attribs(self): """ `kw_only` propagates to attributes defined via `auto_attribs`. """ @attr.s(auto_attribs=True, kw_only=True) class C: x: int y: int with pytest.raises(TypeError): C(0, 1) with pytest.raises(TypeError): C(x=0) c = C(x=0, y=1) assert c.x == 0 assert c.y == 1 def test_base_class_variable(self): """ Base class' class variables can be overridden with an attribute without resorting to using an explicit `attr.ib()`. """ class Base: x: int = 42 @attr.s(auto_attribs=True) class C(Base): x: int assert 1 == C(1).x def test_removes_none_too(self): """ Regression test for #523: make sure defaults that are set to None are removed too. """ @attr.s(auto_attribs=True) class C: x: int = 42 y: typing.Any = None with pytest.raises(AttributeError): C.x with pytest.raises(AttributeError): C.y def test_non_comparable_defaults(self): """ Regression test for #585: objects that are not directly comparable (for example numpy arrays) would cause a crash when used as default values of an attrs auto-attrib class. """ class NonComparable: def __eq__(self, other): raise ValueError @attr.s(auto_attribs=True) class C: x: typing.Any = NonComparable() # noqa: RUF009 def test_basic_resolve(self): """ Resolve the `Attribute.type` attr from basic type annotations. Unannotated types are ignored. """ @attr.s class C: x: "int" = attr.ib() y = attr.ib(type=str) z = attr.ib() attr.resolve_types(C) assert int is attr.fields(C).x.type assert str is attr.fields(C).y.type assert None is attr.fields(C).z.type @pytest.mark.skipif( sys.version_info[:2] < (3, 9), reason="Incompatible behavior on older Pythons", ) def test_extra_resolve(self): """ `get_type_hints` returns extra type hints. """ from typing import Annotated globals = {"Annotated": Annotated} @attr.define class C: x: 'Annotated[float, "test"]' attr.resolve_types(C, globals) assert Annotated[float, "test"] is attr.fields(C).x.type @attr.define class D: x: 'Annotated[float, "test"]' attr.resolve_types(D, globals, include_extras=False) assert float is attr.fields(D).x.type def test_resolve_types_auto_attrib(self, slots): """ Types can be resolved even when strings are involved. """ @attr.s(slots=slots, auto_attribs=True) class A: a: typing.List[int] b: typing.List["int"] c: "typing.List[int]" # Note: I don't have to pass globals and locals here because # int is a builtin and will be available in any scope. attr.resolve_types(A) assert typing.List[int] == attr.fields(A).a.type assert typing.List[int] == attr.fields(A).b.type assert typing.List[int] == attr.fields(A).c.type def test_resolve_types_decorator(self, slots): """ Types can be resolved using it as a decorator. """ @attr.resolve_types @attr.s(slots=slots, auto_attribs=True) class A: a: typing.List[int] b: typing.List["int"] c: "typing.List[int]" assert typing.List[int] == attr.fields(A).a.type assert typing.List[int] == attr.fields(A).b.type assert typing.List[int] == attr.fields(A).c.type def test_self_reference(self, slots): """ References to self class using quotes can be resolved. """ if PY_3_14_PLUS and not slots: pytest.xfail("References are changing a lot in 3.14.") @attr.s(slots=slots, auto_attribs=True) class A: a: "A" b: typing.Optional["A"] # will resolve below -- noqa: F821 attr.resolve_types(A, globals(), locals()) assert A == attr.fields(A).a.type assert typing.Optional[A] == attr.fields(A).b.type def test_forward_reference(self, slots): """ Forward references can be resolved. """ if PY_3_14_PLUS and not slots: pytest.xfail("Forward references are changing a lot in 3.14.") @attr.s(slots=slots, auto_attribs=True) class A: a: typing.List["B"] # will resolve below -- noqa: F821 @attr.s(slots=slots, auto_attribs=True) class B: a: A attr.resolve_types(A, globals(), locals()) attr.resolve_types(B, globals(), locals()) assert typing.List[B] == attr.fields(A).a.type assert A == attr.fields(B).a.type assert typing.List[B] == attr.fields(A).a.type assert A == attr.fields(B).a.type def test_init_type_hints(self): """ Forward references in __init__ can be automatically resolved. """ @attr.s class C: x = attr.ib(type="typing.List[int]") assert_init_annotations(C, x=typing.List[int]) def test_init_type_hints_fake_module(self): """ If you somehow set the __module__ to something that doesn't exist you'll lose __init__ resolution. """ class C: x = attr.ib(type="typing.List[int]") C.__module__ = "totally fake" C = attr.s(C) with pytest.raises(NameError): typing.get_type_hints(C.__init__) def test_inheritance(self): """ Subclasses can be resolved after the parent is resolved. """ @attr.define() class A: n: "int" @attr.define() class B(A): pass attr.resolve_types(A) attr.resolve_types(B) assert int is attr.fields(A).n.type assert int is attr.fields(B).n.type def test_resolve_twice(self): """ You can call resolve_types as many times as you like. This test is here mostly for coverage. """ @attr.define() class A: n: "int" attr.resolve_types(A) assert int is attr.fields(A).n.type attr.resolve_types(A) assert int is attr.fields(A).n.type @pytest.mark.parametrize( "annot", [ typing.ClassVar, "typing.ClassVar", "'typing.ClassVar[dict]'", "t.ClassVar[int]", ], ) def test_is_class_var(annot): """ ClassVars are detected, even if they're a string or quoted. """ assert _is_class_var(annot) python-attrs-attrs-bd2446d/tests/test_cmp.py000066400000000000000000000354571476453530700213220ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for methods from `attrib._cmp`. """ import pytest from attr._cmp import cmp_using from attr._compat import PY_3_13_PLUS # Test parameters. EqCSameType = cmp_using(eq=lambda a, b: a == b, class_name="EqCSameType") PartialOrderCSameType = cmp_using( eq=lambda a, b: a == b, lt=lambda a, b: a < b, class_name="PartialOrderCSameType", ) FullOrderCSameType = cmp_using( eq=lambda a, b: a == b, lt=lambda a, b: a < b, le=lambda a, b: a <= b, gt=lambda a, b: a > b, ge=lambda a, b: a >= b, class_name="FullOrderCSameType", ) EqCAnyType = cmp_using( eq=lambda a, b: a == b, require_same_type=False, class_name="EqCAnyType" ) PartialOrderCAnyType = cmp_using( eq=lambda a, b: a == b, lt=lambda a, b: a < b, require_same_type=False, class_name="PartialOrderCAnyType", ) eq_data = [ (EqCSameType, True), (EqCAnyType, False), ] order_data = [ (PartialOrderCSameType, True), (PartialOrderCAnyType, False), (FullOrderCSameType, True), ] eq_ids = [c[0].__name__ for c in eq_data] order_ids = [c[0].__name__ for c in order_data] cmp_data = eq_data + order_data cmp_ids = eq_ids + order_ids # Compiler strips indents from docstrings in Python 3.13+ indent = "" if PY_3_13_PLUS else " " * 8 class TestEqOrder: """ Tests for eq and order related methods. """ ######### # eq ######### @pytest.mark.parametrize( ("cls", "requires_same_type"), cmp_data, ids=cmp_ids ) def test_equal_same_type(self, cls, requires_same_type): """ Equal objects are detected as equal. """ assert cls(1) == cls(1) assert not (cls(1) != cls(1)) @pytest.mark.parametrize( ("cls", "requires_same_type"), cmp_data, ids=cmp_ids ) def test_unequal_same_type(self, cls, requires_same_type): """ Unequal objects of correct type are detected as unequal. """ assert cls(1) != cls(2) assert not (cls(1) == cls(2)) @pytest.mark.parametrize( ("cls", "requires_same_type"), cmp_data, ids=cmp_ids ) def test_equal_different_type(self, cls, requires_same_type): """ Equal values of different types are detected appropriately. """ assert (cls(1) == cls(1.0)) == (not requires_same_type) assert not (cls(1) != cls(1.0)) == (not requires_same_type) ######### # lt ######### @pytest.mark.parametrize( ("cls", "requires_same_type"), eq_data, ids=eq_ids ) def test_lt_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __lt__. """ with pytest.raises(TypeError): cls(1) < cls(2) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_lt_same_type(self, cls, requires_same_type): """ Less-than objects are detected appropriately. """ assert cls(1) < cls(2) assert not (cls(2) < cls(1)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_lt_same_type(self, cls, requires_same_type): """ Not less-than objects are detected appropriately. """ assert cls(2) >= cls(1) assert not (cls(1) >= cls(2)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_lt_different_type(self, cls, requires_same_type): """ Less-than values of different types are detected appropriately. """ if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __lt__. with pytest.raises(TypeError): cls(1) < cls(2.0) else: assert cls(1) < cls(2.0) assert not (cls(2) < cls(1.0)) ######### # le ######### @pytest.mark.parametrize( ("cls", "requires_same_type"), eq_data, ids=eq_ids ) def test_le_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __le__. """ with pytest.raises(TypeError): cls(1) <= cls(2) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_le_same_type(self, cls, requires_same_type): """ Less-than-or-equal objects are detected appropriately. """ assert cls(1) <= cls(1) assert cls(1) <= cls(2) assert not (cls(2) <= cls(1)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_le_same_type(self, cls, requires_same_type): """ Not less-than-or-equal objects are detected appropriately. """ assert cls(2) > cls(1) assert not (cls(1) > cls(1)) assert not (cls(1) > cls(2)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_le_different_type(self, cls, requires_same_type): """ Less-than-or-equal values of diff. types are detected appropriately. """ if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __le__. with pytest.raises(TypeError): cls(1) <= cls(2.0) else: assert cls(1) <= cls(2.0) assert cls(1) <= cls(1.0) assert not (cls(2) <= cls(1.0)) ######### # gt ######### @pytest.mark.parametrize( ("cls", "requires_same_type"), eq_data, ids=eq_ids ) def test_gt_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __gt__. """ with pytest.raises(TypeError): cls(2) > cls(1) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_gt_same_type(self, cls, requires_same_type): """ Greater-than objects are detected appropriately. """ assert cls(2) > cls(1) assert not (cls(1) > cls(2)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_gt_same_type(self, cls, requires_same_type): """ Not greater-than objects are detected appropriately. """ assert cls(1) <= cls(2) assert not (cls(2) <= cls(1)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_gt_different_type(self, cls, requires_same_type): """ Greater-than values of different types are detected appropriately. """ if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __gt__. with pytest.raises(TypeError): cls(2) > cls(1.0) else: assert cls(2) > cls(1.0) assert not (cls(1) > cls(2.0)) ######### # ge ######### @pytest.mark.parametrize( ("cls", "requires_same_type"), eq_data, ids=eq_ids ) def test_ge_unorderable(self, cls, requires_same_type): """ TypeError is raised if class does not implement __ge__. """ with pytest.raises(TypeError): cls(2) >= cls(1) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_ge_same_type(self, cls, requires_same_type): """ Greater-than-or-equal objects are detected appropriately. """ assert cls(1) >= cls(1) assert cls(2) >= cls(1) assert not (cls(1) >= cls(2)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_not_ge_same_type(self, cls, requires_same_type): """ Not greater-than-or-equal objects are detected appropriately. """ assert cls(1) < cls(2) assert not (cls(1) < cls(1)) assert not (cls(2) < cls(1)) @pytest.mark.parametrize( ("cls", "requires_same_type"), order_data, ids=order_ids ) def test_ge_different_type(self, cls, requires_same_type): """ Greater-than-or-equal values of diff. types are detected appropriately. """ if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __ge__. with pytest.raises(TypeError): cls(2) >= cls(1.0) else: assert cls(2) >= cls(2.0) assert cls(2) >= cls(1.0) assert not (cls(1) >= cls(2.0)) class TestDundersUnnamedClass: """ Tests for dunder attributes of unnamed classes. """ cls = cmp_using(eq=lambda a, b: a == b) def test_class(self): """ Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "Comparable" assert self.cls.__qualname__ == "Comparable" def test_eq(self): """ __eq__ docstring and qualified name should be well behaved. """ method = self.cls.__eq__ assert method.__doc__.strip() == "Return a == b. Computed by attrs." assert method.__name__ == "__eq__" def test_ne(self): """ __ne__ docstring and qualified name should be well behaved. """ method = self.cls.__ne__ assert method.__doc__.strip() == ( "Check equality and either forward a NotImplemented or\n" f"{'' if PY_3_13_PLUS else ' ' * 4}return the result negated." ) assert method.__name__ == "__ne__" class TestTotalOrderingException: """ Test for exceptions related to total ordering. """ def test_eq_must_specified(self): """ `total_ordering` requires `__eq__` to be specified. """ with pytest.raises(ValueError) as ei: cmp_using(lt=lambda a, b: a < b) assert ei.value.args[0] == ( "eq must be define is order to complete ordering from " "lt, le, gt, ge." ) class TestNotImplementedIsPropagated: """ Test related to functions that return NotImplemented. """ def test_not_implemented_is_propagated(self): """ If the comparison function returns NotImplemented, the dunder method should too. """ C = cmp_using(eq=lambda a, b: NotImplemented if a == 1 else a == b) assert C(2) == C(2) assert C(1) != C(1) class TestDundersPartialOrdering: """ Tests for dunder attributes of classes with partial ordering. """ cls = PartialOrderCSameType def test_class(self): """ Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "PartialOrderCSameType" assert self.cls.__qualname__ == "PartialOrderCSameType" def test_eq(self): """ __eq__ docstring and qualified name should be well behaved. """ method = self.cls.__eq__ assert method.__doc__.strip() == "Return a == b. Computed by attrs." assert method.__name__ == "__eq__" def test_ne(self): """ __ne__ docstring and qualified name should be well behaved. """ method = self.cls.__ne__ assert method.__doc__.strip() == ( "Check equality and either forward a NotImplemented or\n" f"{'' if PY_3_13_PLUS else ' ' * 4}return the result negated." ) assert method.__name__ == "__ne__" def test_lt(self): """ __lt__ docstring and qualified name should be well behaved. """ method = self.cls.__lt__ assert method.__doc__.strip() == "Return a < b. Computed by attrs." assert method.__name__ == "__lt__" def test_le(self): """ __le__ docstring and qualified name should be well behaved. """ method = self.cls.__le__ assert method.__doc__.strip().startswith( "Return a <= b. Computed by @total_ordering from" ) assert method.__name__ == "__le__" def test_gt(self): """ __gt__ docstring and qualified name should be well behaved. """ method = self.cls.__gt__ assert method.__doc__.strip().startswith( "Return a > b. Computed by @total_ordering from" ) assert method.__name__ == "__gt__" def test_ge(self): """ __ge__ docstring and qualified name should be well behaved. """ method = self.cls.__ge__ assert method.__doc__.strip().startswith( "Return a >= b. Computed by @total_ordering from" ) assert method.__name__ == "__ge__" class TestDundersFullOrdering: """ Tests for dunder attributes of classes with full ordering. """ cls = FullOrderCSameType def test_class(self): """ Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "FullOrderCSameType" assert self.cls.__qualname__ == "FullOrderCSameType" def test_eq(self): """ __eq__ docstring and qualified name should be well behaved. """ method = self.cls.__eq__ assert method.__doc__.strip() == "Return a == b. Computed by attrs." assert method.__name__ == "__eq__" def test_ne(self): """ __ne__ docstring and qualified name should be well behaved. """ method = self.cls.__ne__ assert method.__doc__.strip() == ( "Check equality and either forward a NotImplemented or\n" f"{'' if PY_3_13_PLUS else ' ' * 4}return the result negated." ) assert method.__name__ == "__ne__" def test_lt(self): """ __lt__ docstring and qualified name should be well behaved. """ method = self.cls.__lt__ assert method.__doc__.strip() == "Return a < b. Computed by attrs." assert method.__name__ == "__lt__" def test_le(self): """ __le__ docstring and qualified name should be well behaved. """ method = self.cls.__le__ assert method.__doc__.strip() == "Return a <= b. Computed by attrs." assert method.__name__ == "__le__" def test_gt(self): """ __gt__ docstring and qualified name should be well behaved. """ method = self.cls.__gt__ assert method.__doc__.strip() == "Return a > b. Computed by attrs." assert method.__name__ == "__gt__" def test_ge(self): """ __ge__ docstring and qualified name should be well behaved. """ method = self.cls.__ge__ assert method.__doc__.strip() == "Return a >= b. Computed by attrs." assert method.__name__ == "__ge__" python-attrs-attrs-bd2446d/tests/test_compat.py000066400000000000000000000031751476453530700220160ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import types from typing import Protocol import pytest import attr @pytest.fixture(name="mp") def _mp(): return types.MappingProxyType({"x": 42, "y": "foo"}) class TestMetadataProxy: """ Ensure properties of metadata proxy independently of hypothesis strategies. """ def test_repr(self, mp): """ repr makes sense and is consistent across Python versions. """ assert any( [ "mappingproxy({'x': 42, 'y': 'foo'})" == repr(mp), "mappingproxy({'y': 'foo', 'x': 42})" == repr(mp), ] ) def test_immutable(self, mp): """ All mutating methods raise errors. """ with pytest.raises(TypeError, match="not support item assignment"): mp["z"] = 23 with pytest.raises(TypeError, match="not support item deletion"): del mp["x"] with pytest.raises(AttributeError, match="no attribute 'update'"): mp.update({}) with pytest.raises(AttributeError, match="no attribute 'clear'"): mp.clear() with pytest.raises(AttributeError, match="no attribute 'pop'"): mp.pop("x") with pytest.raises(AttributeError, match="no attribute 'popitem'"): mp.popitem() with pytest.raises(AttributeError, match="no attribute 'setdefault'"): mp.setdefault("x") def test_attrsinstance_subclass_protocol(): """ It's possible to subclass AttrsInstance and Protocol at once. """ class Foo(attr.AttrsInstance, Protocol): def attribute(self) -> int: ... python-attrs-attrs-bd2446d/tests/test_config.py000066400000000000000000000021421476453530700217710ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr._config`. """ import pytest from attr import _config class TestConfig: def test_default(self): """ Run validators by default. """ assert True is _config._run_validators def test_set_run_validators(self): """ Sets `_run_validators`. """ _config.set_run_validators(False) assert False is _config._run_validators _config.set_run_validators(True) assert True is _config._run_validators def test_get_run_validators(self): """ Returns `_run_validators`. """ _config._run_validators = False assert _config._run_validators is _config.get_run_validators() _config._run_validators = True assert _config._run_validators is _config.get_run_validators() def test_wrong_type(self): """ Passing anything else than a boolean raises TypeError. """ with pytest.raises(TypeError) as e: _config.set_run_validators("False") assert "'run' must be bool." == e.value.args[0] python-attrs-attrs-bd2446d/tests/test_converters.py000066400000000000000000000216051476453530700227230ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr.converters`. """ import pickle import pytest import attr from attr import Converter, Factory, attrib from attr._compat import _AnnotationExtractor from attr.converters import default_if_none, optional, pipe, to_bool class TestConverter: @pytest.mark.parametrize("takes_self", [True, False]) @pytest.mark.parametrize("takes_field", [True, False]) def test_pickle(self, takes_self, takes_field): """ Wrapped converters can be pickled. """ c = Converter(int, takes_self=takes_self, takes_field=takes_field) new_c = pickle.loads(pickle.dumps(c)) assert c == new_c assert takes_self == new_c.takes_self assert takes_field == new_c.takes_field assert c.__call__.__name__ == new_c.__call__.__name__ @pytest.mark.parametrize( "scenario", [ ((False, False), "__attr_converter_le_name(le_value)"), ( (True, True), "__attr_converter_le_name(le_value, self, attr_dict['le_name'])", ), ( (True, False), "__attr_converter_le_name(le_value, self)", ), ( (False, True), "__attr_converter_le_name(le_value, attr_dict['le_name'])", ), ], ) def test_fmt_converter_call(self, scenario): """ _fmt_converter_call determines the arguments to the wrapped converter according to `takes_self` and `takes_field`. """ (takes_self, takes_field), expect = scenario c = Converter(None, takes_self=takes_self, takes_field=takes_field) assert expect == c._fmt_converter_call("le_name", "le_value") def test_works_as_adapter(self): """ Converter instances work as adapters and pass the correct arguments to the wrapped converter callable. """ taken = None instance = object() field = object() def save_args(*args): nonlocal taken taken = args return args[0] Converter(save_args)(42, instance, field) assert (42,) == taken Converter(save_args, takes_self=True)(42, instance, field) assert (42, instance) == taken Converter(save_args, takes_field=True)(42, instance, field) assert (42, field) == taken Converter(save_args, takes_self=True, takes_field=True)( 42, instance, field ) assert (42, instance, field) == taken def test_annotations_if_last_in_pipe(self): """ If the wrapped converter has annotations, they are copied to the Converter __call__. """ def wrapped(_, __, ___) -> float: pass c = Converter(wrapped) assert float is c.__call__.__annotations__["return"] # Doesn't overwrite globally. c2 = Converter(int) assert float is c.__call__.__annotations__["return"] assert None is c2.__call__.__annotations__.get("return") def test_falsey_converter(self): """ Passing a false-y instance still produces a valid converter. """ class MyConv: def __bool__(self): return False def __call__(self, value): return value * 2 @attr.s class C: a = attrib(converter=MyConv()) c = C(21) assert 42 == c.a class TestOptional: """ Tests for `optional`. """ def test_success_with_type(self): """ Wrapped converter is used as usual if value is not None. """ c = optional(int) assert c("42") == 42 def test_success_with_none(self): """ Nothing happens if None. """ c = optional(int) assert c(None) is None def test_fail(self): """ Propagates the underlying conversion error when conversion fails. """ c = optional(int) with pytest.raises(ValueError): c("not_an_int") def test_converter_instance(self): """ Works when passed a Converter instance as argument. """ c = optional(Converter(to_bool)) assert True is c("yes", None, None) class TestDefaultIfNone: def test_missing_default(self): """ Raises TypeError if neither default nor factory have been passed. """ with pytest.raises(TypeError, match="Must pass either"): default_if_none() def test_too_many_defaults(self): """ Raises TypeError if both default and factory are passed. """ with pytest.raises(TypeError, match="but not both"): default_if_none(True, lambda: 42) def test_factory_takes_self(self): """ Raises ValueError if passed Factory has takes_self=True. """ with pytest.raises(ValueError, match="takes_self"): default_if_none(Factory(list, takes_self=True)) @pytest.mark.parametrize("val", [1, 0, True, False, "foo", "", object()]) def test_not_none(self, val): """ If a non-None value is passed, it's handed down. """ c = default_if_none("nope") assert val == c(val) c = default_if_none(factory=list) assert val == c(val) def test_none_value(self): """ Default values are returned when a None is passed. """ c = default_if_none(42) assert 42 == c(None) def test_none_factory(self): """ Factories are used if None is passed. """ c = default_if_none(factory=list) assert [] == c(None) c = default_if_none(default=Factory(list)) assert [] == c(None) class TestPipe: def test_success(self): """ Succeeds if all wrapped converters succeed. """ c = pipe(str, Converter(to_bool), bool) assert ( True is c.converter("True", None, None) is c.converter(True, None, None) ) def test_fail(self): """ Fails if any wrapped converter fails. """ c = pipe(str, to_bool) # First wrapped converter fails: with pytest.raises(ValueError): c(33) # Last wrapped converter fails: with pytest.raises(ValueError): c("33") def test_sugar(self): """ `pipe(c1, c2, c3)` and `[c1, c2, c3]` are equivalent. """ @attr.s class C: a1 = attrib(default="True", converter=pipe(str, to_bool, bool)) a2 = attrib(default=True, converter=[str, to_bool, bool]) c = C() assert True is c.a1 is c.a2 def test_empty(self): """ Empty pipe returns same value. """ o = object() assert o is pipe()(o) def test_wrapped_annotation(self): """ The return type of the wrapped converter is copied into its __call__ and ultimately into pipe's wrapped converter. """ def last(value) -> bool: return bool(value) @attr.s class C: x = attr.ib(converter=[Converter(int), Converter(last)]) i = C(5) assert True is i.x assert ( bool is _AnnotationExtractor( attr.fields(C).x.converter.__call__ ).get_return_type() ) class TestOptionalPipe: def test_optional(self): """ Nothing happens if None. """ c = optional(pipe(str, Converter(to_bool), bool)) assert None is c.converter(None, None, None) def test_pipe(self): """ A value is given, run it through all wrapped converters. """ c = optional(pipe(str, Converter(to_bool), bool)) assert ( True is c.converter("True", None, None) is c.converter(True, None, None) ) def test_instance(self): """ Should work when set as an attrib. """ @attr.s class C: x = attrib( converter=optional(pipe(str, Converter(to_bool), bool)), default=None, ) c1 = C() assert None is c1.x c2 = C("True") assert True is c2.x class TestToBool: def test_unhashable(self): """ Fails if value is unhashable. """ with pytest.raises(ValueError, match="Cannot convert value to bool"): to_bool([]) def test_truthy(self): """ Fails if truthy values are incorrectly converted. """ assert to_bool("t") assert to_bool("yes") assert to_bool("on") def test_falsy(self): """ Fails if falsy values are incorrectly converted. """ assert not to_bool("f") assert not to_bool("no") assert not to_bool("off") python-attrs-attrs-bd2446d/tests/test_dunders.py000066400000000000000000000701061476453530700221750ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for dunder methods from `attrib._make`. """ import copy import inspect import pickle import pytest from hypothesis import given from hypothesis.strategies import booleans import attr from attr._make import ( NOTHING, Factory, _add_repr, _compile_and_eval, _make_init_script, fields, make_class, ) from attr.validators import instance_of from .utils import simple_attr, simple_class EqC = simple_class(eq=True) EqCSlots = simple_class(eq=True, slots=True) OrderC = simple_class(order=True) OrderCSlots = simple_class(order=True, slots=True) ReprC = simple_class(repr=True) ReprCSlots = simple_class(repr=True, slots=True) @attr.s(eq=True) class EqCallableC: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(eq=True, slots=True) class EqCallableCSlots: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(order=True) class OrderCallableC: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) @attr.s(order=True, slots=True) class OrderCallableCSlots: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) # HashC is hashable by explicit definition while HashCSlots is hashable # implicitly. The "Cached" versions are the same, except with hash code # caching enabled HashC = simple_class(unsafe_hash=True) HashCSlots = simple_class(unsafe_hash=None, eq=True, frozen=True, slots=True) HashCCached = simple_class(unsafe_hash=True, cache_hash=True) HashCSlotsCached = simple_class( unsafe_hash=None, eq=True, frozen=True, slots=True, cache_hash=True ) # the cached hash code is stored slightly differently in this case # so it needs to be tested separately HashCFrozenNotSlotsCached = simple_class( frozen=True, slots=False, unsafe_hash=True, cache_hash=True ) def _add_init(cls, frozen): """ Add a __init__ method to *cls*. If *frozen* is True, make it immutable. This function used to be part of _make. It wasn't used anymore however the tests for it are still useful to test the behavior of _make_init. """ has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) script, globs, annots = _make_init_script( cls, cls.__attrs_attrs__, has_pre_init, ( len(inspect.signature(cls.__attrs_pre_init__).parameters) > 1 if has_pre_init else False ), getattr(cls, "__attrs_post_init__", False), frozen, "__slots__" in cls.__dict__, cache_hash=False, base_attr_map={}, is_exc=False, cls_on_setattr=None, attrs_init=False, ) _compile_and_eval(script, globs, filename="__init__") cls.__init__ = globs["__init__"] cls.__init__.__annotations__ = annots return cls class InitC: __attrs_attrs__ = [simple_attr("a"), simple_attr("b")] InitC = _add_init(InitC, False) class TestEqOrder: """ Tests for eq and order related methods. """ @given(booleans()) def test_eq_ignore_attrib(self, slots): """ If `eq` is False for an attribute, ignore that attribute. """ C = make_class( "C", {"a": attr.ib(eq=False), "b": attr.ib()}, slots=slots ) assert C(1, 2) == C(2, 2) @pytest.mark.parametrize("cls", [EqC, EqCSlots]) def test_equal(self, cls): """ Equal objects are detected as equal. """ assert cls(1, 2) == cls(1, 2) assert not (cls(1, 2) != cls(1, 2)) @pytest.mark.parametrize("cls", [EqCallableC, EqCallableCSlots]) def test_equal_callable(self, cls): """ Equal objects are detected as equal. """ assert cls("Test", 1) == cls("test", 1) assert cls("Test", 1) != cls("test", 2) assert not (cls("Test", 1) != cls("test", 1)) assert not (cls("Test", 1) == cls("test", 2)) @pytest.mark.parametrize("cls", [EqC, EqCSlots]) def test_unequal_same_class(self, cls): """ Unequal objects of correct type are detected as unequal. """ assert cls(1, 2) != cls(2, 1) assert not (cls(1, 2) == cls(2, 1)) @pytest.mark.parametrize("cls", [EqCallableC, EqCallableCSlots]) def test_unequal_same_class_callable(self, cls): """ Unequal objects of correct type are detected as unequal. """ assert cls("Test", 1) != cls("foo", 2) assert not (cls("Test", 1) == cls("foo", 2)) @pytest.mark.parametrize( "cls", [EqC, EqCSlots, EqCallableC, EqCallableCSlots] ) def test_unequal_different_class(self, cls): """ Unequal objects of different type are detected even if their attributes match. """ class NotEqC: a = 1 b = 2 assert cls(1, 2) != NotEqC() assert not (cls(1, 2) == NotEqC()) @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) def test_lt(self, cls): """ __lt__ compares objects as tuples of attribute values. """ for a, b in [ ((1, 2), (2, 1)), ((1, 2), (1, 3)), (("a", "b"), ("b", "a")), ]: assert cls(*a) < cls(*b) @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) def test_lt_callable(self, cls): """ __lt__ compares objects as tuples of attribute values. """ # Note: "A" < "a" for a, b in [ (("test1", 1), ("Test1", 2)), (("test0", 1), ("Test1", 1)), ]: assert cls(*a) < cls(*b) @pytest.mark.parametrize( "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] ) def test_lt_unordable(self, cls): """ __lt__ returns NotImplemented if classes differ. """ assert NotImplemented == (cls(1, 2).__lt__(42)) @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) def test_le(self, cls): """ __le__ compares objects as tuples of attribute values. """ for a, b in [ ((1, 2), (2, 1)), ((1, 2), (1, 3)), ((1, 1), (1, 1)), (("a", "b"), ("b", "a")), (("a", "b"), ("a", "b")), ]: assert cls(*a) <= cls(*b) @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) def test_le_callable(self, cls): """ __le__ compares objects as tuples of attribute values. """ # Note: "A" < "a" for a, b in [ (("test1", 1), ("Test1", 1)), (("test1", 1), ("Test1", 2)), (("test0", 1), ("Test1", 1)), (("test0", 2), ("Test1", 1)), ]: assert cls(*a) <= cls(*b) @pytest.mark.parametrize( "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] ) def test_le_unordable(self, cls): """ __le__ returns NotImplemented if classes differ. """ assert NotImplemented == (cls(1, 2).__le__(42)) @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) def test_gt(self, cls): """ __gt__ compares objects as tuples of attribute values. """ for a, b in [ ((2, 1), (1, 2)), ((1, 3), (1, 2)), (("b", "a"), ("a", "b")), ]: assert cls(*a) > cls(*b) @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) def test_gt_callable(self, cls): """ __gt__ compares objects as tuples of attribute values. """ # Note: "A" < "a" for a, b in [ (("Test1", 2), ("test1", 1)), (("Test1", 1), ("test0", 1)), ]: assert cls(*a) > cls(*b) @pytest.mark.parametrize( "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] ) def test_gt_unordable(self, cls): """ __gt__ returns NotImplemented if classes differ. """ assert NotImplemented == (cls(1, 2).__gt__(42)) @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) def test_ge(self, cls): """ __ge__ compares objects as tuples of attribute values. """ for a, b in [ ((2, 1), (1, 2)), ((1, 3), (1, 2)), ((1, 1), (1, 1)), (("b", "a"), ("a", "b")), (("a", "b"), ("a", "b")), ]: assert cls(*a) >= cls(*b) @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) def test_ge_callable(self, cls): """ __ge__ compares objects as tuples of attribute values. """ # Note: "A" < "a" for a, b in [ (("Test1", 1), ("test1", 1)), (("Test1", 2), ("test1", 1)), (("Test1", 1), ("test0", 1)), (("Test1", 1), ("test0", 2)), ]: assert cls(*a) >= cls(*b) @pytest.mark.parametrize( "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] ) def test_ge_unordable(self, cls): """ __ge__ returns NotImplemented if classes differ. """ assert NotImplemented == (cls(1, 2).__ge__(42)) class TestAddRepr: """ Tests for `_add_repr`. """ def test_repr(self, slots): """ If `repr` is False, ignore that attribute. """ C = make_class( "C", {"a": attr.ib(repr=False), "b": attr.ib()}, slots=slots ) assert "C(b=2)" == repr(C(1, 2)) @pytest.mark.parametrize("cls", [ReprC, ReprCSlots]) def test_repr_works(self, cls): """ repr returns a sensible value. """ assert "C(a=1, b=2)" == repr(cls(1, 2)) def test_custom_repr_works(self): """ repr returns a sensible value for attributes with a custom repr callable. """ def custom_repr(value): return "foo:" + str(value) @attr.s class C: a = attr.ib(repr=custom_repr) assert "C(a=foo:1)" == repr(C(1)) def test_infinite_recursion(self): """ In the presence of a cyclic graph, repr will emit an ellipsis and not raise an exception. """ @attr.s class Cycle: value = attr.ib(default=7) cycle = attr.ib(default=None) cycle = Cycle() cycle.cycle = cycle assert "Cycle(value=7, cycle=...)" == repr(cycle) def test_infinite_recursion_long_cycle(self): """ A cyclic graph can pass through other non-attrs objects, and repr will still emit an ellipsis and not raise an exception. """ @attr.s class LongCycle: value = attr.ib(default=14) cycle = attr.ib(default=None) cycle = LongCycle() # Ensure that the reference cycle passes through a non-attrs object. # This demonstrates the need for a thread-local "global" ID tracker. cycle.cycle = {"cycle": [cycle]} assert "LongCycle(value=14, cycle={'cycle': [...]})" == repr(cycle) def test_underscores(self): """ repr does not strip underscores. """ class C: __attrs_attrs__ = [simple_attr("_x")] C = _add_repr(C) i = C() i._x = 42 assert "C(_x=42)" == repr(i) def test_repr_uninitialized_member(self): """ repr signals unset attributes """ C = make_class("C", {"a": attr.ib(init=False)}) assert "C(a=NOTHING)" == repr(C()) @given(add_str=booleans(), slots=booleans()) def test_str(self, add_str, slots): """ If str is True, it returns the same as repr. This only makes sense when subclassing a class with an poor __str__ (like Exceptions). """ @attr.s(str=add_str, slots=slots) class Error(Exception): x = attr.ib() e = Error(42) assert (str(e) == repr(e)) is add_str def test_str_no_repr(self): """ Raises a ValueError if repr=False and str=True. """ with pytest.raises(ValueError) as e: simple_class(repr=False, str=True) assert ( "__str__ can only be generated if a __repr__ exists." ) == e.value.args[0] # these are for use in TestAddHash.test_cache_hash_serialization # they need to be out here so they can be un-pickled @attr.attrs(unsafe_hash=True, cache_hash=False) class HashCacheSerializationTestUncached: foo_value = attr.ib() @attr.attrs(unsafe_hash=True, cache_hash=True) class HashCacheSerializationTestCached: foo_value = attr.ib() @attr.attrs(slots=True, unsafe_hash=True, cache_hash=True) class HashCacheSerializationTestCachedSlots: foo_value = attr.ib() class IncrementingHasher: def __init__(self): self.hash_value = 100 def __hash__(self): rv = self.hash_value self.hash_value += 1 return rv class TestAddHash: """ Tests for `_add_hash`. """ def test_enforces_type(self): """ The `hash` argument to both attrs and attrib must be None, True, or False. """ exc_args = ("Invalid value for hash. Must be True, False, or None.",) with pytest.raises(TypeError) as e: make_class("C", {}, unsafe_hash=1) assert exc_args == e.value.args with pytest.raises(TypeError) as e: make_class("C", {"a": attr.ib(hash=1)}) assert exc_args == e.value.args def test_enforce_no_cache_hash_without_hash(self): """ Ensure exception is thrown if caching the hash code is requested but attrs is not requested to generate `__hash__`. """ exc_args = ( "Invalid value for cache_hash. To use hash caching," " hashing must be either explicitly or implicitly " "enabled.", ) with pytest.raises(TypeError) as e: make_class("C", {}, unsafe_hash=False, cache_hash=True) assert exc_args == e.value.args # unhashable case with pytest.raises(TypeError) as e: make_class( "C", {}, unsafe_hash=None, eq=True, frozen=False, cache_hash=True, ) assert exc_args == e.value.args def test_enforce_no_cached_hash_without_init(self): """ Ensure exception is thrown if caching the hash code is requested but attrs is not requested to generate `__init__`. """ exc_args = ( "Invalid value for cache_hash. To use hash caching," " init must be True.", ) with pytest.raises(TypeError) as e: make_class("C", {}, init=False, unsafe_hash=True, cache_hash=True) assert exc_args == e.value.args @given(booleans(), booleans()) def test_hash_attribute(self, slots, cache_hash): """ If `hash` is False on an attribute, ignore that attribute. """ C = make_class( "C", {"a": attr.ib(hash=False), "b": attr.ib()}, slots=slots, unsafe_hash=True, cache_hash=cache_hash, ) assert hash(C(1, 2)) == hash(C(2, 2)) @given(booleans()) def test_hash_attribute_mirrors_eq(self, eq): """ If `hash` is None, the hash generation mirrors `eq`. """ C = make_class("C", {"a": attr.ib(eq=eq)}, eq=True, frozen=True) if eq: assert C(1) != C(2) assert hash(C(1)) != hash(C(2)) assert hash(C(1)) == hash(C(1)) else: assert C(1) == C(2) assert hash(C(1)) == hash(C(2)) @given(booleans()) def test_hash_mirrors_eq(self, eq): """ If `hash` is None, the hash generation mirrors `eq`. """ C = make_class("C", {"a": attr.ib()}, eq=eq, frozen=True) i = C(1) assert i == i assert hash(i) == hash(i) if eq: assert C(1) == C(1) assert hash(C(1)) == hash(C(1)) else: assert C(1) != C(1) assert hash(C(1)) != hash(C(1)) @pytest.mark.parametrize( "cls", [ HashC, HashCSlots, HashCCached, HashCSlotsCached, HashCFrozenNotSlotsCached, ], ) def test_hash_works(self, cls): """ __hash__ returns different hashes for different values. """ a = cls(1, 2) b = cls(1, 1) assert hash(a) != hash(b) # perform the test again to test the pre-cached path through # __hash__ for the cached-hash versions assert hash(a) != hash(b) def test_hash_default(self): """ Classes are not hashable by default. """ C = make_class("C", {}) with pytest.raises(TypeError) as e: hash(C()) assert e.value.args[0] in ( "'C' objects are unhashable", # PyPy "unhashable type: 'C'", # CPython ) def test_cache_hashing(self): """ Ensure that hash computation if cached if and only if requested """ class HashCounter: """ A class for testing which counts how many times its hash has been requested """ def __init__(self): self.times_hash_called = 0 def __hash__(self): self.times_hash_called += 1 return 12345 Uncached = make_class( "Uncached", {"hash_counter": attr.ib(factory=HashCounter)}, unsafe_hash=True, cache_hash=False, ) Cached = make_class( "Cached", {"hash_counter": attr.ib(factory=HashCounter)}, unsafe_hash=True, cache_hash=True, ) uncached_instance = Uncached() cached_instance = Cached() hash(uncached_instance) hash(uncached_instance) hash(cached_instance) hash(cached_instance) assert 2 == uncached_instance.hash_counter.times_hash_called assert 1 == cached_instance.hash_counter.times_hash_called @pytest.mark.parametrize("cache_hash", [True, False]) def test_copy_hash_cleared(self, cache_hash, frozen, slots): """ Test that the default hash is recalculated after a copy operation. """ kwargs = {"frozen": frozen, "slots": slots, "cache_hash": cache_hash} # Give it an explicit hash if we don't have an implicit one if not frozen: kwargs["unsafe_hash"] = True @attr.s(**kwargs) class C: x = attr.ib() a = C(IncrementingHasher()) # Ensure that any hash cache would be calculated before copy orig_hash = hash(a) b = copy.deepcopy(a) if kwargs["cache_hash"]: # For cache_hash classes, this call is cached assert orig_hash == hash(a) assert orig_hash != hash(b) @pytest.mark.parametrize( ("klass", "cached"), [ (HashCacheSerializationTestUncached, False), (HashCacheSerializationTestCached, True), (HashCacheSerializationTestCachedSlots, True), ], ) def test_cache_hash_serialization_hash_cleared(self, klass, cached): """ Tests that the hash cache is cleared on deserialization to fix https://github.com/python-attrs/attrs/issues/482 . This test is intended to guard against a stale hash code surviving across serialization (which may cause problems when the hash value is different in different interpreters). """ obj = klass(IncrementingHasher()) original_hash = hash(obj) obj_rt = self._roundtrip_pickle(obj) if cached: assert original_hash == hash(obj) assert original_hash != hash(obj_rt) def test_copy_two_arg_reduce(self, frozen): """ If __getstate__ returns None, the tuple returned by object.__reduce__ won't contain the state dictionary; this test ensures that the custom __reduce__ generated when cache_hash=True works in that case. """ @attr.s(frozen=frozen, cache_hash=True, unsafe_hash=True) class C: x = attr.ib() def __getstate__(self): return None # By the nature of this test it doesn't really create an object that's # in a valid state - it basically does the equivalent of # `object.__new__(C)`, so it doesn't make much sense to assert anything # about the result of the copy. This test will just check that it # doesn't raise an *error*. copy.deepcopy(C(1)) def _roundtrip_pickle(self, obj): pickle_str = pickle.dumps(obj) return pickle.loads(pickle_str) class TestAddInit: """ Tests for `_add_init`. """ @given(booleans(), booleans()) def test_init(self, slots, frozen): """ If `init` is False, ignore that attribute. """ C = make_class( "C", {"a": attr.ib(init=False), "b": attr.ib()}, slots=slots, frozen=frozen, ) with pytest.raises(TypeError) as e: C(a=1, b=2) assert e.value.args[0].endswith( "__init__() got an unexpected keyword argument 'a'" ) @given(booleans(), booleans()) def test_no_init_default(self, slots, frozen): """ If `init` is False but a Factory is specified, don't allow passing that argument but initialize it anyway. """ C = make_class( "C", { "_a": attr.ib(init=False, default=42), "_b": attr.ib(init=False, default=Factory(list)), "c": attr.ib(), }, slots=slots, frozen=frozen, ) with pytest.raises(TypeError): C(a=1, c=2) with pytest.raises(TypeError): C(b=1, c=2) i = C(23) assert (42, [], 23) == (i._a, i._b, i.c) @given(booleans(), booleans()) def test_no_init_order(self, slots, frozen): """ If an attribute is `init=False`, it's legal to come after a mandatory attribute. """ make_class( "C", {"a": attr.ib(default=Factory(list)), "b": attr.ib(init=False)}, slots=slots, frozen=frozen, ) def test_sets_attributes(self): """ The attributes are initialized using the passed keywords. """ obj = InitC(a=1, b=2) assert 1 == obj.a assert 2 == obj.b def test_default(self): """ If a default value is present, it's used as fallback. """ class C: __attrs_attrs__ = [ simple_attr(name="a", default=2), simple_attr(name="b", default="hallo"), simple_attr(name="c", default=None), ] C = _add_init(C, False) i = C() assert 2 == i.a assert "hallo" == i.b assert None is i.c def test_factory(self): """ If a default factory is present, it's used as fallback. """ class D: pass class C: __attrs_attrs__ = [ simple_attr(name="a", default=Factory(list)), simple_attr(name="b", default=Factory(D)), ] C = _add_init(C, False) i = C() assert [] == i.a assert isinstance(i.b, D) def test_factory_takes_self(self): """ If takes_self on factories is True, self is passed. """ C = make_class( "C", { "x": attr.ib( default=Factory((lambda self: self), takes_self=True) ) }, ) i = C() assert i is i.x def test_factory_hashable(self): """ Factory is hashable. """ assert hash(Factory(None, False)) == hash(Factory(None, False)) def test_validator(self): """ If a validator is passed, call it with the preliminary instance, the Attribute, and the argument. """ class VException(Exception): pass def raiser(*args): raise VException(*args) C = make_class("C", {"a": attr.ib("a", validator=raiser)}) with pytest.raises(VException) as e: C(42) assert (fields(C).a, 42) == e.value.args[1:] assert isinstance(e.value.args[0], C) def test_validator_slots(self): """ If a validator is passed, call it with the preliminary instance, the Attribute, and the argument. """ class VException(Exception): pass def raiser(*args): raise VException(*args) C = make_class("C", {"a": attr.ib("a", validator=raiser)}, slots=True) with pytest.raises(VException) as e: C(42) assert (fields(C)[0], 42) == e.value.args[1:] assert isinstance(e.value.args[0], C) @given(booleans()) def test_validator_others(self, slots): """ Does not interfere when setting non-attrs attributes. """ C = make_class( "C", {"a": attr.ib("a", validator=instance_of(int))}, slots=slots ) i = C(1) assert 1 == i.a if not slots: i.b = "foo" assert "foo" == i.b else: with pytest.raises(AttributeError): i.b = "foo" def test_underscores(self): """ The argument names in `__init__` are without leading and trailing underscores. """ class C: __attrs_attrs__ = [simple_attr("_private")] C = _add_init(C, False) i = C(private=42) assert 42 == i._private class TestNothing: """ Tests for `NOTHING`. """ def test_copy(self): """ __copy__ returns the same object. """ n = NOTHING assert n is copy.copy(n) def test_deepcopy(self): """ __deepcopy__ returns the same object. """ n = NOTHING assert n is copy.deepcopy(n) def test_eq(self): """ All instances are equal. """ assert NOTHING == NOTHING == NOTHING assert not (NOTHING != NOTHING) assert 1 != NOTHING def test_false(self): """ NOTHING evaluates as falsey. """ assert not NOTHING assert False is bool(NOTHING) @attr.s(unsafe_hash=True, order=True) class C: pass # Store this class so that we recreate it. OriginalC = C @attr.s(unsafe_hash=True, order=True) class C: pass CopyC = C @attr.s(unsafe_hash=True, order=True) class C: """ A different class, to generate different methods. """ a = attr.ib() class TestFilenames: def test_filenames(self): """ The created dunder methods have a "consistent" filename. """ assert ( OriginalC.__init__.__code__.co_filename == "" ) assert ( OriginalC.__eq__.__code__.co_filename == "" ) assert ( OriginalC.__hash__.__code__.co_filename == "" ) assert ( CopyC.__init__.__code__.co_filename == "" ) assert ( CopyC.__eq__.__code__.co_filename == "" ) assert ( CopyC.__hash__.__code__.co_filename == "" ) assert ( C.__init__.__code__.co_filename == "" ) assert ( C.__eq__.__code__.co_filename == "" ) assert ( C.__hash__.__code__.co_filename == "" ) python-attrs-attrs-bd2446d/tests/test_filters.py000066400000000000000000000055201476453530700221770ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr.filters`. """ import pytest import attr from attr import fields from attr.filters import _split_what, exclude, include @attr.s class C: a = attr.ib() b = attr.ib() class TestSplitWhat: """ Tests for `_split_what`. """ def test_splits(self): """ Splits correctly. """ assert ( frozenset((int, str)), frozenset(("abcd", "123")), frozenset((fields(C).a,)), ) == _split_what((str, "123", fields(C).a, int, "abcd")) class TestInclude: """ Tests for `include`. """ @pytest.mark.parametrize( ("incl", "value"), [ ((int,), 42), ((str,), "hello"), ((str, fields(C).a), 42), ((str, fields(C).b), "hello"), (("a",), 42), (("a",), "hello"), (("a", str), 42), (("a", fields(C).b), "hello"), ], ) def test_allow(self, incl, value): """ Return True if a class or attribute is included. """ i = include(*incl) assert i(fields(C).a, value) is True @pytest.mark.parametrize( ("incl", "value"), [ ((str,), 42), ((int,), "hello"), ((str, fields(C).b), 42), ((int, fields(C).b), "hello"), (("b",), 42), (("b",), "hello"), (("b", str), 42), (("b", fields(C).b), "hello"), ], ) def test_drop_class(self, incl, value): """ Return False on non-included classes and attributes. """ i = include(*incl) assert i(fields(C).a, value) is False class TestExclude: """ Tests for `exclude`. """ @pytest.mark.parametrize( ("excl", "value"), [ ((str,), 42), ((int,), "hello"), ((str, fields(C).b), 42), ((int, fields(C).b), "hello"), (("b",), 42), (("b",), "hello"), (("b", str), 42), (("b", fields(C).b), "hello"), ], ) def test_allow(self, excl, value): """ Return True if class or attribute is not excluded. """ e = exclude(*excl) assert e(fields(C).a, value) is True @pytest.mark.parametrize( ("excl", "value"), [ ((int,), 42), ((str,), "hello"), ((str, fields(C).a), 42), ((str, fields(C).b), "hello"), (("a",), 42), (("a",), "hello"), (("a", str), 42), (("a", fields(C).b), "hello"), ], ) def test_drop_class(self, excl, value): """ Return True on non-excluded classes and attributes. """ e = exclude(*excl) assert e(fields(C).a, value) is False python-attrs-attrs-bd2446d/tests/test_funcs.py000066400000000000000000000556001476453530700216510ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr._funcs`. """ import re from collections import OrderedDict from typing import Generic, NamedTuple, TypeVar import pytest from hypothesis import assume, given from hypothesis import strategies as st import attr from attr import asdict, assoc, astuple, evolve, fields, has from attr._compat import Mapping, Sequence from attr.exceptions import AttrsAttributeNotFoundError from attr.validators import instance_of from .strategies import nested_classes, simple_classes MAPPING_TYPES = (dict, OrderedDict) SEQUENCE_TYPES = (list, tuple) @pytest.fixture(scope="session", name="C") def _C(): """ Return a simple but fully featured attrs class with an x and a y attribute. """ import attr @attr.s class C: x = attr.ib() y = attr.ib() return C class TestAsDict: """ Tests for `asdict`. """ @given(st.sampled_from(MAPPING_TYPES)) def test_shallow(self, C, dict_factory): """ Shallow asdict returns correct dict. """ assert {"x": 1, "y": 2} == asdict( C(x=1, y=2), False, dict_factory=dict_factory ) @given(st.sampled_from(MAPPING_TYPES)) def test_recurse(self, C, dict_class): """ Deep asdict returns correct dict. """ assert {"x": {"x": 1, "y": 2}, "y": {"x": 3, "y": 4}} == asdict( C(C(1, 2), C(3, 4)), dict_factory=dict_class ) def test_nested_lists(self, C): """ Test unstructuring deeply nested lists. """ inner = C(1, 2) outer = C([[inner]], None) assert {"x": [[{"x": 1, "y": 2}]], "y": None} == asdict(outer) def test_nested_dicts(self, C): """ Test unstructuring deeply nested dictionaries. """ inner = C(1, 2) outer = C({1: {2: inner}}, None) assert {"x": {1: {2: {"x": 1, "y": 2}}}, "y": None} == asdict(outer) @given(nested_classes, st.sampled_from(MAPPING_TYPES)) def test_recurse_property(self, cls, dict_class): """ Property tests for recursive asdict. """ obj = cls() obj_dict = asdict(obj, dict_factory=dict_class) def assert_proper_dict_class(obj, obj_dict): assert isinstance(obj_dict, dict_class) for field in fields(obj.__class__): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_dict_class(field_val, obj_dict[field.name]) elif isinstance(field_val, Sequence): dict_val = obj_dict[field.name] for item, item_dict in zip(field_val, dict_val): if has(item.__class__): assert_proper_dict_class(item, item_dict) elif isinstance(field_val, Mapping): # This field holds a dictionary. assert isinstance(obj_dict[field.name], dict_class) for key, val in field_val.items(): if has(val.__class__): assert_proper_dict_class( val, obj_dict[field.name][key] ) assert_proper_dict_class(obj, obj_dict) @given(st.sampled_from(MAPPING_TYPES)) def test_filter(self, C, dict_factory): """ Attributes that are supposed to be skipped are skipped. """ assert {"x": {"x": 1}} == asdict( C(C(1, 2), C(3, 4)), filter=lambda a, v: a.name != "y", dict_factory=dict_factory, ) @given(container=st.sampled_from(SEQUENCE_TYPES)) def test_lists_tuples(self, container, C): """ If recurse is True, also recurse into lists. """ assert { "x": 1, "y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"], } == asdict(C(1, container([C(2, 3), C(4, 5), "a"]))) @given(container=st.sampled_from(SEQUENCE_TYPES)) def test_lists_tuples_retain_type(self, container, C): """ If recurse and retain_collection_types are True, also recurse into lists and do not convert them into list. """ assert { "x": 1, "y": container([{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"]), } == asdict( C(1, container([C(2, 3), C(4, 5), "a"])), retain_collection_types=True, ) @given(set_type=st.sampled_from((set, frozenset))) def test_sets_no_retain(self, C, set_type): """ Set types are converted to lists if retain_collection_types=False. """ d = asdict( C(1, set_type((1, 2, 3))), retain_collection_types=False, recurse=True, ) assert {"x": 1, "y": [1, 2, 3]} == d @given(st.sampled_from(MAPPING_TYPES)) def test_dicts(self, C, dict_factory): """ If recurse is True, also recurse into dicts. """ res = asdict(C(1, {"a": C(4, 5)}), dict_factory=dict_factory) assert {"x": 1, "y": {"a": {"x": 4, "y": 5}}} == res assert isinstance(res, dict_factory) @given(simple_classes(private_attrs=False), st.sampled_from(MAPPING_TYPES)) def test_roundtrip(self, cls, dict_class): """ Test dumping to dicts and back for Hypothesis-generated classes. Private attributes don't round-trip (the attribute name is different than the initializer argument). """ instance = cls() dict_instance = asdict(instance, dict_factory=dict_class) assert isinstance(dict_instance, dict_class) roundtrip_instance = cls(**dict_instance) assert instance == roundtrip_instance @given(simple_classes()) def test_asdict_preserve_order(self, cls): """ Field order should be preserved when dumping to an ordered_dict. """ instance = cls() dict_instance = asdict(instance, dict_factory=dict) assert [a.name for a in fields(cls)] == list(dict_instance.keys()) def test_retain_keys_are_tuples(self): """ retain_collect_types also retains keys. """ @attr.s class A: a = attr.ib() instance = A({(1,): 1}) assert {"a": {(1,): 1}} == attr.asdict( instance, retain_collection_types=True ) def test_tuple_keys(self): """ If a key is collection type, retain_collection_types is False, the key is serialized as a tuple. See #646 """ @attr.s class A: a = attr.ib() instance = A({(1,): 1}) assert {"a": {(1,): 1}} == attr.asdict(instance) def test_named_tuple_retain_type(self): """ Namedtuples can be serialized if retain_collection_types is True. See #1164 """ class Coordinates(NamedTuple): lat: float lon: float @attr.s class A: coords: Coordinates = attr.ib() instance = A(Coordinates(50.419019, 30.516225)) assert {"coords": Coordinates(50.419019, 30.516225)} == attr.asdict( instance, retain_collection_types=True ) def test_type_error_with_retain_type(self): """ Serialization that fails with TypeError leaves the error through if they're not tuples. See #1164 """ message = "__new__() missing 1 required positional argument (asdict)" class Coordinates(list): def __init__(self, first, *rest): if isinstance(first, list): raise TypeError(message) super().__init__([first, *rest]) @attr.s class A: coords: Coordinates = attr.ib() instance = A(Coordinates(50.419019, 30.516225)) with pytest.raises(TypeError, match=re.escape(message)): attr.asdict(instance, retain_collection_types=True) class TestAsTuple: """ Tests for `astuple`. """ @given(st.sampled_from(SEQUENCE_TYPES)) def test_shallow(self, C, tuple_factory): """ Shallow astuple returns correct dict. """ assert tuple_factory([1, 2]) == astuple( C(x=1, y=2), False, tuple_factory=tuple_factory ) @given(st.sampled_from(SEQUENCE_TYPES)) def test_recurse(self, C, tuple_factory): """ Deep astuple returns correct tuple. """ assert tuple_factory( [tuple_factory([1, 2]), tuple_factory([3, 4])] ) == astuple(C(C(1, 2), C(3, 4)), tuple_factory=tuple_factory) @given(nested_classes, st.sampled_from(SEQUENCE_TYPES)) def test_recurse_property(self, cls, tuple_class): """ Property tests for recursive astuple. """ obj = cls() obj_tuple = astuple(obj, tuple_factory=tuple_class) def assert_proper_tuple_class(obj, obj_tuple): assert isinstance(obj_tuple, tuple_class) for index, field in enumerate(fields(obj.__class__)): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_tuple_class(field_val, obj_tuple[index]) assert_proper_tuple_class(obj, obj_tuple) @given(nested_classes, st.sampled_from(SEQUENCE_TYPES)) def test_recurse_retain(self, cls, tuple_class): """ Property tests for asserting collection types are retained. """ obj = cls() obj_tuple = astuple( obj, tuple_factory=tuple_class, retain_collection_types=True ) def assert_proper_col_class(obj, obj_tuple): # Iterate over all attributes, and if they are lists or mappings # in the original, assert they are the same class in the dumped. for index, field in enumerate(fields(obj.__class__)): field_val = getattr(obj, field.name) if has(field_val.__class__): # This field holds a class, recurse the assertions. assert_proper_col_class(field_val, obj_tuple[index]) elif isinstance(field_val, (list, tuple)): # This field holds a sequence of something. expected_type = type(obj_tuple[index]) assert type(field_val) is expected_type for obj_e, obj_tuple_e in zip(field_val, obj_tuple[index]): if has(obj_e.__class__): assert_proper_col_class(obj_e, obj_tuple_e) elif isinstance(field_val, dict): orig = field_val tupled = obj_tuple[index] assert type(orig) is type(tupled) for obj_e, obj_tuple_e in zip( orig.items(), tupled.items() ): if has(obj_e[0].__class__): # Dict key assert_proper_col_class(obj_e[0], obj_tuple_e[0]) if has(obj_e[1].__class__): # Dict value assert_proper_col_class(obj_e[1], obj_tuple_e[1]) assert_proper_col_class(obj, obj_tuple) @given(st.sampled_from(SEQUENCE_TYPES)) def test_filter(self, C, tuple_factory): """ Attributes that are supposed to be skipped are skipped. """ assert tuple_factory([tuple_factory([1])]) == astuple( C(C(1, 2), C(3, 4)), filter=lambda a, v: a.name != "y", tuple_factory=tuple_factory, ) @given(container=st.sampled_from(SEQUENCE_TYPES)) def test_lists_tuples(self, container, C): """ If recurse is True, also recurse into lists. """ assert (1, [(2, 3), (4, 5), "a"]) == astuple( C(1, container([C(2, 3), C(4, 5), "a"])) ) @given(st.sampled_from(SEQUENCE_TYPES)) def test_dicts(self, C, tuple_factory): """ If recurse is True, also recurse into dicts. """ res = astuple(C(1, {"a": C(4, 5)}), tuple_factory=tuple_factory) assert tuple_factory([1, {"a": tuple_factory([4, 5])}]) == res assert isinstance(res, tuple_factory) @given(container=st.sampled_from(SEQUENCE_TYPES)) def test_lists_tuples_retain_type(self, container, C): """ If recurse and retain_collection_types are True, also recurse into lists and do not convert them into list. """ assert (1, container([(2, 3), (4, 5), "a"])) == astuple( C(1, container([C(2, 3), C(4, 5), "a"])), retain_collection_types=True, ) @given(container=st.sampled_from(MAPPING_TYPES)) def test_dicts_retain_type(self, container, C): """ If recurse and retain_collection_types are True, also recurse into lists and do not convert them into list. """ assert (1, container({"a": (4, 5)})) == astuple( C(1, container({"a": C(4, 5)})), retain_collection_types=True ) @given(simple_classes(), st.sampled_from(SEQUENCE_TYPES)) def test_roundtrip(self, cls, tuple_class): """ Test dumping to tuple and back for Hypothesis-generated classes. """ instance = cls() tuple_instance = astuple(instance, tuple_factory=tuple_class) assert isinstance(tuple_instance, tuple_class) roundtrip_instance = cls(*tuple_instance) assert instance == roundtrip_instance @given(set_type=st.sampled_from((set, frozenset))) def test_sets_no_retain(self, C, set_type): """ Set types are converted to lists if retain_collection_types=False. """ d = astuple( C(1, set_type((1, 2, 3))), retain_collection_types=False, recurse=True, ) assert (1, [1, 2, 3]) == d def test_named_tuple_retain_type(self): """ Namedtuples can be serialized if retain_collection_types is True. See #1164 """ class Coordinates(NamedTuple): lat: float lon: float @attr.s class A: coords: Coordinates = attr.ib() instance = A(Coordinates(50.419019, 30.516225)) assert (Coordinates(50.419019, 30.516225),) == attr.astuple( instance, retain_collection_types=True ) def test_type_error_with_retain_type(self): """ Serialization that fails with TypeError leaves the error through if they're not tuples. See #1164 """ message = "__new__() missing 1 required positional argument (astuple)" class Coordinates(list): def __init__(self, first, *rest): if isinstance(first, list): raise TypeError(message) super().__init__([first, *rest]) @attr.s class A: coords: Coordinates = attr.ib() instance = A(Coordinates(50.419019, 30.516225)) with pytest.raises(TypeError, match=re.escape(message)): attr.astuple(instance, retain_collection_types=True) class TestHas: """ Tests for `has`. """ def test_positive(self, C): """ Returns `True` on decorated classes. """ assert has(C) def test_positive_empty(self): """ Returns `True` on decorated classes even if there are no attributes. """ @attr.s class D: pass assert has(D) def test_negative(self): """ Returns `False` on non-decorated classes. """ assert not has(object) def test_generics(self): """ Works with generic classes. """ T = TypeVar("T") @attr.define class A(Generic[T]): a: T assert has(A) assert has(A[str]) # Verify twice, since there's caching going on. assert has(A[str]) def test_generics_negative(self): """ Returns `False` on non-decorated generic classes. """ T = TypeVar("T") class A(Generic[T]): a: T assert not has(A) assert not has(A[str]) # Verify twice, since there's caching going on. assert not has(A[str]) class TestAssoc: """ Tests for `assoc`. """ @given(slots=st.booleans(), frozen=st.booleans()) def test_empty(self, slots, frozen): """ Empty classes without changes get copied. """ @attr.s(slots=slots, frozen=frozen) class C: pass i1 = C() i2 = assoc(i1) assert i1 is not i2 assert i1 == i2 @given(simple_classes()) def test_no_changes(self, C): """ No changes means a verbatim copy. """ i1 = C() i2 = assoc(i1) assert i1 is not i2 assert i1 == i2 @given(simple_classes(), st.data()) def test_change(self, C, data): """ Changes work. """ # Take the first attribute, and change it. assume(fields(C)) # Skip classes with no attributes. field_names = [a.name for a in fields(C)] original = C() chosen_names = data.draw(st.sets(st.sampled_from(field_names))) change_dict = {name: data.draw(st.integers()) for name in chosen_names} changed = assoc(original, **change_dict) for k, v in change_dict.items(): assert getattr(changed, k) == v @given(simple_classes()) def test_unknown(self, C): """ Wanting to change an unknown attribute raises an AttrsAttributeNotFoundError. """ # No generated class will have a four letter attribute. with pytest.raises(AttrsAttributeNotFoundError) as e: assoc(C(), aaaa=2) assert (f"aaaa is not an attrs attribute on {C!r}.",) == e.value.args def test_frozen(self): """ Works on frozen classes. """ @attr.s(frozen=True) class C: x = attr.ib() y = attr.ib() assert C(3, 2) == assoc(C(1, 2), x=3) class TestEvolve: """ Tests for `evolve`. """ @given(slots=st.booleans(), frozen=st.booleans()) def test_empty(self, slots, frozen): """ Empty classes without changes get copied. """ @attr.s(slots=slots, frozen=frozen) class C: pass i1 = C() i2 = evolve(i1) assert i1 is not i2 assert i1 == i2 @given(simple_classes()) def test_no_changes(self, C): """ No changes means a verbatim copy. """ i1 = C() i2 = evolve(i1) assert i1 is not i2 assert i1 == i2 @given(simple_classes(), st.data()) def test_change(self, C, data): """ Changes work. """ # Take the first attribute, and change it. assume(fields(C)) # Skip classes with no attributes. field_names = [a.name for a in fields(C)] original = C() chosen_names = data.draw(st.sets(st.sampled_from(field_names))) # We pay special attention to private attributes, they should behave # like in `__init__`. change_dict = { name.replace("_", ""): data.draw(st.integers()) for name in chosen_names } changed = evolve(original, **change_dict) for name in chosen_names: assert getattr(changed, name) == change_dict[name.replace("_", "")] @given(simple_classes()) def test_unknown(self, C): """ Wanting to change an unknown attribute raises an AttrsAttributeNotFoundError. """ # No generated class will have a four letter attribute. with pytest.raises(TypeError) as e: evolve(C(), aaaa=2) if hasattr(C, "__attrs_init__"): expected = ( "__attrs_init__() got an unexpected keyword argument 'aaaa'" ) else: expected = "__init__() got an unexpected keyword argument 'aaaa'" assert e.value.args[0].endswith(expected) def test_validator_failure(self): """ TypeError isn't swallowed when validation fails within evolve. """ @attr.s class C: a = attr.ib(validator=instance_of(int)) with pytest.raises(TypeError) as e: evolve(C(a=1), a="some string") m = e.value.args[0] assert m.startswith("'a' must be ") def test_private(self): """ evolve() acts as `__init__` with regards to private attributes. """ @attr.s class C: _a = attr.ib() assert evolve(C(1), a=2)._a == 2 with pytest.raises(TypeError): evolve(C(1), _a=2) with pytest.raises(TypeError): evolve(C(1), a=3, _a=2) def test_non_init_attrs(self): """ evolve() handles `init=False` attributes. """ @attr.s class C: a = attr.ib() b = attr.ib(init=False, default=0) assert evolve(C(1), a=2).a == 2 def test_regression_attrs_classes(self): """ evolve() can evolve fields that are instances of attrs classes. Regression test for #804 """ @attr.s class Cls1: param1 = attr.ib() @attr.s class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") obj2b = Cls2(param2="b") obj1a = Cls1(param1=obj2a) assert Cls1(param1=Cls2(param2="b")) == attr.evolve( obj1a, param1=obj2b ) def test_dicts(self): """ evolve() can replace an attrs class instance with a dict. See #806 """ @attr.s class Cls1: param1 = attr.ib() @attr.s class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") obj2b = {"foo": 42, "param2": 42} obj1a = Cls1(param1=obj2a) assert Cls1({"foo": 42, "param2": 42}) == attr.evolve( obj1a, param1=obj2b ) def test_no_inst(self): """ Missing inst argument raises a TypeError like Python would. """ with pytest.raises( TypeError, match=r"evolve\(\) takes 1 positional argument" ): evolve(x=1) def test_too_many_pos_args(self): """ More than one positional argument raises a TypeError like Python would. """ with pytest.raises( TypeError, match=r"evolve\(\) takes 1 positional argument, but 2 were given", ): evolve(1, 2) def test_can_change_inst(self): """ If the instance is passed by positional argument, a field named `inst` can be changed. """ @attr.define class C: inst: int assert C(42) == evolve(C(23), inst=42) python-attrs-attrs-bd2446d/tests/test_functional.py000066400000000000000000000477011476453530700227000ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ End-to-end tests. """ import copy import inspect import pickle from copy import deepcopy import pytest from hypothesis import given from hypothesis.strategies import booleans import attr from attr._compat import PY_3_13_PLUS from attr._make import NOTHING, Attribute from attr.exceptions import FrozenInstanceError @attr.s class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @attr.s(slots=True) class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() foo = None @attr.s() class C2: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s(slots=True) class C2Slots: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s class Base: x = attr.ib() def meth(self): return self.x @attr.s(slots=True) class BaseSlots: x = attr.ib() def meth(self): return self.x @attr.s class Sub(Base): y = attr.ib() @attr.s(slots=True) class SubSlots(BaseSlots): y = attr.ib() @attr.s(frozen=True, slots=True) class Frozen: x = attr.ib() @attr.s class SubFrozen(Frozen): y = attr.ib() @attr.s(frozen=True, slots=False) class FrozenNoSlots: x = attr.ib() class Meta(type): pass @attr.s class WithMeta(metaclass=Meta): pass @attr.s(slots=True) class WithMetaSlots(metaclass=Meta): pass FromMakeClass = attr.make_class("FromMakeClass", ["x"]) class TestFunctional: """ Functional tests. """ @pytest.mark.parametrize("cls", [C2, C2Slots]) def test_fields(self, cls): """ `attr.fields` works. """ assert ( Attribute( name="x", alias="x", default=foo, validator=None, repr=True, cmp=None, eq=True, order=True, hash=None, init=True, inherited=False, ), Attribute( name="y", alias="y", default=attr.Factory(list), validator=None, repr=True, cmp=None, eq=True, order=True, hash=None, init=True, inherited=False, ), ) == attr.fields(cls) @pytest.mark.parametrize("cls", [C1, C1Slots]) def test_asdict(self, cls): """ `attr.asdict` works. """ assert {"x": 1, "y": 2} == attr.asdict(cls(x=1, y=2)) @pytest.mark.parametrize("cls", [C1, C1Slots]) def test_validator(self, cls): """ `instance_of` raises `TypeError` on type mismatch. """ with pytest.raises(TypeError) as e: cls("1", 2) # Using C1 explicitly, since slotted classes don't support this. assert ( "'x' must be (got '1' that is a ).", attr.fields(C1).x, int, "1", ) == e.value.args @given(booleans()) def test_renaming(self, slots): """ Private members are renamed but only in `__init__`. """ @attr.s(slots=slots) class C3: _x = attr.ib() assert "C3(_x=1)" == repr(C3(x=1)) @given(booleans(), booleans()) def test_programmatic(self, slots, frozen): """ `attr.make_class` works. """ PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen) assert ( Attribute( name="a", alias="a", default=NOTHING, validator=None, repr=True, cmp=None, eq=True, order=True, hash=None, init=True, inherited=False, ), Attribute( name="b", alias="b", default=NOTHING, validator=None, repr=True, cmp=None, eq=True, order=True, hash=None, init=True, inherited=False, ), ) == attr.fields(PC) @pytest.mark.parametrize("cls", [Sub, SubSlots]) def test_subclassing_with_extra_attrs(self, cls): """ Subclassing (where the subclass has extra attrs) does what you'd hope for. """ obj = object() i = cls(x=obj, y=2) assert i.x is i.meth() is obj assert i.y == 2 if cls is Sub: assert f"Sub(x={obj}, y=2)" == repr(i) else: assert f"SubSlots(x={obj}, y=2)" == repr(i) @pytest.mark.parametrize("base", [Base, BaseSlots]) def test_subclass_without_extra_attrs(self, base): """ Subclassing (where the subclass does not have extra attrs) still behaves the same as a subclass with extra attrs. """ class Sub2(base): pass obj = object() i = Sub2(x=obj) assert i.x is i.meth() is obj assert f"Sub2(x={obj})" == repr(i) @pytest.mark.parametrize( "frozen_class", [ Frozen, # has slots=True attr.make_class("FrozenToo", ["x"], slots=False, frozen=True), ], ) def test_frozen_instance(self, frozen_class): """ Frozen instances can't be modified (easily). """ frozen = frozen_class(1) with pytest.raises(FrozenInstanceError) as e: frozen.x = 2 with pytest.raises(FrozenInstanceError) as e: del frozen.x assert e.value.args[0] == "can't set attribute" assert 1 == frozen.x @pytest.mark.parametrize( "cls", [ C1, C1Slots, C2, C2Slots, Base, BaseSlots, Sub, SubSlots, Frozen, FrozenNoSlots, FromMakeClass, ], ) @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1)) def test_pickle_attributes(self, cls, protocol): """ Pickling/un-pickling of Attribute instances works. """ for attribute in attr.fields(cls): assert attribute == pickle.loads(pickle.dumps(attribute, protocol)) @pytest.mark.parametrize( "cls", [ C1, C1Slots, C2, C2Slots, Base, BaseSlots, Sub, SubSlots, Frozen, FrozenNoSlots, FromMakeClass, ], ) @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1)) def test_pickle_object(self, cls, protocol): """ Pickle object serialization works on all kinds of attrs classes. """ obj = cls(123, 456) if len(attr.fields(cls)) == 2 else cls(123) assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol))) def test_subclassing_frozen_gives_frozen(self): """ The frozen-ness of classes is inherited. Subclasses of frozen classes are also frozen and can be instantiated. """ i = SubFrozen("foo", "bar") assert i.x == "foo" assert i.y == "bar" with pytest.raises(FrozenInstanceError): i.x = "baz" @pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots]) def test_metaclass_preserved(self, cls): """ Metaclass data is preserved. """ assert Meta is type(cls) def test_default_decorator(self): """ Default decorator sets the default and the respective method gets called. """ @attr.s class C: x = attr.ib(default=1) y = attr.ib() @y.default def compute(self): return self.x + 1 assert C(1, 2) == C() @pytest.mark.parametrize("weakref_slot", [True, False]) def test_attrib_overwrite(self, slots, frozen, weakref_slot): """ Subclasses can overwrite attributes of their base class. """ @attr.s(slots=slots, frozen=frozen, weakref_slot=weakref_slot) class SubOverwrite(Base): x = attr.ib(default=attr.Factory(list)) assert SubOverwrite([]) == SubOverwrite() def test_dict_patch_class(self): """ dict-classes are never replaced. """ class C: x = attr.ib() C_new = attr.s(C) assert C_new is C def test_hash_by_id(self): """ With dict classes, hashing by ID is active for hash=False. This is incorrect behavior but we have to retain it for backwards-compatibility. """ @attr.s(unsafe_hash=False) class HashByIDBackwardCompat: x = attr.ib() assert hash(HashByIDBackwardCompat(1)) != hash( HashByIDBackwardCompat(1) ) @attr.s(unsafe_hash=False, eq=False) class HashByID: x = attr.ib() assert hash(HashByID(1)) != hash(HashByID(1)) @attr.s(unsafe_hash=True) class HashByValues: x = attr.ib() assert hash(HashByValues(1)) == hash(HashByValues(1)) def test_handles_different_defaults(self): """ Unhashable defaults + subclassing values work. """ @attr.s class Unhashable: pass @attr.s class C: x = attr.ib(default=Unhashable()) @attr.s class D(C): pass def test_unsafe_hash_false_eq_false(self, slots): """ unsafe_hash=False and eq=False make a class hashable by ID. """ @attr.s(unsafe_hash=False, eq=False, slots=slots) class C: pass assert hash(C()) != hash(C()) def test_hash_deprecated(self): """ Using the hash argument is deprecated. """ def test_eq_false(self, slots): """ eq=False makes a class hashable by ID. """ @attr.s(eq=False, slots=slots) class C: pass # Ensure both objects live long enough such that their ids/hashes # can't be recycled. Thanks to Ask Hjorth Larsen for pointing that # out. c1 = C() c2 = C() assert hash(c1) != hash(c2) def test_overwrite_base(self): """ Base classes can overwrite each other and the attributes are added in the order they are defined. """ @attr.s class C: c = attr.ib(default=100) x = attr.ib(default=1) b = attr.ib(default=23) @attr.s class D(C): a = attr.ib(default=42) x = attr.ib(default=2) d = attr.ib(default=3.14) @attr.s class E(D): y = attr.ib(default=3) z = attr.ib(default=4) assert "E(c=100, b=23, a=42, x=2, d=3.14, y=3, z=4)" == repr(E()) @pytest.mark.parametrize("base_slots", [True, False]) @pytest.mark.parametrize("sub_slots", [True, False]) @pytest.mark.parametrize("base_frozen", [True, False]) @pytest.mark.parametrize("sub_frozen", [True, False]) @pytest.mark.parametrize("base_weakref_slot", [True, False]) @pytest.mark.parametrize("sub_weakref_slot", [True, False]) @pytest.mark.parametrize("base_converter", [True, False]) @pytest.mark.parametrize("sub_converter", [True, False]) def test_frozen_slots_combo( self, base_slots, sub_slots, base_frozen, sub_frozen, base_weakref_slot, sub_weakref_slot, base_converter, sub_converter, ): """ A class with a single attribute, inheriting from another class with a single attribute. """ @attr.s( frozen=base_frozen, slots=base_slots, weakref_slot=base_weakref_slot, ) class Base: a = attr.ib(converter=int if base_converter else None) @attr.s( frozen=sub_frozen, slots=sub_slots, weakref_slot=sub_weakref_slot ) class Sub(Base): b = attr.ib(converter=int if sub_converter else None) i = Sub("1", "2") assert i.a == (1 if base_converter else "1") assert i.b == (2 if sub_converter else "2") if base_frozen or sub_frozen: with pytest.raises(FrozenInstanceError): i.a = "2" with pytest.raises(FrozenInstanceError): i.b = "3" def test_tuple_class_aliasing(self): """ itemgetter and property are legal attribute names. """ @attr.s class C: property = attr.ib() itemgetter = attr.ib() x = attr.ib() assert "property" == attr.fields(C).property.name assert "itemgetter" == attr.fields(C).itemgetter.name assert "x" == attr.fields(C).x.name def test_auto_exc(self, slots, frozen): """ Classes with auto_exc=True have a Exception-style __str__, compare and hash by id, and store the fields additionally in self.args. """ @attr.s(auto_exc=True, slots=slots, frozen=frozen) class FooError(Exception): x = attr.ib() y = attr.ib(init=False, default=42) z = attr.ib(init=False) a = attr.ib() FooErrorMade = attr.make_class( "FooErrorMade", bases=(Exception,), attrs={ "x": attr.ib(), "y": attr.ib(init=False, default=42), "z": attr.ib(init=False), "a": attr.ib(), }, auto_exc=True, slots=slots, frozen=frozen, ) assert FooError(1, "foo") != FooError(1, "foo") assert FooErrorMade(1, "foo") != FooErrorMade(1, "foo") for cls in (FooError, FooErrorMade): with pytest.raises(cls) as ei1: raise cls(1, "foo") with pytest.raises(cls) as ei2: raise cls(1, "foo") e1 = ei1.value e2 = ei2.value assert e1 is e1 assert e1 == e1 assert e2 == e2 assert e1 != e2 assert "(1, 'foo')" == str(e1) == str(e2) assert (1, "foo") == e1.args == e2.args hash(e1) == hash(e1) hash(e2) == hash(e2) if not frozen: deepcopy(e1) deepcopy(e2) def test_auto_exc_one_attrib(self, slots, frozen): """ Having one attribute works with auto_exc=True. Easy to get wrong with tuple literals. """ @attr.s(auto_exc=True, slots=slots, frozen=frozen) class FooError(Exception): x = attr.ib() FooError(1) def test_eq_only(self, slots, frozen): """ Classes with order=False cannot be ordered. """ @attr.s(eq=True, order=False, slots=slots, frozen=frozen) class C: x = attr.ib() possible_errors = ( "unorderable types: C() < C()", "'<' not supported between instances of 'C' and 'C'", "unorderable types: C < C", # old PyPy 3 ) with pytest.raises(TypeError) as ei: C(5) < C(6) assert ei.value.args[0] in possible_errors @pytest.mark.parametrize("cmp", [True, False]) def test_attrib_cmp_shortcut(self, slots, cmp): """ Setting cmp on `attr.ib`s sets both eq and order. """ @attr.s(slots=slots) class C: x = attr.ib(cmp=cmp) assert cmp is attr.fields(C).x.eq assert cmp is attr.fields(C).x.order def test_no_setattr_if_validate_without_validators(self, slots): """ If a class has on_setattr=attr.setters.validate (former default in NG APIs) but sets no validators, don't use the (slower) setattr in __init__. Regression test for #816. """ @attr.s(on_setattr=attr.setters.validate, slots=slots) class C: x = attr.ib() @attr.s(on_setattr=attr.setters.validate, slots=slots) class D(C): y = attr.ib() src = inspect.getsource(D.__init__) assert "setattr" not in src assert "self.x = x" in src assert "self.y = y" in src assert object.__setattr__ == D.__setattr__ def test_no_setattr_if_convert_without_converters(self, slots): """ If a class has on_setattr=attr.setters.convert but sets no validators, don't use the (slower) setattr in __init__. """ @attr.s(on_setattr=attr.setters.convert, slots=slots) class C: x = attr.ib() @attr.s(on_setattr=attr.setters.convert, slots=slots) class D(C): y = attr.ib() src = inspect.getsource(D.__init__) assert "setattr" not in src assert "self.x = x" in src assert "self.y = y" in src assert object.__setattr__ == D.__setattr__ def test_no_setattr_with_ng_defaults(self, slots): """ If a class has the NG default on_setattr=[convert, validate] but sets no validators or converters, don't use the (slower) setattr in __init__. """ @attr.define(slots=slots) class C: x = attr.ib() src = inspect.getsource(C.__init__) assert "setattr" not in src assert "self.x = x" in src assert object.__setattr__ == C.__setattr__ @attr.define(slots=slots) class D(C): y = attr.ib() src = inspect.getsource(D.__init__) assert "setattr" not in src assert "self.x = x" in src assert "self.y = y" in src assert object.__setattr__ == D.__setattr__ def test_on_setattr_detect_inherited_validators(self): """ _make_init detects the presence of a validator even if the field is inherited. """ @attr.s(on_setattr=attr.setters.validate) class C: x = attr.ib(validator=42) @attr.s(on_setattr=attr.setters.validate) class D(C): y = attr.ib() src = inspect.getsource(D.__init__) assert "_setattr = _cached_setattr_get(self)" in src assert "_setattr('x', x)" in src assert "_setattr('y', y)" in src assert object.__setattr__ != D.__setattr__ def test_unsafe_hash(self, slots): """ attr.s(unsafe_hash=True) makes a class hashable. """ @attr.s(slots=slots, unsafe_hash=True) class Hashable: pass assert hash(Hashable()) def test_init_subclass(self, slots): """ __attrs_init_subclass__ is called on subclasses. """ REGISTRY = [] @attr.s(slots=slots) class Base: @classmethod def __attrs_init_subclass__(cls): REGISTRY.append(cls) @attr.s(slots=slots) class ToRegister(Base): pass assert [ToRegister] == REGISTRY @pytest.mark.skipif(not PY_3_13_PLUS, reason="requires Python 3.13+") class TestReplace: def test_replaces(self): """ copy.replace() is added by default and works like `attrs.evolve`. """ inst = C1(1, 2) assert C1(1, 42) == copy.replace(inst, y=42) assert C1(42, 2) == copy.replace(inst, x=42) def test_already_has_one(self): """ If the object already has a __replace__, it's left alone. """ sentinel = object() @attr.s class C: x = attr.ib() __replace__ = sentinel assert sentinel == C.__replace__ def test_invalid_field_name(self): """ Invalid field names raise a TypeError. This is consistent with dataclasses. """ inst = C1(1, 2) with pytest.raises(TypeError): copy.replace(inst, z=42) python-attrs-attrs-bd2446d/tests/test_hooks.py000066400000000000000000000207251476453530700216560ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from __future__ import annotations from datetime import datetime import pytest import attr class TestTransformHook: """ Tests for `attrs(tranform_value_serializer=func)` """ def test_hook_applied(self): """ The transform hook is applied to all attributes. Types can be missing, explicitly set, or annotated. """ results = [] def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) results[:] = [(a.name, a.type) for a in attribs] return attribs @attr.s(field_transformer=hook) class C: x = attr.ib() y = attr.ib(type=int) z: float = attr.ib() assert [("x", None), ("y", int), ("z", float)] == results def test_hook_applied_auto_attrib(self): """ The transform hook is applied to all attributes and type annotations are detected. """ results = [] def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) results[:] = [(a.name, a.type) for a in attribs] return attribs @attr.s(auto_attribs=True, field_transformer=hook) class C: x: int y: str = attr.ib() assert [("x", int), ("y", str)] == results def test_hook_applied_modify_attrib(self): """ The transform hook can modify attributes. """ def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) return [a.evolve(converter=a.type) for a in attribs] @attr.s(auto_attribs=True, field_transformer=hook) class C: x: int = attr.ib(converter=int) y: float c = C(x="3", y="3.14") assert C(x=3, y=3.14) == c def test_hook_remove_field(self): """ It is possible to remove fields via the hook. """ def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) return [a for a in attribs if a.type is not int] @attr.s(auto_attribs=True, field_transformer=hook) class C: x: int y: float assert {"y": 2.7} == attr.asdict(C(2.7)) def test_hook_add_field(self): """ It is possible to add fields via the hook. """ def hook(cls, attribs): a1 = attribs[0] a2 = a1.evolve(name="new") return [a1, a2] @attr.s(auto_attribs=True, field_transformer=hook) class C: x: int assert {"x": 1, "new": 2} == attr.asdict(C(1, 2)) def test_hook_override_alias(self): """ It is possible to set field alias via hook """ def use_dataclass_names(cls, attribs): return [a.evolve(alias=a.name) for a in attribs] @attr.s(auto_attribs=True, field_transformer=use_dataclass_names) class NameCase: public: int _private: int __dunder__: int assert NameCase(public=1, _private=2, __dunder__=3) == NameCase( 1, 2, 3 ) def test_hook_reorder_fields(self): """ It is possible to reorder fields via the hook. """ def hook(cls, attribs): return sorted(attribs, key=lambda x: x.metadata["field_order"]) @attr.s(field_transformer=hook) class C: x: int = attr.ib(metadata={"field_order": 1}) y: int = attr.ib(metadata={"field_order": 0}) assert {"x": 0, "y": 1} == attr.asdict(C(1, 0)) def test_hook_reorder_fields_before_order_check(self): """ It is possible to reorder fields via the hook before order-based errors are raised. Regression test for #1147. """ def hook(cls, attribs): return sorted(attribs, key=lambda x: x.metadata["field_order"]) @attr.s(field_transformer=hook) class C: x: int = attr.ib(metadata={"field_order": 1}, default=0) y: int = attr.ib(metadata={"field_order": 0}) assert {"x": 0, "y": 1} == attr.asdict(C(1)) def test_hook_conflicting_defaults_after_reorder(self): """ Raises `ValueError` if attributes with defaults are followed by mandatory attributes after the hook reorders fields. Regression test for #1147. """ def hook(cls, attribs): return sorted(attribs, key=lambda x: x.metadata["field_order"]) with pytest.raises(ValueError) as e: @attr.s(field_transformer=hook) class C: x: int = attr.ib(metadata={"field_order": 1}) y: int = attr.ib(metadata={"field_order": 0}, default=0) assert ( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: Attribute" "(name='x', default=NOTHING, validator=None, repr=True, " "eq=True, eq_key=None, order=True, order_key=None, " "hash=None, init=True, " "metadata=mappingproxy({'field_order': 1}), type='int', converter=None, " "kw_only=False, inherited=False, on_setattr=None, alias=None)", ) == e.value.args def test_hook_with_inheritance(self): """ The hook receives all fields from base classes. """ def hook(cls, attribs): assert ["x", "y"] == [a.name for a in attribs] # Remove Base' "x" return attribs[1:] @attr.s(auto_attribs=True) class Base: x: int @attr.s(auto_attribs=True, field_transformer=hook) class Sub(Base): y: int assert {"y": 2} == attr.asdict(Sub(2)) def test_attrs_attrclass(self): """ The list of attrs returned by a field_transformer is converted to "AttrsClass" again. Regression test for #821. """ @attr.s(auto_attribs=True, field_transformer=lambda c, a: list(a)) class C: x: int fields_type = type(attr.fields(C)) assert "CAttributes" == fields_type.__name__ assert issubclass(fields_type, tuple) def test_hook_generator(self): """ field_transfromers can be a generators. Regression test for #1416. """ def hook(cls, attribs): yield from attribs @attr.s(auto_attribs=True, field_transformer=hook) class Base: x: int assert ["x"] == [a.name for a in attr.fields(Base)] class TestAsDictHook: def test_asdict(self): """ asdict() calls the hooks in attrs classes and in other datastructures like lists or dicts. """ def hook(inst, a, v): if isinstance(v, datetime): return v.isoformat() return v @attr.dataclass class Child: x: datetime y: list[datetime] @attr.dataclass class Parent: a: Child b: list[Child] c: dict[str, Child] d: dict[str, datetime] inst = Parent( a=Child(1, [datetime(2020, 7, 1)]), b=[Child(2, [datetime(2020, 7, 2)])], c={"spam": Child(3, [datetime(2020, 7, 3)])}, d={"eggs": datetime(2020, 7, 4)}, ) result = attr.asdict(inst, value_serializer=hook) assert { "a": {"x": 1, "y": ["2020-07-01T00:00:00"]}, "b": [{"x": 2, "y": ["2020-07-02T00:00:00"]}], "c": {"spam": {"x": 3, "y": ["2020-07-03T00:00:00"]}}, "d": {"eggs": "2020-07-04T00:00:00"}, } == result def test_asdict_calls(self): """ The correct instances and attribute names are passed to the hook. """ calls = [] def hook(inst, a, v): calls.append((inst, a.name if a else a, v)) return v @attr.dataclass class Child: x: int @attr.dataclass class Parent: a: Child b: list[Child] c: dict[str, Child] inst = Parent(a=Child(1), b=[Child(2)], c={"spam": Child(3)}) attr.asdict(inst, value_serializer=hook) assert [ (inst, "a", inst.a), (inst.a, "x", inst.a.x), (inst, "b", inst.b), (inst.b[0], "x", inst.b[0].x), (inst, "c", inst.c), (None, None, "spam"), (inst.c["spam"], "x", inst.c["spam"].x), ] == calls python-attrs-attrs-bd2446d/tests/test_import.py000066400000000000000000000005331476453530700220400ustar00rootroot00000000000000# SPDX-License-Identifier: MIT class TestImportStar: def test_from_attr_import_star(self): """ import * from attr """ # attr_import_star contains `from attr import *`, which cannot # be done here because *-imports are only allowed on module level. from . import attr_import_star # noqa: F401 python-attrs-attrs-bd2446d/tests/test_init_subclass.py000066400000000000000000000025301476453530700233670ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `__init_subclass__` related functionality. """ import attr def test_init_subclass_vanilla(slots): """ `super().__init_subclass__` can be used if the subclass is not an attrs class both with dict and slotted classes. """ @attr.s(slots=slots) class Base: def __init_subclass__(cls, param, **kw): super().__init_subclass__(**kw) cls.param = param class Vanilla(Base, param="foo"): pass assert "foo" == Vanilla().param def test_init_subclass_attrs(): """ `__init_subclass__` works with attrs classes as long as slots=False. """ @attr.s(slots=False) class Base: def __init_subclass__(cls, param, **kw): super().__init_subclass__(**kw) cls.param = param @attr.s class Attrs(Base, param="foo"): pass assert "foo" == Attrs().param def test_init_subclass_slots_workaround(): """ `__init_subclass__` works with modern APIs if care is taken around classes existing twice. """ subs = {} @attr.define class Base: def __init_subclass__(cls): subs[cls.__qualname__] = cls @attr.define class Sub1(Base): x: int @attr.define class Sub2(Base): y: int assert (Sub1, Sub2) == tuple(subs.values()) python-attrs-attrs-bd2446d/tests/test_make.py000066400000000000000000002146441476453530700214550ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr._make`. """ import copy import functools import gc import inspect import itertools import sys import unicodedata from operator import attrgetter from typing import Generic, TypeVar import pytest from hypothesis import assume, given from hypothesis.strategies import booleans, integers, lists, sampled_from, text import attr from attr import _config from attr._compat import PY_3_10_PLUS, PY_3_14_PLUS from attr._make import ( Attribute, Factory, _AndValidator, _Attributes, _ClassBuilder, _CountingAttr, _determine_attrib_eq_order, _determine_attrs_eq_order, _determine_whether_to_implement, _transform_attrs, and_, fields, fields_dict, make_class, validate, ) from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError from .strategies import ( gen_attr_names, list_of_attrs, optional_bool, simple_attrs, simple_attrs_with_metadata, simple_attrs_without_metadata, simple_classes, ) from .utils import simple_attr attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c)) @pytest.fixture(name="with_and_without_validation", params=[True, False]) def _with_and_without_validation(request): """ Run tests with and without validation enabled. """ attr.validators.set_disabled(request.param) try: yield finally: attr.validators.set_disabled(False) class TestCountingAttr: """ Tests for `attr`. """ def test_returns_Attr(self): """ Returns an instance of _CountingAttr. """ a = attr.ib() assert isinstance(a, _CountingAttr) def test_validators_lists_to_wrapped_tuples(self): """ If a list is passed as validator, it's just converted to a tuple. """ def v1(_, __): pass def v2(_, __): pass a = attr.ib(validator=[v1, v2]) assert _AndValidator((v1, v2)) == a._validator def test_validator_decorator_single(self): """ If _CountingAttr.validator is used as a decorator and there is no decorator set, the decorated method is used as the validator. """ a = attr.ib() @a.validator def v(): pass assert v == a._validator @pytest.mark.parametrize( "wrap", [lambda v: v, lambda v: [v], lambda v: and_(v)] ) def test_validator_decorator(self, wrap): """ If _CountingAttr.validator is used as a decorator and there is already a decorator set, the decorators are composed using `and_`. """ def v(_, __): pass a = attr.ib(validator=wrap(v)) @a.validator def v2(self, _, __): pass assert _AndValidator((v, v2)) == a._validator def test_default_decorator_already_set(self): """ Raise DefaultAlreadySetError if the decorator is used after a default has been set. """ a = attr.ib(default=42) with pytest.raises(DefaultAlreadySetError): @a.default def f(self): pass def test_default_decorator_sets(self): """ Decorator wraps the method in a Factory with pass_self=True and sets the default. """ a = attr.ib() @a.default def f(self): pass assert Factory(f, True) == a._default def make_tc(): class TransformC: z = attr.ib() y = attr.ib() x = attr.ib() a = 42 return TransformC class TestTransformAttrs: """ Tests for `_transform_attrs`. """ def test_no_modifications(self): """ Does not attach __attrs_attrs__ to the class. """ C = make_tc() _transform_attrs(C, None, False, False, True, None) assert None is getattr(C, "__attrs_attrs__", None) def test_normal(self): """ Transforms every `_CountingAttr` and leaves others (a) be. """ C = make_tc() attrs, _, _ = _transform_attrs(C, None, False, False, True, None) assert ["z", "y", "x"] == [a.name for a in attrs] def test_empty(self): """ No attributes works as expected. """ @attr.s class C: pass assert _Attributes((), [], {}) == _transform_attrs( C, None, False, False, True, None ) def test_transforms_to_attribute(self): """ All `_CountingAttr`s are transformed into `Attribute`s. """ C = make_tc() attrs, base_attrs, _ = _transform_attrs( C, None, False, False, True, None ) assert [] == base_attrs assert 3 == len(attrs) assert all(isinstance(a, Attribute) for a in attrs) def test_conflicting_defaults(self): """ Raises `ValueError` if attributes with defaults are followed by mandatory attributes. """ class C: x = attr.ib(default=None) y = attr.ib() with pytest.raises(ValueError) as e: _transform_attrs(C, None, False, False, True, None) assert ( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: Attribute" "(name='y', default=NOTHING, validator=None, repr=True, " "eq=True, eq_key=None, order=True, order_key=None, " "hash=None, init=True, " "metadata=mappingproxy({}), type=None, converter=None, " "kw_only=False, inherited=False, on_setattr=None, alias=None)", ) == e.value.args def test_kw_only(self): """ Converts all attributes, including base class' attributes, if `kw_only` is provided. Therefore, `kw_only` allows attributes with defaults to precede mandatory attributes. Updates in the subclass *don't* affect the base class attributes. """ @attr.s class B: b = attr.ib() for b_a in B.__attrs_attrs__: assert b_a.kw_only is False class C(B): x = attr.ib(default=None) y = attr.ib() attrs, base_attrs, _ = _transform_attrs( C, None, False, True, True, None ) assert len(attrs) == 3 assert len(base_attrs) == 1 for a in attrs: assert a.kw_only is True for b_a in B.__attrs_attrs__: assert b_a.kw_only is False def test_these(self): """ If these is passed, use it and ignore body and base classes. """ class Base: z = attr.ib() class C(Base): y = attr.ib() attrs, base_attrs, _ = _transform_attrs( C, {"x": attr.ib()}, False, False, True, None ) assert [] == base_attrs assert (simple_attr("x"),) == attrs def test_these_leave_body(self): """ If these is passed, no attributes are removed from the body. """ @attr.s(init=False, these={"x": attr.ib()}) class C: x = 5 assert 5 == C().x assert "C(x=5)" == repr(C()) def test_these_ordered(self): """ If these is passed ordered attrs, their order respect instead of the counter. """ b = attr.ib(default=2) a = attr.ib(default=1) @attr.s(these={"a": a, "b": b}) class C: pass assert "C(a=1, b=2)" == repr(C()) def test_multiple_inheritance_old(self): """ Old multiple inheritance attribute collection behavior is retained. See #285 """ @attr.s class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @attr.s class B(A): b1 = attr.ib(default="b1") b2 = attr.ib(default="b2") @attr.s class C(B, A): c1 = attr.ib(default="c1") c2 = attr.ib(default="c2") @attr.s class D(A): d1 = attr.ib(default="d1") d2 = attr.ib(default="d2") @attr.s class E(C, D): e1 = attr.ib(default="e1") e2 = attr.ib(default="e2") assert ( "E(a1='a1', a2='a2', b1='b1', b2='b2', c1='c1', c2='c2', d1='d1', " "d2='d2', e1='e1', e2='e2')" ) == repr(E()) def test_overwrite_proper_mro(self): """ The proper MRO path works single overwrites too. """ @attr.s(collect_by_mro=True) class C: x = attr.ib(default=1) @attr.s(collect_by_mro=True) class D(C): x = attr.ib(default=2) assert "D(x=2)" == repr(D()) def test_multiple_inheritance_proper_mro(self): """ Attributes are collected according to the MRO. See #428 """ @attr.s class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @attr.s class B(A): b1 = attr.ib(default="b1") b2 = attr.ib(default="b2") @attr.s class C(B, A): c1 = attr.ib(default="c1") c2 = attr.ib(default="c2") @attr.s class D(A): d1 = attr.ib(default="d1") d2 = attr.ib(default="d2") @attr.s(collect_by_mro=True) class E(C, D): e1 = attr.ib(default="e1") e2 = attr.ib(default="e2") assert ( "E(a1='a1', a2='a2', d1='d1', d2='d2', b1='b1', b2='b2', c1='c1', " "c2='c2', e1='e1', e2='e2')" ) == repr(E()) def test_mro(self): """ Attributes and methods are looked up the same way. See #428 """ @attr.s(collect_by_mro=True) class A: x = attr.ib(10) def xx(self): return 10 @attr.s(collect_by_mro=True) class B(A): y = attr.ib(20) @attr.s(collect_by_mro=True) class C(A): x = attr.ib(50) def xx(self): return 50 @attr.s(collect_by_mro=True) class D(B, C): pass d = D() assert d.x == d.xx() def test_inherited(self): """ Inherited Attributes have `.inherited` True, otherwise False. """ @attr.s class A: a = attr.ib() @attr.s class B(A): b = attr.ib() @attr.s class C(B): a = attr.ib() c = attr.ib() f = attr.fields assert False is f(A).a.inherited assert True is f(B).a.inherited assert False is f(B).b.inherited assert False is f(C).a.inherited assert True is f(C).b.inherited assert False is f(C).c.inherited class TestAttributes: """ Tests for the `attrs`/`attr.s` class decorator. """ def test_sets_attrs(self): """ Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s. """ @attr.s class C: x = attr.ib() assert "x" == C.__attrs_attrs__[0].name assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__) def test_empty(self): """ No attributes, no problems. """ @attr.s class C3: pass assert "C3()" == repr(C3()) assert C3() == C3() @given(attr=attrs_st, attr_name=sampled_from(Attribute.__slots__)) def test_immutable(self, attr, attr_name): """ Attribute instances are immutable. """ with pytest.raises(AttributeError): setattr(attr, attr_name, 1) @pytest.mark.parametrize( "method_name", ["__repr__", "__eq__", "__hash__", "__init__"] ) def test_adds_all_by_default(self, method_name): """ If no further arguments are supplied, all add_XXX functions except add_hash are applied. __hash__ is set to None. """ # Set the method name to a sentinel and check whether it has been # overwritten afterwards. sentinel = object() class C: x = attr.ib() setattr(C, method_name, sentinel) C = attr.s(C) meth = getattr(C, method_name) assert sentinel != meth if method_name == "__hash__": assert meth is None @pytest.mark.parametrize( ("arg_name", "method_name"), [ ("repr", "__repr__"), ("eq", "__eq__"), ("order", "__le__"), ("unsafe_hash", "__hash__"), ("init", "__init__"), ], ) def test_respects_add_arguments(self, arg_name, method_name): """ If a certain `XXX` is `False`, `__XXX__` is not added to the class. """ # Set the method name to a sentinel and check whether it has been # overwritten afterwards. sentinel = object() am_args = { "repr": True, "eq": True, "order": True, "unsafe_hash": True, "init": True, } am_args[arg_name] = False if arg_name == "eq": am_args["order"] = False class C: x = attr.ib() setattr(C, method_name, sentinel) C = attr.s(**am_args)(C) assert sentinel == getattr(C, method_name) @pytest.mark.parametrize("init", [True, False]) def test_respects_init_attrs_init(self, init): """ If init=False, adds __attrs_init__ to the class. Otherwise, it does not. """ class C: x = attr.ib() C = attr.s(init=init)(C) assert hasattr(C, "__attrs_init__") != init @given(slots_outer=booleans(), slots_inner=booleans()) def test_repr_qualname(self, slots_outer, slots_inner): """ The name in repr is the __qualname__. """ @attr.s(slots=slots_outer) class C: @attr.s(slots=slots_inner) class D: pass assert "C.D()" == repr(C.D()) assert "GC.D()" == repr(GC.D()) @given(slots_outer=booleans(), slots_inner=booleans()) def test_repr_fake_qualname(self, slots_outer, slots_inner): """ Setting repr_ns overrides a potentially guessed namespace. """ with pytest.deprecated_call(match="The `repr_ns` argument"): @attr.s(slots=slots_outer) class C: @attr.s(repr_ns="C", slots=slots_inner) class D: pass assert "C.D()" == repr(C.D()) @given(slots_outer=booleans(), slots_inner=booleans()) def test_name_not_overridden(self, slots_outer, slots_inner): """ __name__ is different from __qualname__. """ @attr.s(slots=slots_outer) class C: @attr.s(slots=slots_inner) class D: pass assert C.D.__name__ == "D" assert C.D.__qualname__ == C.__qualname__ + ".D" @pytest.mark.usefixtures("with_and_without_validation") def test_pre_init(self): """ Verify that __attrs_pre_init__ gets called if defined. """ @attr.s class C: def __attrs_pre_init__(self2): self2.z = 30 c = C() assert 30 == getattr(c, "z", None) @pytest.mark.usefixtures("with_and_without_validation") def test_pre_init_args(self): """ Verify that __attrs_pre_init__ gets called with extra args if defined. """ @attr.s class C: x = attr.ib() def __attrs_pre_init__(self2, x): self2.z = x + 1 c = C(x=10) assert 11 == getattr(c, "z", None) @pytest.mark.usefixtures("with_and_without_validation") def test_pre_init_kwargs(self): """ Verify that __attrs_pre_init__ gets called with extra args and kwargs if defined. """ @attr.s class C: x = attr.ib() y = attr.field(kw_only=True) def __attrs_pre_init__(self2, x, y): self2.z = x + y + 1 c = C(10, y=11) assert 22 == getattr(c, "z", None) @pytest.mark.usefixtures("with_and_without_validation") def test_pre_init_kwargs_only(self): """ Verify that __attrs_pre_init__ gets called with extra kwargs only if defined. """ @attr.s class C: y = attr.field(kw_only=True) def __attrs_pre_init__(self2, y): self2.z = y + 1 c = C(y=11) assert 12 == getattr(c, "z", None) @pytest.mark.usefixtures("with_and_without_validation") def test_pre_init_kw_only_work_with_defaults(self): """ Default values together with kw_only don't break __attrs__pre_init__. """ val = None @attr.define class KWOnlyAndDefault: kw_and_default: int = attr.field(kw_only=True, default=3) def __attrs_pre_init__(self, *, kw_and_default): nonlocal val val = kw_and_default inst = KWOnlyAndDefault() assert 3 == val == inst.kw_and_default @pytest.mark.usefixtures("with_and_without_validation") def test_post_init(self): """ Verify that __attrs_post_init__ gets called if defined. """ @attr.s class C: x = attr.ib() y = attr.ib() def __attrs_post_init__(self2): self2.z = self2.x + self2.y c = C(x=10, y=20) assert 30 == getattr(c, "z", None) @pytest.mark.usefixtures("with_and_without_validation") def test_pre_post_init_order(self): """ Verify that __attrs_post_init__ gets called if defined. """ @attr.s class C: x = attr.ib() def __attrs_pre_init__(self2): self2.z = 30 def __attrs_post_init__(self2): self2.z += self2.x c = C(x=10) assert 40 == getattr(c, "z", None) def test_types(self): """ Sets the `Attribute.type` attr from type argument. """ @attr.s class C: x = attr.ib(type=int) y = attr.ib(type=str) z = attr.ib() assert int is fields(C).x.type assert str is fields(C).y.type assert None is fields(C).z.type def test_clean_class(self, slots): """ Attribute definitions do not appear on the class body after @attr.s. """ @attr.s(slots=slots) class C: x = attr.ib() x = getattr(C, "x", None) assert not isinstance(x, _CountingAttr) def test_factory_sugar(self): """ Passing factory=f is syntactic sugar for passing default=Factory(f). """ @attr.s class C: x = attr.ib(factory=list) assert Factory(list) == attr.fields(C).x.default def test_sugar_factory_mutex(self): """ Passing both default and factory raises ValueError. """ with pytest.raises(ValueError, match="mutually exclusive"): @attr.s class C: x = attr.ib(factory=list, default=Factory(list)) def test_sugar_callable(self): """ Factory has to be a callable to prevent people from passing Factory into it. """ with pytest.raises(ValueError, match="must be a callable"): @attr.s class C: x = attr.ib(factory=Factory(list)) def test_inherited_does_not_affect_hashing_and_equality(self): """ Whether or not an Attribute has been inherited doesn't affect how it's hashed and compared. """ @attr.s class BaseClass: x = attr.ib() @attr.s class SubClass(BaseClass): pass ba = attr.fields(BaseClass)[0] sa = attr.fields(SubClass)[0] assert ba == sa assert hash(ba) == hash(sa) class TestKeywordOnlyAttributes: """ Tests for keyword-only attributes. """ def test_adds_keyword_only_arguments(self): """ Attributes can be added as keyword-only. """ @attr.s class C: a = attr.ib() b = attr.ib(default=2, kw_only=True) c = attr.ib(kw_only=True) d = attr.ib(default=attr.Factory(lambda: 4), kw_only=True) c = C(1, c=3) assert c.a == 1 assert c.b == 2 assert c.c == 3 assert c.d == 4 def test_ignores_kw_only_when_init_is_false(self): """ Specifying ``kw_only=True`` when ``init=False`` is essentially a no-op. """ @attr.s class C: x = attr.ib(init=False, default=0, kw_only=True) y = attr.ib() c = C(1) assert c.x == 0 assert c.y == 1 def test_keyword_only_attributes_presence(self): """ Raises `TypeError` when keyword-only arguments are not specified. """ @attr.s class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: C() assert ( "missing 1 required keyword-only argument: 'x'" ) in e.value.args[0] def test_keyword_only_attributes_unexpected(self): """ Raises `TypeError` when unexpected keyword argument passed. """ @attr.s class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: C(x=5, y=10) assert "got an unexpected keyword argument 'y'" in e.value.args[0] def test_keyword_only_attributes_can_come_in_any_order(self): """ Mandatory vs non-mandatory attr order only matters when they are part of the __init__ signature and when they aren't kw_only (which are moved to the end and can be mandatory or non-mandatory in any order, as they will be specified as keyword args anyway). """ @attr.s class C: a = attr.ib(kw_only=True) b = attr.ib(kw_only=True, default="b") c = attr.ib(kw_only=True) d = attr.ib() e = attr.ib(default="e") f = attr.ib(kw_only=True) g = attr.ib(kw_only=True, default="g") h = attr.ib(kw_only=True) i = attr.ib(init=False) c = C("d", a="a", c="c", f="f", h="h") assert c.a == "a" assert c.b == "b" assert c.c == "c" assert c.d == "d" assert c.e == "e" assert c.f == "f" assert c.g == "g" assert c.h == "h" def test_keyword_only_attributes_allow_subclassing(self): """ Subclass can define keyword-only attributed without defaults, when the base class has attributes with defaults. """ @attr.s class Base: x = attr.ib(default=0) @attr.s class C(Base): y = attr.ib(kw_only=True) c = C(y=1) assert c.x == 0 assert c.y == 1 def test_keyword_only_class_level(self): """ `kw_only` can be provided at the attr.s level, converting all attributes to `kw_only.` """ @attr.s(kw_only=True) class C: x = attr.ib() y = attr.ib(kw_only=True) with pytest.raises(TypeError): C(0, y=1) c = C(x=0, y=1) assert c.x == 0 assert c.y == 1 def test_keyword_only_class_level_subclassing(self): """ Subclass `kw_only` propagates to attrs inherited from the base, allowing non-default following default. """ @attr.s class Base: x = attr.ib(default=0) @attr.s(kw_only=True) class C(Base): y = attr.ib() with pytest.raises(TypeError): C(1) c = C(x=0, y=1) assert c.x == 0 assert c.y == 1 def test_init_false_attribute_after_keyword_attribute(self): """ A positional attribute cannot follow a `kw_only` attribute, but an `init=False` attribute can because it won't appear in `__init__` """ @attr.s class KwArgBeforeInitFalse: kwarg = attr.ib(kw_only=True) non_init_function_default = attr.ib(init=False) non_init_keyword_default = attr.ib( init=False, default="default-by-keyword" ) @non_init_function_default.default def _init_to_init(self): return self.kwarg + "b" c = KwArgBeforeInitFalse(kwarg="a") assert c.kwarg == "a" assert c.non_init_function_default == "ab" assert c.non_init_keyword_default == "default-by-keyword" def test_init_false_attribute_after_keyword_attribute_with_inheritance( self, ): """ A positional attribute cannot follow a `kw_only` attribute, but an `init=False` attribute can because it won't appear in `__init__`. This test checks that we allow this even when the `kw_only` attribute appears in a parent class """ @attr.s class KwArgBeforeInitFalseParent: kwarg = attr.ib(kw_only=True) @attr.s class KwArgBeforeInitFalseChild(KwArgBeforeInitFalseParent): non_init_function_default = attr.ib(init=False) non_init_keyword_default = attr.ib( init=False, default="default-by-keyword" ) @non_init_function_default.default def _init_to_init(self): return self.kwarg + "b" c = KwArgBeforeInitFalseChild(kwarg="a") assert c.kwarg == "a" assert c.non_init_function_default == "ab" assert c.non_init_keyword_default == "default-by-keyword" @attr.s class GC: @attr.s class D: pass class TestMakeClass: """ Tests for `make_class`. """ @pytest.mark.parametrize("ls", [list, tuple]) def test_simple(self, ls): """ Passing a list of strings creates attributes with default args. """ C1 = make_class("C1", ls(["a", "b"])) @attr.s class C2: a = attr.ib() b = attr.ib() assert C1.__attrs_attrs__ == C2.__attrs_attrs__ def test_dict(self): """ Passing a dict of name: _CountingAttr creates an equivalent class. """ C1 = make_class( "C1", {"a": attr.ib(default=42), "b": attr.ib(default=None)} ) @attr.s class C2: a = attr.ib(default=42) b = attr.ib(default=None) assert C1.__attrs_attrs__ == C2.__attrs_attrs__ def test_attr_args(self): """ attributes_arguments are passed to attributes """ C = make_class("C", ["x"], repr=False) assert repr(C(1)).startswith(" 0 for cls_a, raw_a in zip(fields(C), list_of_attrs): assert cls_a.metadata != {} assert cls_a.metadata == raw_a.metadata def test_metadata(self): """ If metadata that is not None is passed, it is used. This is necessary for coverage because the previous test is hypothesis-based. """ md = {} a = attr.ib(metadata=md) assert md is a.metadata class TestClassBuilder: """ Tests for `_ClassBuilder`. """ def test_repr_str(self): """ Trying to add a `__str__` without having a `__repr__` raises a ValueError. """ with pytest.raises(ValueError) as ei: make_class("C", {}, repr=False, str=True) assert ( "__str__ can only be generated if a __repr__ exists.", ) == ei.value.args def test_repr(self): """ repr of builder itself makes sense. """ class C: pass b = _ClassBuilder( C, None, True, True, False, False, False, False, False, False, True, None, False, None, ) assert "<_ClassBuilder(cls=C)>" == repr(b) def test_returns_self(self): """ All methods return the builder for chaining. """ class C: x = attr.ib() b = _ClassBuilder( C, None, True, True, False, False, False, False, False, False, True, None, False, None, ) cls = ( b.add_eq() .add_order() .add_hash() .add_init() .add_attrs_init() .add_repr("ns") .add_str() .build_class() ) assert "ns.C(x=1)" == repr(cls(1)) @pytest.mark.parametrize( "meth_name", [ "__init__", "__hash__", "__repr__", "__str__", "__eq__", "__lt__", "__le__", "__gt__", "__ge__", ], ) def test_attaches_meta_dunders(self, meth_name): """ Generated methods have correct __module__, __name__, and __qualname__ attributes. """ @attr.s(unsafe_hash=True, str=True) class C: def organic(self): pass @attr.s(unsafe_hash=True, str=True) class D: pass meth_C = getattr(C, meth_name) meth_D = getattr(D, meth_name) assert meth_name == meth_C.__name__ == meth_D.__name__ assert C.organic.__module__ == meth_C.__module__ == meth_D.__module__ # This is assertion that would fail if a single __ne__ instance # was reused across multiple _make_eq calls. organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] assert organic_prefix + "." + meth_name == meth_C.__qualname__ def test_handles_missing_meta_on_class( self, monkeypatch: pytest.MonkeyPatch ): """ If the class hasn't a __module__ or __qualname__, the method hasn't either. """ class C: pass orig_hasattr = __builtins__["hasattr"] def our_hasattr(obj, name, /) -> bool: if name in ("__module__", "__qualname__"): return False return orig_hasattr(obj, name) monkeypatch.setitem( _ClassBuilder.__init__.__globals__["__builtins__"], "hasattr", our_hasattr, ) b = _ClassBuilder( C, these=None, slots=False, frozen=False, weakref_slot=True, getstate_setstate=False, auto_attribs=False, is_exc=False, kw_only=False, cache_hash=False, collect_by_mro=True, on_setattr=None, has_custom_setattr=False, field_transformer=None, ) def fake_meth(self): pass fake_meth.__module__ = "42" fake_meth.__qualname__ = "23" b._cls = {} # No module and qualname rv = b._add_method_dunders(fake_meth) assert "42" == rv.__module__ == fake_meth.__module__ assert "23" == rv.__qualname__ == fake_meth.__qualname__ def test_weakref_setstate(self): """ __weakref__ is not set on in setstate because it's not writable in slotted classes. """ @attr.s(slots=True) class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) assert C() == copy.deepcopy(C()) def test_no_references_to_original(self): """ When subclassing a slotted class, there are no stray references to the original class. """ @attr.s(slots=True) class C: pass @attr.s(slots=True) class C2(C): pass # The original C2 is in a reference cycle, so force a collect: gc.collect() assert [C2] == C.__subclasses__() @pytest.mark.xfail(PY_3_14_PLUS, reason="Currently broken on nightly.") def test_no_references_to_original_when_using_cached_property(self): """ When subclassing a slotted class and using cached property, there are no stray references to the original class. """ @attr.s(slots=True) class C: pass @attr.s(slots=True) class C2(C): @functools.cached_property def value(self) -> int: return 0 # The original C2 is in a reference cycle, so force a collect: gc.collect() assert [C2] == C.__subclasses__() def _get_copy_kwargs(include_slots=True): """ Generate a list of compatible attr.s arguments for the `copy` tests. """ options = ["frozen", "unsafe_hash", "cache_hash"] if include_slots: options.extend(["slots", "weakref_slot"]) out_kwargs = [] for args in itertools.product([True, False], repeat=len(options)): kwargs = dict(zip(options, args)) kwargs["unsafe_hash"] = kwargs["unsafe_hash"] or None if kwargs["cache_hash"] and not ( kwargs["frozen"] or kwargs["unsafe_hash"] ): continue out_kwargs.append(kwargs) return out_kwargs @pytest.mark.parametrize("kwargs", _get_copy_kwargs()) def test_copy(self, kwargs): """ Ensure that an attrs class can be copied successfully. """ @attr.s(eq=True, **kwargs) class C: x = attr.ib() a = C(1) b = copy.deepcopy(a) assert a == b @pytest.mark.parametrize("kwargs", _get_copy_kwargs(include_slots=False)) def test_copy_custom_setstate(self, kwargs): """ Ensure that non-slots classes respect a custom __setstate__. """ @attr.s(eq=True, **kwargs) class C: x = attr.ib() def __getstate__(self): return self.__dict__ def __setstate__(self, state): state["x"] *= 5 self.__dict__.update(state) expected = C(25) actual = copy.copy(C(5)) assert actual == expected class TestInitAlias: """ Tests for Attribute alias handling. """ def test_default_and_specify(self): """ alias is present on the Attributes returned from attr.fields. If left unspecified, it defaults to standard private-attribute handling. If specified, it passes through the explicit alias. """ # alias is None by default on _CountingAttr default_counting = attr.ib() assert default_counting.alias is None override_counting = attr.ib(alias="specified") assert override_counting.alias == "specified" @attr.s class Cases: public_default = attr.ib() _private_default = attr.ib() __dunder_default__ = attr.ib() public_override = attr.ib(alias="public") _private_override = attr.ib(alias="_private") __dunder_override__ = attr.ib(alias="__dunder__") cases = attr.fields_dict(Cases) # Default applies private-name mangling logic assert cases["public_default"].name == "public_default" assert cases["public_default"].alias == "public_default" assert cases["_private_default"].name == "_private_default" assert cases["_private_default"].alias == "private_default" assert cases["__dunder_default__"].name == "__dunder_default__" assert cases["__dunder_default__"].alias == "dunder_default__" # Override is passed through assert cases["public_override"].name == "public_override" assert cases["public_override"].alias == "public" assert cases["_private_override"].name == "_private_override" assert cases["_private_override"].alias == "_private" assert cases["__dunder_override__"].name == "__dunder_override__" assert cases["__dunder_override__"].alias == "__dunder__" # And aliases are applied to the __init__ signature example = Cases( public_default=1, private_default=2, dunder_default__=3, public=4, _private=5, __dunder__=6, ) assert example.public_default == 1 assert example._private_default == 2 assert example.__dunder_default__ == 3 assert example.public_override == 4 assert example._private_override == 5 assert example.__dunder_override__ == 6 def test_evolve(self): """ attr.evolve uses Attribute.alias to determine parameter names. """ @attr.s class EvolveCase: _override = attr.ib(alias="_override") __mangled = attr.ib() __dunder__ = attr.ib() org = EvolveCase(1, 2, 3) # Previous behavior of evolve as broken for double-underscore # passthrough, and would raise here due to mis-mapping the __dunder__ # alias assert attr.evolve(org) == org # evolve uses the alias to match __init__ signature assert attr.evolve( org, _override=0, ) == EvolveCase(0, 2, 3) # and properly passes through dunders and mangles assert attr.evolve( org, EvolveCase__mangled=4, dunder__=5, ) == EvolveCase(1, 4, 5) class TestMakeOrder: """ Tests for _make_order(). """ def test_subclasses_cannot_be_compared(self): """ Calling comparison methods on subclasses raises a TypeError. We use the actual operation so we get an error raised. """ @attr.s class A: a = attr.ib() @attr.s class B(A): pass a = A(42) b = B(42) assert a <= a assert a >= a assert not a < a assert not a > a assert ( NotImplemented == a.__lt__(b) == a.__le__(b) == a.__gt__(b) == a.__ge__(b) ) with pytest.raises(TypeError): a <= b with pytest.raises(TypeError): a >= b with pytest.raises(TypeError): a < b with pytest.raises(TypeError): a > b class TestDetermineAttrsEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. """ assert (42, 42) == _determine_attrs_eq_order(None, None, None, 42) @pytest.mark.parametrize("eq", [True, False]) def test_order_mirrors_eq_by_default(self, eq): """ If order is None, it mirrors eq. """ assert (eq, eq) == _determine_attrs_eq_order(None, eq, None, True) def test_order_without_eq(self): """ eq=False, order=True raises a meaningful ValueError. """ with pytest.raises( ValueError, match="`order` can only be True if `eq` is True too." ): _determine_attrs_eq_order(None, False, True, True) @given(cmp=booleans(), eq=optional_bool, order=optional_bool) def test_mix(self, cmp, eq, order): """ If cmp is not None, eq and order must be None and vice versa. """ assume(eq is not None or order is not None) with pytest.raises( ValueError, match="Don't mix `cmp` with `eq' and `order`." ): _determine_attrs_eq_order(cmp, eq, order, True) class TestDetermineAttribEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. """ assert (42, None, 42, None) == _determine_attrib_eq_order( None, None, None, 42 ) def test_eq_callable_order_boolean(self): """ eq=callable or order=callable need to transformed into eq/eq_key or order/order_key. """ assert (True, str.lower, False, None) == _determine_attrib_eq_order( None, str.lower, False, True ) def test_eq_callable_order_callable(self): """ eq=callable or order=callable need to transformed into eq/eq_key or order/order_key. """ assert (True, str.lower, True, abs) == _determine_attrib_eq_order( None, str.lower, abs, True ) def test_eq_boolean_order_callable(self): """ eq=callable or order=callable need to transformed into eq/eq_key or order/order_key. """ assert (True, None, True, str.lower) == _determine_attrib_eq_order( None, True, str.lower, True ) @pytest.mark.parametrize("eq", [True, False]) def test_order_mirrors_eq_by_default(self, eq): """ If order is None, it mirrors eq. """ assert (eq, None, eq, None) == _determine_attrib_eq_order( None, eq, None, True ) def test_order_without_eq(self): """ eq=False, order=True raises a meaningful ValueError. """ with pytest.raises( ValueError, match="`order` can only be True if `eq` is True too." ): _determine_attrib_eq_order(None, False, True, True) @given(cmp=booleans(), eq=optional_bool, order=optional_bool) def test_mix(self, cmp, eq, order): """ If cmp is not None, eq and order must be None and vice versa. """ assume(eq is not None or order is not None) with pytest.raises( ValueError, match="Don't mix `cmp` with `eq' and `order`." ): _determine_attrib_eq_order(cmp, eq, order, True) class TestDocs: @pytest.mark.parametrize( "meth_name", [ "__init__", "__repr__", "__eq__", "__lt__", "__le__", "__gt__", "__ge__", ], ) def test_docs(self, meth_name): """ Tests the presence and correctness of the documentation for the generated methods """ @attr.s class A: pass if hasattr(A, "__qualname__"): method = getattr(A, meth_name) expected = f"Method generated by attrs for class {A.__qualname__}." assert expected == method.__doc__ class BareC: pass class BareSlottedC: __slots__ = () class TestAutoDetect: @pytest.mark.parametrize("C", [BareC, BareSlottedC]) def test_determine_detects_non_presence_correctly(self, C): """ On an empty class, nothing should be detected. """ assert True is _determine_whether_to_implement( C, None, True, ("__init__",) ) assert True is _determine_whether_to_implement( C, None, True, ("__repr__",) ) assert True is _determine_whether_to_implement( C, None, True, ("__eq__", "__ne__") ) assert True is _determine_whether_to_implement( C, None, True, ("__le__", "__lt__", "__ge__", "__gt__") ) def test_make_all_by_default(self, slots, frozen): """ If nothing is there to be detected, imply init=True, repr=True, unsafe_hash=None, eq=True, order=True. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() i = C(1) o = object() assert i.__init__ is not o.__init__ assert i.__repr__ is not o.__repr__ assert i.__eq__ is not o.__eq__ assert i.__ne__ is not o.__ne__ assert i.__le__ is not o.__le__ assert i.__lt__ is not o.__lt__ assert i.__ge__ is not o.__ge__ assert i.__gt__ is not o.__gt__ def test_detect_auto_init(self, slots, frozen): """ If auto_detect=True and an __init__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) class CI: x = attr.ib() def __init__(self): object.__setattr__(self, "x", 42) assert 42 == CI().x def test_detect_auto_repr(self, slots, frozen): """ If auto_detect=True and an __repr__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __repr__(self): return "hi" assert "hi" == repr(C(42)) def test_hash_uses_eq(self, slots, frozen): """ If eq is passed in, then __hash__ should use the eq callable to generate the hash code. """ @attr.s(slots=slots, frozen=frozen, unsafe_hash=True) class C: x = attr.ib(eq=str) @attr.s(slots=slots, frozen=frozen, unsafe_hash=True) class D: x = attr.ib() # These hashes should be the same because 1 is turned into # string before hashing. assert hash(C("1")) == hash(C(1)) assert hash(D("1")) != hash(D(1)) def test_detect_auto_hash(self, slots, frozen): """ If auto_detect=True and an __hash__ exists, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __hash__(self): return 0xC0FFEE assert 0xC0FFEE == hash(C(42)) def test_detect_auto_eq(self, slots, frozen): """ If auto_detect=True and an __eq__ or an __ne__, exist, don't write one. """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __eq__(self, o): raise ValueError("worked") with pytest.raises(ValueError, match="worked"): C(1) == C(1) @attr.s(auto_detect=True, slots=slots, frozen=frozen) class D: x = attr.ib() def __ne__(self, o): raise ValueError("worked") with pytest.raises(ValueError, match="worked"): D(1) != D(1) def test_detect_auto_order(self, slots, frozen): """ If auto_detect=True and an __ge__, __gt__, __le__, or and __lt__ exist, don't write one. It's surprisingly difficult to test this programmatically, so we do it by hand. """ def assert_not_set(cls, ex, meth_name): __tracebackhide__ = True a = getattr(cls, meth_name) if meth_name == ex: assert a == 42 else: assert a is getattr(object, meth_name) def assert_none_set(cls, ex): __tracebackhide__ = True for m in ("le", "lt", "ge", "gt"): assert_not_set(cls, ex, "__" + m + "__") @attr.s(auto_detect=True, slots=slots, frozen=frozen) class LE: __le__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) class LT: __lt__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) class GE: __ge__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) class GT: __gt__ = 42 assert_none_set(LE, "__le__") assert_none_set(LT, "__lt__") assert_none_set(GE, "__ge__") assert_none_set(GT, "__gt__") def test_override_init(self, slots, frozen): """ If init=True is passed, ignore __init__. """ @attr.s(init=True, auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __init__(self): pytest.fail("should not be called") assert C(1) == C(1) def test_override_repr(self, slots, frozen): """ If repr=True is passed, ignore __repr__. """ @attr.s(repr=True, auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __repr__(self): pytest.fail("should not be called") assert "C(x=1)" == repr(C(1)) def test_override_hash(self, slots, frozen): """ If unsafe_hash=True is passed, ignore __hash__. """ @attr.s(unsafe_hash=True, auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __hash__(self): pytest.fail("should not be called") assert hash(C(1)) def test_override_eq(self, slots, frozen): """ If eq=True is passed, ignore __eq__ and __ne__. """ @attr.s(eq=True, auto_detect=True, slots=slots, frozen=frozen) class C: x = attr.ib() def __eq__(self, o): pytest.fail("should not be called") def __ne__(self, o): pytest.fail("should not be called") assert C(1) == C(1) @pytest.mark.parametrize( ("eq", "order", "cmp"), [ (True, None, None), (True, True, None), (None, True, None), (None, None, True), ], ) def test_override_order(self, slots, frozen, eq, order, cmp): """ If order=True is passed, ignore __le__, __lt__, __gt__, __ge__. eq=True and cmp=True both imply order=True so test it too. """ def meth(self, o): pytest.fail("should not be called") @attr.s( cmp=cmp, order=order, eq=eq, auto_detect=True, slots=slots, frozen=frozen, ) class C: x = attr.ib() __le__ = __lt__ = __gt__ = __ge__ = meth assert C(1) < C(2) assert C(1) <= C(2) assert C(2) > C(1) assert C(2) >= C(1) @pytest.mark.parametrize("first", [True, False]) def test_total_ordering(self, slots, first): """ functools.total_ordering works as expected if an order method and an eq method are detected. Ensure the order doesn't matter. """ class C: x = attr.ib() own_eq_called = attr.ib(default=False) own_le_called = attr.ib(default=False) def __eq__(self, o): self.own_eq_called = True return self.x == o.x def __le__(self, o): self.own_le_called = True return self.x <= o.x if first: C = functools.total_ordering( attr.s(auto_detect=True, slots=slots)(C) ) else: C = attr.s(auto_detect=True, slots=slots)( functools.total_ordering(C) ) c1, c2 = C(1), C(2) assert c1 < c2 assert c1.own_le_called c1, c2 = C(1), C(2) assert c2 > c1 assert c2.own_le_called c1, c2 = C(1), C(2) assert c2 != c1 assert c1 == c1 assert c1.own_eq_called def test_detects_setstate_getstate(self, slots): """ __getstate__ and __setstate__ are not overwritten if either is present. """ @attr.s(slots=slots, auto_detect=True) class C: def __getstate__(self): return ("hi",) assert getattr(object, "__setstate__", None) is getattr( C, "__setstate__", None ) @attr.s(slots=slots, auto_detect=True) class C: called = attr.ib(False) def __setstate__(self, state): self.called = True i = C() assert False is i.called i.__setstate__(()) assert True is i.called assert getattr(object, "__getstate__", None) is getattr( C, "__getstate__", None ) @pytest.mark.skipif(PY_3_10_PLUS, reason="Pre-3.10 only.") def test_match_args_pre_310(self): """ __match_args__ is not created on Python versions older than 3.10. """ @attr.s class C: a = attr.ib() assert None is getattr(C, "__match_args__", None) @pytest.mark.skipif( not PY_3_10_PLUS, reason="Structural pattern matching is 3.10+" ) class TestMatchArgs: """ Tests for match_args and __match_args__ generation. """ def test_match_args(self): """ __match_args__ is created by default on Python 3.10. """ @attr.define class C: a = attr.field() assert ("a",) == C.__match_args__ def test_explicit_match_args(self): """ A custom __match_args__ set is not overwritten. """ ma = () @attr.define class C: a = attr.field() __match_args__ = ma assert C(42).__match_args__ is ma @pytest.mark.parametrize("match_args", [True, False]) def test_match_args_attr_set(self, match_args): """ __match_args__ is set depending on match_args. """ @attr.define(match_args=match_args) class C: a = attr.field() if match_args: assert hasattr(C, "__match_args__") else: assert not hasattr(C, "__match_args__") def test_match_args_kw_only(self): """ kw_only classes don't generate __match_args__. kw_only fields are not included in __match_args__. """ @attr.define class C: a = attr.field(kw_only=True) b = attr.field() assert C.__match_args__ == ("b",) @attr.define(kw_only=True) class C: a = attr.field() b = attr.field() assert C.__match_args__ == () def test_match_args_argument(self): """ match_args being False with inheritance. """ @attr.define(match_args=False) class X: a = attr.field() assert "__match_args__" not in X.__dict__ @attr.define(match_args=False) class Y: a = attr.field() __match_args__ = ("b",) assert Y.__match_args__ == ("b",) @attr.define(match_args=False) class Z(Y): z = attr.field() assert Z.__match_args__ == ("b",) @attr.define class A: a = attr.field() z = attr.field() @attr.define(match_args=False) class B(A): b = attr.field() assert B.__match_args__ == ("a", "z") def test_make_class(self): """ match_args generation with make_class. """ C1 = make_class("C1", ["a", "b"]) assert ("a", "b") == C1.__match_args__ C1 = make_class("C1", ["a", "b"], match_args=False) assert not hasattr(C1, "__match_args__") C1 = make_class("C1", ["a", "b"], kw_only=True) assert () == C1.__match_args__ C1 = make_class("C1", {"a": attr.ib(kw_only=True), "b": attr.ib()}) assert ("b",) == C1.__match_args__ python-attrs-attrs-bd2446d/tests/test_mypy.yml000066400000000000000000001331241476453530700217000ustar00rootroot00000000000000- case: attr_s_with_type_argument parametrized: - val: "a = attr.ib(type=int)" - val: "a: int = attr.ib()" main: | import attr @attr.s class C: {{ val }} C() # E: Missing positional argument "a" in call to "C" [call-arg] C(1) C(a=1) C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" [arg-type] - case: attr_s_with_type_annotations main: | import attr @attr.s class C: a: int = attr.ib() C() # E: Missing positional argument "a" in call to "C" [call-arg] C(1) C(a=1) C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" [arg-type] - case: testAttrsSimple main: | import attr @attr.s class A: a = attr.ib() _b = attr.ib() c = attr.ib(18) _d = attr.ib(validator=None, default=18) E = 18 def foo(self): return self.a reveal_type(A) # N: Revealed type is "def (a: Any, b: Any, c: Any =, d: Any =) -> main.A" A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) A(1, [2], '3', 4, 5) # E: Too many arguments for "A" [call-arg] - case: testAttrsAnnotated regex: true main: | import attr from typing import List, ClassVar @attr.s class A: a: int = attr.ib() _b: List[int] = attr.ib() c: str = attr.ib('18') _d: int = attr.ib(validator=None, default=18) E = 7 F: ClassVar[int] = 22 reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins\.list\[builtins\.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsPython2Annotations regex: true main: | import attr from typing import List, ClassVar @attr.s class A: a = attr.ib() # type: int _b = attr.ib() # type: List[int] c = attr.ib('18') # type: str _d = attr.ib(validator=None, default=18) # type: int E = 7 F: ClassVar[int] = 22 reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins\.list\[builtins\.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsAutoAttribs regex: true main: | import attr from typing import List, ClassVar @attr.s(auto_attribs=True) class A: a: int _b: List[int] c: str = '18' _d: int = attr.ib(validator=None, default=18) E = 7 F: ClassVar[int] = 22 reveal_type(A) # N: Revealed type is "def \(a: builtins\.int, b: builtins.list\[builtins.int\], c: builtins\.str =, d: builtins\.int =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] # E: Argument 3 to "A" has incompatible type "int"; expected "str" \[arg-type\] A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsUntypedNoUntypedDefs mypy_config: | disallow_untyped_defs = True main: | import attr @attr.s class A: a = attr.ib() # E: Need type annotation for "a" [var-annotated] _b = attr.ib() # E: Need type annotation for "_b" [var-annotated] c = attr.ib(18) # E: Need type annotation for "c" [var-annotated] _d = attr.ib(validator=None, default=18) # E: Need type annotation for "_d" [var-annotated] E = 18 - case: testAttrsWrongReturnValue main: | import attr @attr.s class A: x: int = attr.ib(8) def foo(self) -> str: return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.s class B: x = attr.ib(8) # type: int def foo(self) -> str: return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.dataclass class C: x: int = 8 def foo(self) -> str: return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] @attr.s class D: x = attr.ib(8, type=int) def foo(self) -> str: return self.x # E: Incompatible return value type (got "int", expected "str") [return-value] - case: testAttrsSeriousNames regex: true main: | from attr import attrib, attrs from typing import List @attrs(init=True) class A: a = attrib() _b: List[int] = attrib() c = attrib(18) _d = attrib(validator=None, default=18) CLASS_VAR = 18 reveal_type(A) # N: Revealed type is "def \(a: Any, b: builtins.list\[builtins.int\], c: Any =, d: Any =\) -> main\.A" A(1, [2]) A(1, [2], '3', 4) A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "[Ll]ist\[int\]" \[arg-type\] A(1, [2], '3', 4, 5) # E: Too many arguments for "A" \[call-arg\] - case: testAttrsDefaultErrors main: | import attr @attr.s class A: x = attr.ib(default=17) y = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] @attr.s(auto_attribs=True) class B: x: int = 17 y: int # E: Non-default attributes not allowed after default attributes. [misc] @attr.s(auto_attribs=True) class C: x: int = attr.ib(default=17) y: int # E: Non-default attributes not allowed after default attributes. [misc] @attr.s class D: x = attr.ib() y = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] @x.default def foo(self): return 17 - case: testAttrsNotBooleans main: | import attr x = True @attr.s(cmp=x) # E: "cmp" argument must be a True, False, or None literal [literal-required] class A: a = attr.ib(init=x) # E: "init" argument must be a True or False literal [literal-required] - case: testAttrsInitFalse main: | from attr import attrib, attrs @attrs(auto_attribs=True, init=False) class A: a: int _b: int c: int = 18 _d: int = attrib(validator=None, default=18) reveal_type(A) # N: Revealed type is "def () -> main.A" A() A(1, [2]) # E: Too many arguments for "A" [call-arg] A(1, [2], '3', 4) # E: Too many arguments for "A" [call-arg] - case: testAttrsInitAttribFalse main: | from attr import attrib, attrs @attrs class A: a = attrib(init=False) b = attrib() reveal_type(A) # N: Revealed type is "def (b: Any) -> main.A" - case: testAttrsCmpTrue regex: true main: | from attr import attrib, attrs @attrs(auto_attribs=True) class A: a: int reveal_type(A) # N: Revealed type is "def \(a: builtins.int\) -> main.A" reveal_type(A.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(A.__le__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(A.__gt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(A.__ge__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" A(1) < A(2) A(1) <= A(2) A(1) > A(2) A(1) >= A(2) A(1) == A(2) A(1) != A(2) A(1) < 1 # E: Unsupported operand types for < \("A" and "int"\) \[operator\] A(1) <= 1 # E: Unsupported operand types for <= \("A" and "int"\) \[operator\] A(1) > 1 # E: Unsupported operand types for > \("A" and "int"\) \[operator\] A(1) >= 1 # E: Unsupported operand types for >= \("A" and "int"\) \[operator\] A(1) == 1 A(1) != 1 1 < A(1) # E: Unsupported operand types for < \("int" and "A"\) \[operator\] 1 <= A(1) # E: Unsupported operand types for <= \("int" and "A"\) \[operator\] 1 > A(1) # E: Unsupported operand types for > \("int" and "A"\) \[operator\] 1 >= A(1) # E: Unsupported operand types for >= \("int" and "A"\) \[operator\] 1 == A(1) 1 != A(1) - case: testAttrsEqFalse main: | from attr import attrib, attrs @attrs(auto_attribs=True, eq=False) class A: a: int reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" reveal_type(A.__eq__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" reveal_type(A.__ne__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" A(1) < A(2) # E: Unsupported left operand type for < ("A") [operator] A(1) <= A(2) # E: Unsupported left operand type for <= ("A") [operator] A(1) > A(2) # E: Unsupported left operand type for > ("A") [operator] A(1) >= A(2) # E: Unsupported left operand type for >= ("A") [operator] A(1) == A(2) A(1) != A(2) A(1) < 1 # E: Unsupported operand types for > ("int" and "A") [operator] A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") [operator] A(1) > 1 # E: Unsupported operand types for < ("int" and "A") [operator] A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") [operator] A(1) == 1 A(1) != 1 1 < A(1) # E: Unsupported operand types for < ("int" and "A") [operator] 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") [operator] 1 > A(1) # E: Unsupported operand types for > ("int" and "A") [operator] 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") [operator] 1 == A(1) 1 != A(1) - case: testAttrsOrderFalse main: | from attr import attrib, attrs @attrs(auto_attribs=True, order=False) class A: a: int reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" A(1) < A(2) # E: Unsupported left operand type for < ("A") [operator] A(1) <= A(2) # E: Unsupported left operand type for <= ("A") [operator] A(1) > A(2) # E: Unsupported left operand type for > ("A") [operator] A(1) >= A(2) # E: Unsupported left operand type for >= ("A") [operator] A(1) == A(2) A(1) != A(2) A(1) < 1 # E: Unsupported operand types for > ("int" and "A") [operator] A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") [operator] A(1) > 1 # E: Unsupported operand types for < ("int" and "A") [operator] A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") [operator] A(1) == 1 A(1) != 1 1 < A(1) # E: Unsupported operand types for < ("int" and "A") [operator] 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") [operator] 1 > A(1) # E: Unsupported operand types for > ("int" and "A") [operator] 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") [operator] 1 == A(1) 1 != A(1) - case: testAttrsCmpEqOrderValues main: | from attr import attrib, attrs @attrs(cmp=True) class DeprecatedTrue: ... @attrs(cmp=False) class DeprecatedFalse: ... @attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" [misc] class Mixed: ... @attrs(order=True, eq=False) # E: eq must be True if order is True [misc] class Confused: ... - case: testAttrsInheritance main: | import attr @attr.s class A: a: int = attr.ib() @attr.s class B: b: str = attr.ib() @attr.s class C(A, B): c: bool = attr.ib() reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool) -> main.C" - case: testAttrsNestedInClasses main: | import attr @attr.s class C: y = attr.ib() @attr.s class D: x: int = attr.ib() reveal_type(C) # N: Revealed type is "def (y: Any) -> main.C" reveal_type(C.D) # N: Revealed type is "def (x: builtins.int) -> main.C.D" - case: testAttrsInheritanceOverride main: | import attr @attr.s class A: a: int = attr.ib() x: int = attr.ib() @attr.s class B(A): b: str = attr.ib() x: int = attr.ib(default=22) @attr.s class C(B): c: bool = attr.ib() # No error here because the x below overwrites the x above. x: int = attr.ib() reveal_type(A) # N: Revealed type is "def (a: builtins.int, x: builtins.int) -> main.A" reveal_type(B) # N: Revealed type is "def (a: builtins.int, b: builtins.str, x: builtins.int =) -> main.B" reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool, x: builtins.int) -> main.C" - case: testAttrsTypeEquals main: | import attr @attr.s class A: a = attr.ib(type=int) b = attr.ib(18, type=int) reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.int =) -> main.A" - case: testAttrsFrozen main: | import attr @attr.s(frozen=True) class A: a = attr.ib() a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only [misc] - case: testAttrsNextGenFrozen main: | from attr import frozen, field @frozen class A: a = field() a = A(5) a.a = 16 # E: Property "a" defined in "A" is read-only [misc] - case: testAttrsNextGenDetect main: | from attr import define, field @define class A: a = field() @define class B: a: int @define class C: a: int = field() b = field() @define class D: a: int b = field() reveal_type(A) # N: Revealed type is "def (a: Any) -> main.A" reveal_type(B) # N: Revealed type is "def (a: builtins.int) -> main.B" reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: Any) -> main.C" reveal_type(D) # N: Revealed type is "def (b: Any) -> main.D" - case: testAttrsDataClass main: | import attr from typing import List, ClassVar @attr.dataclass class A: a: int _b: List[str] c: str = '18' _d: int = attr.ib(validator=None, default=18) E = 7 F: ClassVar[int] = 22 reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.str], c: builtins.str =, d: builtins.int =) -> main.A" A(1, ['2']) - case: testAttrsTypeAlias main: | from typing import List import attr Alias = List[int] @attr.s(auto_attribs=True) class A: Alias2 = List[str] x: Alias y: Alias2 = attr.ib() reveal_type(A) # N: Revealed type is "def (x: builtins.list[builtins.int], y: builtins.list[builtins.str]) -> main.A" - case: testAttrsGeneric regex: true main: | from typing import TypeVar, Generic, List import attr T = TypeVar('T') @attr.s(auto_attribs=True) class A(Generic[T]): x: List[T] y: T = attr.ib() def foo(self) -> List[T]: return [self.y] def bar(self) -> T: return self.x[0] def problem(self) -> T: return self.x # E: Incompatible return value type \(got "[Ll]ist\[T\]", expected "T"\) \[return-value\] reveal_type(A) # N: Revealed type is "def \[T\] \(x: builtins\.list\[T`1\], y: T`1\) -> main.A\[T`1\]" a = A([1], 2) reveal_type(a) # N: Revealed type is "main\.A\[builtins.int\]" reveal_type(a.x) # N: Revealed type is "builtins\.list\[builtins\.int\]" reveal_type(a.y) # N: Revealed type is "builtins\.int" A(['str'], 7) # E: Cannot infer type argument 1 of "A" \[misc\] A([1], '2') # E: Cannot infer type argument 1 of "A" \[misc\] - case: testAttrsUntypedGenericInheritance main: | from typing import Generic, TypeVar import attr T = TypeVar("T") @attr.s(auto_attribs=True) class Base(Generic[T]): attr: T @attr.s(auto_attribs=True) class Sub(Base): pass sub = Sub(attr=1) reveal_type(sub) # N: Revealed type is "main.Sub" reveal_type(sub.attr) # N: Revealed type is "Any" skip: True # Need to investigate why this is broken - case: testAttrsGenericInheritance main: | from typing import Generic, TypeVar import attr S = TypeVar("S") T = TypeVar("T") @attr.s(auto_attribs=True) class Base(Generic[T]): attr: T @attr.s(auto_attribs=True) class Sub(Base[S]): pass sub_int = Sub[int](attr=1) reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int]" reveal_type(sub_int.attr) # N: Revealed type is "builtins.int" sub_str = Sub[str](attr='ok') reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str]" reveal_type(sub_str.attr) # N: Revealed type is "builtins.str" - case: testAttrsGenericInheritance2 main: | from typing import Generic, TypeVar import attr T1 = TypeVar("T1") T2 = TypeVar("T2") T3 = TypeVar("T3") @attr.s(auto_attribs=True) class Base(Generic[T1, T2, T3]): one: T1 two: T2 three: T3 @attr.s(auto_attribs=True) class Sub(Base[int, str, float]): pass sub = Sub(one=1, two='ok', three=3.14) reveal_type(sub) # N: Revealed type is "main.Sub" reveal_type(sub.one) # N: Revealed type is "builtins.int*" reveal_type(sub.two) # N: Revealed type is "builtins.str*" reveal_type(sub.three) # N: Revealed type is "builtins.float*" skip: True # Need to investigate why this is broken - case: testAttrsMultiGenericInheritance main: | from typing import Generic, TypeVar import attr T = TypeVar("T") @attr.s(auto_attribs=True, eq=False) class Base(Generic[T]): base_attr: T S = TypeVar("S") @attr.s(auto_attribs=True, eq=False) class Middle(Base[int], Generic[S]): middle_attr: S @attr.s(auto_attribs=True, eq=False) class Sub(Middle[str]): pass reveal_type(Sub.__init__) sub = Sub(base_attr=1, middle_attr='ok') reveal_type(sub) # N: Revealed type is "main.Sub" reveal_type(sub.base_attr) # N: Revealed type is "builtins.int*" reveal_type(sub.middle_attr) # N: Revealed type is "builtins.str*" skip: True # Need to investigate why this is broken - case: testAttrsGenericClassmethod main: | from typing import TypeVar, Generic, Optional import attr T = TypeVar('T') @attr.s(auto_attribs=True) class A(Generic[T]): x: Optional[T] @classmethod def clsmeth(cls) -> None: reveal_type(cls) # N: Revealed type is "type[main.A[T`1]]" - case: testAttrsForwardReference main: | from typing import Optional import attr @attr.s(auto_attribs=True) class A: parent: 'B' @attr.s(auto_attribs=True) class B: parent: Optional[A] reveal_type(A) # N: Revealed type is "def (parent: main.B) -> main.A" reveal_type(B) # N: Revealed type is "def (parent: Union[main.A, None]) -> main.B" A(B(None)) - case: testAttrsForwardReferenceInClass main: | from typing import Optional import attr @attr.s(auto_attribs=True) class A: parent: A.B @attr.s(auto_attribs=True) class B: parent: Optional[A] reveal_type(A) # N: Revealed type is "def (parent: main.A.B) -> main.A" reveal_type(A.B) # N: Revealed type is "def (parent: Union[main.A, None]) -> main.A.B" A(A.B(None)) - case: testAttrsImporting main: | from helper import A reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> helper.A" files: - path: helper.py content: | import attr @attr.s(auto_attribs=True) class A: a: int b: str = attr.ib() - case: testAttrsOtherMethods main: | import attr @attr.s(auto_attribs=True) class A: a: int b: str = attr.ib() @classmethod def new(cls) -> A: reveal_type(cls) # N: Revealed type is "type[main.A]" return cls(6, 'hello') @classmethod def bad(cls) -> A: return cls(17) # E: Missing positional argument "b" in call to "A" [call-arg] def foo(self) -> int: return self.a reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> main.A" a = A.new() reveal_type(a.foo) # N: Revealed type is "def () -> builtins.int" - case: testAttrsOtherOverloads main: | import attr from typing import overload, Union @attr.s class A: a = attr.ib() b = attr.ib(default=3) @classmethod def other(cls) -> str: return "..." @overload @classmethod def foo(cls, x: int) -> int: ... @overload @classmethod def foo(cls, x: str) -> str: ... @classmethod def foo(cls, x: Union[int, str]) -> Union[int, str]: reveal_type(cls) # N: Revealed type is "type[main.A]" reveal_type(cls.other()) # N: Revealed type is "builtins.str" return x reveal_type(A.foo(3)) # N: Revealed type is "builtins.int" reveal_type(A.foo("foo")) # N: Revealed type is "builtins.str" - case: testAttrsDefaultDecorator main: | import attr @attr.s class C: x: int = attr.ib(default=1) y: int = attr.ib() @y.default def name_does_not_matter(self): return self.x + 1 C() - case: testAttrsValidatorDecorator main: | import attr @attr.s class C: x = attr.ib() @x.validator def check(self, attribute, value): if value > 42: raise ValueError("x must be smaller or equal to 42") C(42) C(43) - case: testAttrsLocalVariablesInClassMethod main: | import attr @attr.s(auto_attribs=True) class A: a: int b: int = attr.ib() @classmethod def new(cls, foo: int) -> A: a = foo b = a return cls(a, b) - case: testAttrsUnionForward main: | import attr from typing import Union, List @attr.s(auto_attribs=True) class A: frob: List['AOrB'] class B: pass AOrB = Union[A, B] reveal_type(A) # N: Revealed type is "def (frob: builtins.list[Union[main.A, main.B]]) -> main.A" reveal_type(B) # N: Revealed type is "def () -> main.B" A([B()]) - case: testAttrsUsingConverter main: | import attr import helper def converter2(s:int) -> str: return 'hello' @attr.s class C: x: str = attr.ib(converter=helper.converter) y: str = attr.ib(converter=converter2) # Because of the converter the __init__ takes an int, but the variable is a str. reveal_type(C) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> main.C" reveal_type(C(15, 16).x) # N: Revealed type is "builtins.str" files: - path: helper.py content: | def converter(s:int) -> str: return 'hello' - case: testAttrsUsingBadConverter skip: sys.version_info[:2] < (3, 10) main: | import attr from typing import overload @overload def bad_overloaded_converter(x: int, y: int) -> int: ... @overload def bad_overloaded_converter(x: str, y: str) -> str: ... def bad_overloaded_converter(x, y=7): return x def bad_converter() -> str: return '' @attr.dataclass class A: bad: str = attr.ib(converter=bad_converter) bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) reveal_type(A) out: | main:15: error: Cannot determine __init__ type from converter [misc] main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type] main:16: error: Cannot determine __init__ type from converter [misc] main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type] main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" - case: testAttrsUsingBadConverterReprocess skip: sys.version_info[:2] < (3, 10) main: | import attr from typing import overload forward: 'A' @overload def bad_overloaded_converter(x: int, y: int) -> int: ... @overload def bad_overloaded_converter(x: str, y: str) -> str: ... def bad_overloaded_converter(x, y=7): return x def bad_converter() -> str: return '' @attr.dataclass class A: bad: str = attr.ib(converter=bad_converter) bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) reveal_type(A) out: | main:16: error: Cannot determine __init__ type from converter [misc] main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type] main:17: error: Cannot determine __init__ type from converter [misc] main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any] | Converter[Any, Any] | list[Callable[[Any], Any] | Converter[Any, Any]] | tuple[Callable[[Any], Any] | Converter[Any, Any]] | None" [arg-type] main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" - case: testAttrsUsingUnsupportedConverter main: | import attr class Thing: def do_it(self, int) -> str: return "" thing = Thing() def factory(default: int): return 1 @attr.s class C: x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] y: str = attr.ib(converter=lambda x: x) z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> main.C" - case: testAttrsUsingConverterAndSubclass main: | import attr def converter(s:int) -> str: return 'hello' @attr.s class C: x: str = attr.ib(converter=converter) @attr.s class A(C): pass # Because of the convert the __init__ takes an int, but the variable is a str. reveal_type(A) # N: Revealed type is "def (x: builtins.int) -> main.A" reveal_type(A(15).x) # N: Revealed type is "builtins.str" - case: testAttrsUsingConverterWithTypes main: | from typing import overload import attr @attr.dataclass class A: x: str @attr.s class C: x: complex = attr.ib(converter=complex) y: int = attr.ib(converter=int) z: A = attr.ib(converter=A) o = C("1", "2", "3") o = C(1, 2, "3") - case: testThreeArgConverterTypes main: | from typing import Any from attrs import AttrsInstance, Attribute, Converter def my_converter(value: Any) -> str: """A converter that only takes the value.""" return str(value) def my_converter_with_self(value: Any, self: AttrsInstance) -> str: """This converter takes the value and the self.""" return str(value) def my_converter_with_field(value: Any, field: Attribute) -> str: """This converter takes the value and the field.""" return str(value) reveal_type(Converter(my_converter)) Converter(my_converter_with_self) Converter(my_converter_with_field) reveal_type(Converter(my_converter_with_self, takes_self=True)) Converter(my_converter, takes_self=True) Converter(my_converter_with_field, takes_self=True) reveal_type(Converter(my_converter_with_field, takes_field=True)) Converter(my_converter, takes_field=True) Converter(my_converter_with_self, takes_field=True) out: | main:17: note: Revealed type is "attr.Converter[Any, builtins.str]" main:18: error: Argument 1 to "Converter" has incompatible type "Callable[[Any, AttrsInstance], str]"; expected "Callable[[Any], str]" [arg-type] main:19: error: Argument 1 to "Converter" has incompatible type "Callable[[Any, Attribute[Any]], str]"; expected "Callable[[Any], str]" [arg-type] main:21: note: Revealed type is "attr.Converter[Any, builtins.str]" main:22: error: No overload variant of "Converter" matches argument types "Callable[[Any], str]", "bool" [call-overload] main:22: note: Possible overload variants: main:22: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] main:22: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:22: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] main:22: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:23: error: No overload variant of "Converter" matches argument types "Callable[[Any, Attribute[Any]], str]", "bool" [call-overload] main:23: note: Possible overload variants: main:23: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] main:23: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:23: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] main:23: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:25: note: Revealed type is "attr.Converter[Any, builtins.str]" main:26: error: No overload variant of "Converter" matches argument types "Callable[[Any], str]", "bool" [call-overload] main:26: note: Possible overload variants: main:26: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] main:26: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:26: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] main:26: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:27: error: No overload variant of "Converter" matches argument types "Callable[[Any, AttrsInstance], str]", "bool" [call-overload] main:27: note: Possible overload variants: main:27: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] main:27: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:27: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] main:27: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] - case: testAttrsCmpWithSubclasses regex: true main: | import attr @attr.s class A: pass @attr.s class B: pass @attr.s class C(A, B): pass @attr.s class D(A): pass reveal_type(A.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(B.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(C.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" reveal_type(D.__lt__) # N: Revealed type is "def \[_AT\] \(self: _AT`\d+, other: _AT`\d+\) -> builtins.bool" A() < A() B() < B() A() < B() # E: Unsupported operand types for < \("A" and "B"\) \[operator\] C() > A() C() > B() C() > C() C() > D() # E: Unsupported operand types for > \("C" and "D"\) \[operator\] D() >= A() D() >= B() # E: Unsupported operand types for >= \("D" and "B"\) \[operator\] D() >= C() # E: Unsupported operand types for >= \("D" and "C"\) \[operator\] D() >= D() A() <= 1 # E: Unsupported operand types for <= \("A" and "int"\) \[operator\] B() <= 1 # E: Unsupported operand types for <= \("B" and "int"\) \[operator\] C() <= 1 # E: Unsupported operand types for <= \("C" and "int"\) \[operator\] D() <= 1 # E: Unsupported operand types for <= \("D" and "int"\) \[operator\] - case: testAttrsComplexSuperclass main: | import attr @attr.s class C: x: int = attr.ib(default=1) y: int = attr.ib() @y.default def name_does_not_matter(self): return self.x + 1 @attr.s class A(C): z: int = attr.ib(default=18) reveal_type(C) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =) -> main.C" reveal_type(A) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =, z: builtins.int =) -> main.A" - case: testAttrsMultiAssign main: | import attr @attr.s class A: x, y, z = attr.ib(), attr.ib(type=int), attr.ib(default=17) reveal_type(A) # N: Revealed type is "def (x: Any, y: builtins.int, z: Any =) -> main.A" - case: testAttrsMultiAssign2 main: | import attr @attr.s class A: x = y = z = attr.ib() # E: Too many names for one attribute [misc] - case: testAttrsPrivateInit main: | import attr @attr.s class C: _x = attr.ib(init=False, default=42) C() C(_x=42) # E: Unexpected keyword argument "_x" for "C" [call-arg] - case: testAttrsAutoMustBeAll main: | import attr @attr.s(auto_attribs=True) class A: a: int b = 17 # The following forms are not allowed with auto_attribs=True c = attr.ib() # E: Need type annotation for "c" [var-annotated] d, e = attr.ib(), attr.ib() # E: Need type annotation for "d" [var-annotated] # E: Need type annotation for "e" [var-annotated] f = g = attr.ib() # E: Need type annotation for "f" [var-annotated] # E: Need type annotation for "g" [var-annotated] - case: testAttrsRepeatedName main: | import attr @attr.s class A: a = attr.ib(default=8) b = attr.ib() a = attr.ib() reveal_type(A) # N: Revealed type is "def (b: Any, a: Any) -> main.A" @attr.s class B: a: int = attr.ib(default=8) b: int = attr.ib() a: int = attr.ib() # E: Name "a" already defined on line 10 [no-redef] reveal_type(B) # N: Revealed type is "def (b: builtins.int, a: builtins.int) -> main.B" @attr.s(auto_attribs=True) class C: a: int = 8 b: int a: int = attr.ib() # E: Name "a" already defined on line 16 [no-redef] reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> main.C" - case: testAttrsFrozenSubclass main: | import attr @attr.dataclass class NonFrozenBase: a: int @attr.dataclass(frozen=True) class FrozenBase: a: int @attr.dataclass(frozen=True) class FrozenNonFrozen(NonFrozenBase): b: int @attr.dataclass(frozen=True) class FrozenFrozen(FrozenBase): b: int @attr.dataclass class NonFrozenFrozen(FrozenBase): b: int # Make sure these are untouched non_frozen_base = NonFrozenBase(1) non_frozen_base.a = 17 frozen_base = FrozenBase(1) frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only [misc] a = FrozenNonFrozen(1, 2) a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only [misc] a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only [misc] b = FrozenFrozen(1, 2) b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only [misc] b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only [misc] c = NonFrozenFrozen(1, 2) c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only [misc] c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only [misc] - case: testAttrsCallableAttributes main: | from typing import Callable import attr def blah(a: int, b: int) -> bool: return True @attr.s(auto_attribs=True) class F: _cb: Callable[[int, int], bool] = blah def foo(self) -> bool: return self._cb(5, 6) @attr.s class G: _cb: Callable[[int, int], bool] = attr.ib(blah) def foo(self) -> bool: return self._cb(5, 6) @attr.s(auto_attribs=True, frozen=True) class FFrozen(F): def bar(self) -> bool: return self._cb(5, 6) - case: testAttrsWithFactory main: | from typing import List import attr def my_factory() -> int: return 7 @attr.s class A: x: List[int] = attr.ib(factory=list) y: int = attr.ib(factory=my_factory) A() - case: testAttrsFactoryAndDefault main: | import attr @attr.s class A: x: int = attr.ib(factory=int, default=7) # E: Can't pass both "default" and "factory". [misc] - case: testAttrsFactoryBadReturn regex: true main: | import attr def my_factory() -> int: return 7 @attr.s class A: x: int = attr.ib(factory=list) # E: Incompatible types in assignment \(expression has type "[Ll]ist\[.*\]", variable has type "int"\) \[assignment\] y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment \(expression has type "int", variable has type "str"\) \[assignment\] - case: testAttrsDefaultAndInit main: | import attr @attr.s class C: a = attr.ib(init=False, default=42) b = attr.ib() # Ok because previous attribute is init=False c = attr.ib(default=44) d = attr.ib(init=False) # Ok because this attribute is init=False e = attr.ib() # E: Non-default attributes not allowed after default attributes. [misc] - case: testAttrsOptionalConverter main: | # flags: --strict-optional import attr from attr.converters import optional from typing import Optional def converter(s:int) -> str: return 'hello' @attr.s class A: y: Optional[int] = attr.ib(converter=optional(int)) z: Optional[str] = attr.ib(converter=optional(converter)) A(None, None) - case: testAttrsTypeVarNoCollision main: | from typing import TypeVar, Generic import attr T = TypeVar("T", bytes, str) # Make sure the generated __le__ (and friends) don't use T for their arguments. @attr.s(auto_attribs=True) class A(Generic[T]): v: T - case: testAttrsKwOnlyAttrib main: | import attr @attr.s class A: a = attr.ib(kw_only=True) A() # E: Missing named argument "a" for "A" [call-arg] A(15) # E: Too many positional arguments for "A" [misc] A(a=15) - case: testAttrsKwOnlyClass main: | import attr @attr.s(kw_only=True, auto_attribs=True) class A: a: int b: bool A() # E: Missing named argument "a" for "A" [call-arg] # E: Missing named argument "b" for "A" [call-arg] A(b=True, a=15) - case: testAttrsKwOnlyClassNoInit main: | import attr @attr.s(kw_only=True) class B: a = attr.ib(init=False) b = attr.ib() B(b=True) - case: testAttrsKwOnlyWithDefault main: | import attr @attr.s class C: a = attr.ib(0) b = attr.ib(kw_only=True) c = attr.ib(16, kw_only=True) C(b=17) - case: testAttrsKwOnlyClassWithMixedDefaults main: | import attr @attr.s(kw_only=True) class D: a = attr.ib(10) b = attr.ib() c = attr.ib(15) D(b=17) - case: testAttrsKwOnlySubclass main: | import attr @attr.s class A2: a = attr.ib(default=0) @attr.s class B2(A2): b = attr.ib(kw_only=True) B2(b=1) - case: testAttrsNonKwOnlyAfterKwOnly main: | import attr @attr.s(kw_only=True) class A: a = attr.ib(default=0) @attr.s class B(A): b = attr.ib() @attr.s class C: a = attr.ib(kw_only=True) b = attr.ib(15) - case: testAttrsDisallowUntypedWorksForward main: | # flags: --disallow-untyped-defs import attr from typing import List @attr.s class B: x: C = attr.ib() class C(List[C]): pass reveal_type(B) # N: Revealed type is "def (x: main.C) -> main.B" - case: testDisallowUntypedWorksForwardBad mypy_config: disallow_untyped_defs = True main: | import attr @attr.s class B: x = attr.ib() # E: Need type annotation for "x" [var-annotated] reveal_type(B) # N: Revealed type is "def (x: Any) -> main.B" - case: testAttrsDefaultDecoratorDeferred main: | defer: Yes import attr @attr.s class C: x: int = attr.ib(default=1) y: int = attr.ib() @y.default def inc(self): return self.x + 1 class Yes: ... - case: testAttrsValidatorDecoratorDeferred main: | defer: Yes import attr @attr.s class C: x = attr.ib() @x.validator def check(self, attribute, value): if value > 42: raise ValueError("x must be smaller or equal to 42") C(42) C(43) class Yes: ... - case: testTypeInAttrUndefined main: | import attr @attr.s class C: total = attr.ib(type=Bad) # E: Name "Bad" is not defined [name-defined] - case: testTypeInAttrForwardInRuntime main: | import attr @attr.s class C: total = attr.ib(type=Forward) reveal_type(C.total) # N: Revealed type is "main.Forward" C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" [arg-type] class Forward: ... - case: testDefaultInAttrForward main: | import attr @attr.s class C: total = attr.ib(default=func()) def func() -> int: return 5 C() C(1) C(1, 2) # E: Too many arguments for "C" [call-arg] - case: testTypeInAttrUndefinedFrozen main: | import attr @attr.s(frozen=True) class C: total = attr.ib(type=Bad) # E: Name "Bad" is not defined [name-defined] C(0).total = 1 # E: Property "total" defined in "C" is read-only [misc] - case: testTypeInAttrDeferredStar main: | import lib files: - path: lib.py content: | import attr MYPY = False if MYPY: # Force deferral from other import * @attr.s class C: total = attr.ib(type=int) C() # E: Missing positional argument "total" in call to "C" [call-arg] C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" [arg-type] - path: other.py content: | import lib - case: testAttrsDefaultsMroOtherFile main: | import a files: - path: a.py content: | import attr from b import A1, A2 @attr.s class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. [misc] pass - path: b.py content: | import attr @attr.s class A1: a: str = attr.ib('test') @attr.s class A2: b: int = attr.ib() - case: testAttrsInheritanceNoAnnotation main: | import attr @attr.s class A: foo = attr.ib() # type: int x = 0 @attr.s class B(A): foo = x reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> main.B" - case: testFields regex: true main: | from attrs import define, fields @define class A: a: int b: str reveal_type(fields(A)) # N: Revealed type is "[Tt]uple\[attr.Attribute\[builtins.int\], attr.Attribute\[builtins.str\], fallback=main.A.__main_A_AttrsAttributes__\]" - case: testFieldsError regex: true main: | from attrs import fields class A: a: int b: str fields(A) # E: Argument 1 to "fields" has incompatible type "[Tt]ype\[A\]"; expected "[Tt]ype\[AttrsInstance\]" \[arg-type\] - case: testAsDict main: | from attrs import asdict, define @define class A: a: int asdict(A(1)) - case: testAsDictError main: | from attrs import asdict class A: a: int asdict(A()) # E: Argument 1 to "asdict" has incompatible type "A"; expected "AttrsInstance" [arg-type] - case: testHasTypeGuard main: | from attrs import define, has @define class A: pass reveal_type(A) # N: Revealed type is "def () -> main.A" if has(A): reveal_type(A) # N: Revealed type is "type[attr.AttrsInstance]" - case: testNothingType regex: true main: | from typing import Optional from attrs import NOTHING, NothingType def takes_nothing(arg: Optional[NothingType]) -> None: return None takes_nothing(NOTHING) takes_nothing(None) takes_nothing(1) # E: Argument 1 to "takes_nothing" has incompatible type "Literal\[1\]"; expected "(Optional\[Literal\[_Nothing.NOTHING\]\]|Literal\[_Nothing.NOTHING\] \| None)" \[arg-type\] python-attrs-attrs-bd2446d/tests/test_next_gen.py000066400000000000000000000265411476453530700223440ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Integration tests for next-generation APIs. """ import re from contextlib import contextmanager from functools import partial import pytest import attr as _attr # don't use it by accident import attrs from attr._compat import PY_3_11_PLUS @attrs.define class C: x: str y: int class TestNextGen: def test_simple(self): """ Instantiation works. """ C("1", 2) def test_field_type(self): """ Make class with attrs.field and type parameter. """ classFields = {"testint": attrs.field(type=int)} A = attrs.make_class("A", classFields) assert int is attrs.fields(A).testint.type def test_no_slots(self): """ slots can be deactivated. """ @attrs.define(slots=False) class NoSlots: x: int ns = NoSlots(1) assert {"x": 1} == ns.__dict__ def test_validates(self): """ Validators at __init__ and __setattr__ work. """ @attrs.define class Validated: x: int = attrs.field(validator=attrs.validators.instance_of(int)) v = Validated(1) with pytest.raises(TypeError): Validated(None) with pytest.raises(TypeError): v.x = "1" def test_no_order(self): """ Order is off by default but can be added. """ with pytest.raises(TypeError): C("1", 2) < C("2", 3) @attrs.define(order=True) class Ordered: x: int assert Ordered(1) < Ordered(2) def test_override_auto_attribs_true(self): """ Don't guess if auto_attrib is set explicitly. Having an unannotated attrs.ib/attrs.field fails. """ with pytest.raises(attrs.exceptions.UnannotatedAttributeError): @attrs.define(auto_attribs=True) class ThisFails: x = attrs.field() y: int def test_override_auto_attribs_false(self): """ Don't guess if auto_attrib is set explicitly. Annotated fields that don't carry an attrs.ib are ignored. """ @attrs.define(auto_attribs=False) class NoFields: x: int y: int assert NoFields() == NoFields() def test_auto_attribs_detect(self): """ define correctly detects if a class lacks type annotations. """ @attrs.define class OldSchool: x = attrs.field() assert OldSchool(1) == OldSchool(1) # Test with maybe_cls = None @attrs.define() class OldSchool2: x = attrs.field() assert OldSchool2(1) == OldSchool2(1) def test_auto_attribs_detect_fields_and_annotations(self): """ define infers auto_attribs=True if fields have type annotations """ @attrs.define class NewSchool: x: int y: list = attrs.field() @y.validator def _validate_y(self, attribute, value): if value < 0: raise ValueError("y must be positive") assert NewSchool(1, 1) == NewSchool(1, 1) with pytest.raises(ValueError): NewSchool(1, -1) assert list(attrs.fields_dict(NewSchool).keys()) == ["x", "y"] def test_auto_attribs_partially_annotated(self): """ define infers auto_attribs=True if any type annotations are found """ @attrs.define class NewSchool: x: int y: list z = 10 # fields are defined for any annotated attributes assert NewSchool(1, []) == NewSchool(1, []) assert list(attrs.fields_dict(NewSchool).keys()) == ["x", "y"] # while the unannotated attributes are left as class vars assert NewSchool.z == 10 assert "z" in NewSchool.__dict__ def test_auto_attribs_detect_annotations(self): """ define correctly detects if a class has type annotations. """ @attrs.define class NewSchool: x: int assert NewSchool(1) == NewSchool(1) # Test with maybe_cls = None @attrs.define() class NewSchool2: x: int assert NewSchool2(1) == NewSchool2(1) def test_exception(self): """ Exceptions are detected and correctly handled. """ @attrs.define class E(Exception): msg: str other: int with pytest.raises(E) as ei: raise E("yolo", 42) e = ei.value assert ("yolo", 42) == e.args assert "yolo" == e.msg assert 42 == e.other def test_frozen(self): """ attrs.frozen freezes classes. """ @attrs.frozen class F: x: str f = F(1) with pytest.raises(attrs.exceptions.FrozenInstanceError): f.x = 2 def test_auto_detect_eq(self): """ auto_detect=True works for eq. Regression test for #670. """ @attrs.define class C: def __eq__(self, o): raise ValueError with pytest.raises(ValueError): C() == C() def test_subclass_frozen(self): """ It's possible to subclass an `attrs.frozen` class and the frozen-ness is inherited. """ @attrs.frozen class A: a: int @attrs.frozen class B(A): b: int @attrs.define(on_setattr=attrs.setters.NO_OP) class C(B): c: int assert B(1, 2) == B(1, 2) assert C(1, 2, 3) == C(1, 2, 3) with pytest.raises(attrs.exceptions.FrozenInstanceError): A(1).a = 1 with pytest.raises(attrs.exceptions.FrozenInstanceError): B(1, 2).a = 1 with pytest.raises(attrs.exceptions.FrozenInstanceError): B(1, 2).b = 2 with pytest.raises(attrs.exceptions.FrozenInstanceError): C(1, 2, 3).c = 3 def test_catches_frozen_on_setattr(self): """ Passing frozen=True and on_setattr hooks is caught, even if the immutability is inherited. """ @attrs.define(frozen=True) class A: pass with pytest.raises( ValueError, match="Frozen classes can't use on_setattr." ): @attrs.define(frozen=True, on_setattr=attrs.setters.validate) class B: pass with pytest.raises( ValueError, match=re.escape( "Frozen classes can't use on_setattr " "(frozen-ness was inherited)." ), ): @attrs.define(on_setattr=attrs.setters.validate) class C(A): pass @pytest.mark.parametrize( "decorator", [ partial(_attr.s, frozen=True, slots=True, auto_exc=True), attrs.frozen, attrs.define, attrs.mutable, ], ) def test_discard_context(self, decorator): """ raise from None works. Regression test for #703. """ @decorator class MyException(Exception): x: str = attrs.field() with pytest.raises(MyException) as ei: try: raise ValueError except ValueError: raise MyException("foo") from None assert "foo" == ei.value.x assert ei.value.__cause__ is None @pytest.mark.parametrize( "decorator", [ partial(_attr.s, frozen=True, slots=True, auto_exc=True), attrs.frozen, attrs.define, attrs.mutable, ], ) def test_setting_exception_mutable_attributes(self, decorator): """ contextlib.contextlib (re-)sets __traceback__ on raised exceptions. Ensure that works, as well as if done explicitly """ @decorator class MyException(Exception): pass @contextmanager def do_nothing(): yield with do_nothing(), pytest.raises(MyException) as ei: raise MyException assert isinstance(ei.value, MyException) # this should not raise an exception either ei.value.__traceback__ = ei.value.__traceback__ ei.value.__cause__ = ValueError("cause") ei.value.__context__ = TypeError("context") ei.value.__suppress_context__ = True ei.value.__suppress_context__ = False ei.value.__notes__ = [] del ei.value.__notes__ if PY_3_11_PLUS: ei.value.add_note("note") del ei.value.__notes__ def test_converts_and_validates_by_default(self): """ If no on_setattr is set, assume setters.convert, setters.validate. """ @attrs.define class C: x: int = attrs.field(converter=int) @x.validator def _v(self, _, value): if value < 10: raise ValueError("must be >=10") inst = C(10) # Converts inst.x = "11" assert 11 == inst.x # Validates with pytest.raises(ValueError, match="must be >=10"): inst.x = "9" def test_mro_ng(self): """ Attributes and methods are looked up the same way in NG by default. See #428 """ @attrs.define class A: x: int = 10 def xx(self): return 10 @attrs.define class B(A): y: int = 20 @attrs.define class C(A): x: int = 50 def xx(self): return 50 @attrs.define class D(B, C): pass d = D() assert d.x == d.xx() class TestAsTuple: def test_smoke(self): """ `attrs.astuple` only changes defaults, so we just call it and compare. """ inst = C("foo", 42) assert attrs.astuple(inst) == _attr.astuple(inst) class TestAsDict: def test_smoke(self): """ `attrs.asdict` only changes defaults, so we just call it and compare. """ inst = C("foo", {(1,): 42}) assert attrs.asdict(inst) == _attr.asdict( inst, retain_collection_types=True ) class TestImports: """ Verify our re-imports and mirroring works. """ def test_converters(self): """ Importing from attrs.converters works. """ from attrs.converters import optional assert optional is _attr.converters.optional def test_exceptions(self): """ Importing from attrs.exceptions works. """ from attrs.exceptions import FrozenError assert FrozenError is _attr.exceptions.FrozenError def test_filters(self): """ Importing from attrs.filters works. """ from attrs.filters import include assert include is _attr.filters.include def test_setters(self): """ Importing from attrs.setters works. """ from attrs.setters import pipe assert pipe is _attr.setters.pipe def test_validators(self): """ Importing from attrs.validators works. """ from attrs.validators import and_ assert and_ is _attr.validators.and_ python-attrs-attrs-bd2446d/tests/test_packaging.py000066400000000000000000000020111476453530700224430ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from importlib import metadata import pytest import attr import attrs @pytest.fixture(name="mod", params=(attr, attrs)) def _mod(request): return request.param class TestLegacyMetadataHack: def test_version(self, mod, recwarn): """ __version__ returns the correct version and doesn't warn. """ assert metadata.version("attrs") == mod.__version__ assert [] == recwarn.list def test_does_not_exist(self, mod): """ Asking for unsupported dunders raises an AttributeError. """ with pytest.raises( AttributeError, match=f"module {mod.__name__} has no attribute __yolo__", ): mod.__yolo__ def test_version_info(self, recwarn, mod): """ ___version_info__ is not deprecated, therefore doesn't raise a warning and parses correctly. """ assert isinstance(mod.__version_info__, attr.VersionInfo) assert [] == recwarn.list python-attrs-attrs-bd2446d/tests/test_pattern_matching.py000066400000000000000000000043051476453530700240560ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import pytest import attr class TestPatternMatching: """ Pattern matching syntax test cases. """ @pytest.mark.parametrize("dec", [attr.s, attr.define, attr.frozen]) def test_simple_match_case(self, dec): """ Simple match case statement works as expected with all class decorators. """ @dec class C: a = attr.ib() assert ("a",) == C.__match_args__ matched = False c = C(a=1) match c: case C(a): matched = True assert matched assert 1 == a def test_explicit_match_args(self): """ Does not overwrite a manually set empty __match_args__. """ ma = () @attr.define class C: a = attr.field() __match_args__ = ma c = C(a=1) msg = r"C\(\) accepts 0 positional sub-patterns \(1 given\)" with pytest.raises(TypeError, match=msg): match c: case C(_): pass def test_match_args_kw_only(self): """ kw_only classes don't generate __match_args__. kw_only fields are not included in __match_args__. """ @attr.define class C: a = attr.field(kw_only=True) b = attr.field() assert ("b",) == C.__match_args__ c = C(a=1, b=1) msg = r"C\(\) accepts 1 positional sub-pattern \(2 given\)" with pytest.raises(TypeError, match=msg): match c: case C(a, b): pass found = False match c: case C(b, a=a): found = True assert found @attr.define(kw_only=True) class C: a = attr.field() b = attr.field() c = C(a=1, b=1) msg = r"C\(\) accepts 0 positional sub-patterns \(2 given\)" with pytest.raises(TypeError, match=msg): match c: case C(a, b): pass found = False match c: case C(a=a, b=b): found = True assert found assert (1, 1) == (a, b) python-attrs-attrs-bd2446d/tests/test_pyright.py000066400000000000000000000053561476453530700222240ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from __future__ import annotations import json import shutil import subprocess from pathlib import Path import pytest pytestmark = [ pytest.mark.skipif( shutil.which("pyright") is None, reason="Requires pyright." ), ] def parse_pyright_output(test_file: Path) -> set[tuple[str, str]]: pyright = subprocess.run( # noqa: PLW1510 ["pyright", "--outputjson", str(test_file)], capture_output=True ) pyright_result = json.loads(pyright.stdout) # We use tuples instead of proper classes to get nicer diffs from pytest. return { (d["severity"], d["message"]) for d in pyright_result["generalDiagnostics"] } def test_pyright_baseline(): """ The typing.dataclass_transform decorator allows pyright to determine attrs decorated class types. """ test_file = Path(__file__).parent / "dataclass_transform_example.py" diagnostics = parse_pyright_output(test_file) expected_diagnostics = { ( "information", 'Type of "Define.__init__" is "(self: Define, a: str, b: int) -> None"', ), ( "information", 'Type of "DefineConverter.__init__" is ' '"(self: DefineConverter, with_converter: str | Buffer | ' 'SupportsInt | SupportsIndex | SupportsTrunc) -> None"', ), ( "error", 'Cannot assign to attribute "a" for class ' '"Frozen"\n\xa0\xa0Attribute "a" is read-only', ), ( "information", 'Type of "d.a" is "Literal[\'new\']"', ), ( "error", 'Cannot assign to attribute "a" for class ' '"FrozenDefine"\n\xa0\xa0Attribute "a" is read-only', ), ( "information", 'Type of "d2.a" is "Literal[\'new\']"', ), ( "information", 'Type of "af.__init__" is "(_a: int) -> None"', ), } assert expected_diagnostics == diagnostics def test_pyright_attrsinstance_compat(tmp_path): """ Test that `AttrsInstance` is compatible with Pyright. """ test_pyright_attrsinstance_compat_path = ( tmp_path / "test_pyright_attrsinstance_compat.py" ) test_pyright_attrsinstance_compat_path.write_text( """\ import attrs # We can assign any old object to `AttrsInstance`. foo: attrs.AttrsInstance = object() reveal_type(attrs.AttrsInstance) """ ) diagnostics = parse_pyright_output(test_pyright_attrsinstance_compat_path) expected_diagnostics = { ( "information", 'Type of "attrs.AttrsInstance" is "type[AttrsInstance]"', ) } assert diagnostics == expected_diagnostics python-attrs-attrs-bd2446d/tests/test_setattr.py000066400000000000000000000273221476453530700222210ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import pickle import pytest import attr from attr import setters from attr.exceptions import FrozenAttributeError from attr.validators import instance_of, matches_re @attr.s(frozen=True) class Frozen: x = attr.ib() @attr.s class WithOnSetAttrHook: x = attr.ib(on_setattr=lambda *args: None) class TestSetAttr: def test_change(self): """ The return value of a hook overwrites the value. But they are not run on __init__. """ def hook(*a, **kw): return "hooked!" @attr.s class Hooked: x = attr.ib(on_setattr=hook) y = attr.ib() h = Hooked("x", "y") assert "x" == h.x assert "y" == h.y h.x = "xxx" h.y = "yyy" assert "yyy" == h.y assert "hooked!" == h.x def test_frozen_attribute(self): """ Frozen attributes raise FrozenAttributeError, others are not affected. """ @attr.s class PartiallyFrozen: x = attr.ib(on_setattr=setters.frozen) y = attr.ib() pf = PartiallyFrozen("x", "y") pf.y = "yyy" assert "yyy" == pf.y with pytest.raises(FrozenAttributeError): pf.x = "xxx" assert "x" == pf.x @pytest.mark.parametrize( "on_setattr", [setters.validate, [setters.validate], setters.pipe(setters.validate)], ) def test_validator(self, on_setattr): """ Validators are run and they don't alter the value. """ @attr.s(on_setattr=on_setattr) class ValidatedAttribute: x = attr.ib() y = attr.ib(validator=[instance_of(str), matches_re("foo.*qux")]) va = ValidatedAttribute(42, "foobarqux") with pytest.raises(TypeError) as ei: va.y = 42 assert "foobarqux" == va.y assert ei.value.args[0].startswith("'y' must be <") with pytest.raises(ValueError) as ei: va.y = "quxbarfoo" assert ei.value.args[0].startswith("'y' must match regex '") assert "foobarqux" == va.y va.y = "foobazqux" assert "foobazqux" == va.y def test_pipe(self): """ Multiple hooks are possible, in that case the last return value is used. They can be supplied using the pipe functions or by passing a list to on_setattr. """ taken = None def takes_all(val, instance, attrib): nonlocal taken taken = val, instance, attrib return val s = [setters.convert, lambda _, __, nv: nv + 1] @attr.s class Piped: x1 = attr.ib( converter=[ attr.Converter( takes_all, takes_field=True, takes_self=True ), int, ], on_setattr=setters.pipe(*s), ) x2 = attr.ib(converter=int, on_setattr=s) p = Piped("41", "22") assert ("41", p) == taken[:-1] assert "x1" == taken[-1].name assert 41 == p.x1 assert 22 == p.x2 p.x1 = "41" p.x2 = "22" assert 42 == p.x1 assert 23 == p.x2 def test_make_class(self): """ on_setattr of make_class gets forwarded. """ C = attr.make_class("C", {"x": attr.ib()}, on_setattr=setters.frozen) c = C(1) with pytest.raises(FrozenAttributeError): c.x = 2 def test_no_validator_no_converter(self): """ validate and convert tolerate missing validators and converters. """ @attr.s(on_setattr=[setters.convert, setters.validate]) class C: x = attr.ib() c = C(1) c.x = 2 assert 2 == c.x def test_validate_respects_run_validators_config(self): """ If run validators is off, validate doesn't run them. """ @attr.s(on_setattr=setters.validate) class C: x = attr.ib(validator=attr.validators.instance_of(int)) c = C(1) attr.set_run_validators(False) c.x = "1" assert "1" == c.x attr.set_run_validators(True) with pytest.raises(TypeError) as ei: c.x = "1" assert ei.value.args[0].startswith("'x' must be <") def test_frozen_on_setattr_class_is_caught(self): """ @attr.s(on_setattr=X, frozen=True) raises an ValueError. """ with pytest.raises(ValueError) as ei: @attr.s(frozen=True, on_setattr=setters.validate) class C: x = attr.ib() assert "Frozen classes can't use on_setattr." == ei.value.args[0] def test_frozen_on_setattr_attribute_is_caught(self): """ attr.ib(on_setattr=X) on a frozen class raises an ValueError. """ with pytest.raises(ValueError) as ei: @attr.s(frozen=True) class C: x = attr.ib(on_setattr=setters.validate) assert "Frozen classes can't use on_setattr." == ei.value.args[0] def test_setattr_reset_if_no_custom_setattr(self, slots): """ If a class with an active setattr is subclassed and no new setattr is generated, the __setattr__ is set to object.__setattr__. We do the double test because of Python 2. """ def boom(*args): pytest.fail("Must not be called.") @attr.s class Hooked: x = attr.ib(on_setattr=boom) @attr.s(slots=slots) class NoHook(WithOnSetAttrHook): x = attr.ib() assert NoHook.__setattr__ == object.__setattr__ assert 1 == NoHook(1).x assert Hooked.__attrs_own_setattr__ assert not NoHook.__attrs_own_setattr__ assert WithOnSetAttrHook.__attrs_own_setattr__ def test_setattr_inherited_do_not_reset(self, slots): """ If we inherit a __setattr__ that has been written by the user, we must not reset it unless necessary. """ class A: """ Not an attrs class on purpose to prevent accidental resets that would render the asserts meaningless. """ def __setattr__(self, *args): pass @attr.s(slots=slots) class B(A): pass assert B.__setattr__ == A.__setattr__ @attr.s(slots=slots) class C(B): pass assert C.__setattr__ == A.__setattr__ def test_pickling_retains_attrs_own(self, slots): """ Pickling/Unpickling does not lose ownership information about __setattr__. """ i = WithOnSetAttrHook(1) assert True is i.__attrs_own_setattr__ i2 = pickle.loads(pickle.dumps(i)) assert True is i2.__attrs_own_setattr__ WOSAH = pickle.loads(pickle.dumps(WithOnSetAttrHook)) assert True is WOSAH.__attrs_own_setattr__ def test_slotted_class_can_have_custom_setattr(self): """ A slotted class can define a custom setattr and it doesn't get overwritten. Regression test for #680. """ @attr.s(slots=True) class A: def __setattr__(self, key, value): raise SystemError with pytest.raises(SystemError): A().x = 1 @pytest.mark.xfail(raises=attr.exceptions.FrozenAttributeError) def test_slotted_confused(self): """ If we have a in-between non-attrs class, setattr reset detection should still work, but currently doesn't. It works with dict classes because we can look the finished class and patch it. With slotted classes we have to deduce it ourselves. """ @attr.s(slots=True) class A: x = attr.ib(on_setattr=setters.frozen) class B(A): pass @attr.s(slots=True) class C(B): x = attr.ib() C(1).x = 2 def test_setattr_auto_detect_if_no_custom_setattr(self, slots): """ It's possible to remove the on_setattr hook from an attribute and therefore write a custom __setattr__. """ assert 1 == WithOnSetAttrHook(1).x @attr.s(auto_detect=True, slots=slots) class RemoveNeedForOurSetAttr(WithOnSetAttrHook): x = attr.ib() def __setattr__(self, name, val): object.__setattr__(self, name, val * 2) i = RemoveNeedForOurSetAttr(1) assert not RemoveNeedForOurSetAttr.__attrs_own_setattr__ assert 2 == i.x def test_setattr_restore_respects_auto_detect(self, slots): """ If __setattr__ should be restored but the user supplied its own and set auto_detect, leave is alone. """ @attr.s(auto_detect=True, slots=slots) class CustomSetAttr: def __setattr__(self, _, __): pass assert CustomSetAttr.__setattr__ != object.__setattr__ def test_setattr_auto_detect_frozen(self, slots): """ frozen=True together with a detected custom __setattr__ are rejected. """ with pytest.raises( ValueError, match="Can't freeze a class with a custom __setattr__." ): @attr.s(auto_detect=True, slots=slots, frozen=True) class CustomSetAttr(Frozen): def __setattr__(self, _, __): pass def test_setattr_auto_detect_on_setattr(self, slots): """ on_setattr attributes together with a detected custom __setattr__ are rejected. """ with pytest.raises( ValueError, match="Can't combine custom __setattr__ with on_setattr hooks.", ): @attr.s(auto_detect=True, slots=slots) class HookAndCustomSetAttr: x = attr.ib(on_setattr=lambda *args: None) def __setattr__(self, _, __): pass @pytest.mark.parametrize("a_slots", [True, False]) @pytest.mark.parametrize("b_slots", [True, False]) @pytest.mark.parametrize("c_slots", [True, False]) def test_setattr_inherited_do_not_reset_intermediate( self, a_slots, b_slots, c_slots ): """ A user-provided intermediate __setattr__ is not reset to object.__setattr__. This only can work with auto_detect activated, such that attrs can know that there is a user-provided __setattr__. """ @attr.s(slots=a_slots) class A: x = attr.ib(on_setattr=setters.frozen) @attr.s(slots=b_slots, auto_detect=True) class B(A): x = attr.ib(on_setattr=setters.NO_OP) def __setattr__(self, key, value): raise SystemError @attr.s(slots=c_slots) class C(B): pass assert getattr(A, "__attrs_own_setattr__", False) is True assert getattr(B, "__attrs_own_setattr__", False) is False assert getattr(C, "__attrs_own_setattr__", False) is False with pytest.raises(SystemError): C(1).x = 3 def test_docstring(self): """ Generated __setattr__ has a useful docstring. """ assert ( "Method generated by attrs for class WithOnSetAttrHook." == WithOnSetAttrHook.__setattr__.__doc__ ) def test_setattr_converter_piped(self): """ If a converter is used, it is piped through the on_setattr hooks. Regression test for https://github.com/python-attrs/attrs/issues/1327 """ @attr.define # converter on setattr is implied in NG class C: x = attr.field(converter=[int]) c = C("1") c.x = "2" assert 2 == c.x python-attrs-attrs-bd2446d/tests/test_slots.py000066400000000000000000000644311476453530700217010ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Unit tests for slots-related functionality. """ import functools import pickle import weakref from unittest import mock import pytest import attr import attrs from attr._compat import PY_3_14_PLUS, PYPY # Pympler doesn't work on PyPy. try: from pympler.asizeof import asizeof has_pympler = True except BaseException: # Won't be an import error. # noqa: BLE001 has_pympler = False @attr.s class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() def method(self): return self.x @classmethod def classmethod(cls): return "clsmethod" @staticmethod def staticmethod(): return "staticmethod" def my_class(self): return __class__ def my_super(self): """Just to test out the no-arg super.""" return super().__repr__() @attr.s(slots=True, unsafe_hash=True) class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() def method(self): return self.x @classmethod def classmethod(cls): return "clsmethod" @staticmethod def staticmethod(): return "staticmethod" def my_class(self): return __class__ def my_super(self): """Just to test out the no-arg super.""" return super().__repr__() def test_slots_being_used(): """ The class is really using __slots__. """ non_slot_instance = C1(x=1, y="test") slot_instance = C1Slots(x=1, y="test") assert "__dict__" not in dir(slot_instance) assert "__slots__" in dir(slot_instance) assert "__dict__" in dir(non_slot_instance) assert "__slots__" not in dir(non_slot_instance) assert {"__weakref__", "x", "y"} == set(slot_instance.__slots__) if has_pympler: assert asizeof(slot_instance) < asizeof(non_slot_instance) non_slot_instance.t = "test" with pytest.raises(AttributeError): slot_instance.t = "test" assert 1 == non_slot_instance.method() assert 1 == slot_instance.method() assert attr.fields(C1Slots) == attr.fields(C1) assert attr.asdict(slot_instance) == attr.asdict(non_slot_instance) def test_basic_attr_funcs(): """ Comparison, `__eq__`, `__hash__`, `__repr__`, `attrs.asdict` work. """ a = C1Slots(x=1, y=2) b = C1Slots(x=1, y=3) a_ = C1Slots(x=1, y=2) # Comparison. assert b > a assert a_ == a # Hashing. hash(b) # Just to assert it doesn't raise. # Repr. assert "C1Slots(x=1, y=2)" == repr(a) assert {"x": 1, "y": 2} == attr.asdict(a) def test_inheritance_from_nonslots(): """ Inheritance from a non-slotted class works. Note that a slotted class inheriting from an ordinary class loses most of the benefits of slotted classes, but it should still work. """ @attr.s(slots=True, unsafe_hash=True) class C2Slots(C1): z = attr.ib() c2 = C2Slots(x=1, y=2, z="test") assert 1 == c2.x assert 2 == c2.y assert "test" == c2.z c2.t = "test" # This will work, using the base class. assert "test" == c2.t assert 1 == c2.method() assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() assert {"z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 c2_ = C2Slots(x=1, y=2, z="test") assert c2 == c2_ assert "C2Slots(x=1, y=2, z='test')" == repr(c2) hash(c2) # Just to assert it doesn't raise. assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) def test_nonslots_these(): """ Enhancing a dict class using 'these' works. This will actually *replace* the class with another one, using slots. """ class SimpleOrdinaryClass: def __init__(self, x, y, z): self.x = x self.y = y self.z = z def method(self): return self.x @classmethod def classmethod(cls): return "clsmethod" @staticmethod def staticmethod(): return "staticmethod" C2Slots = attr.s( these={"x": attr.ib(), "y": attr.ib(), "z": attr.ib()}, init=False, slots=True, unsafe_hash=True, )(SimpleOrdinaryClass) c2 = C2Slots(x=1, y=2, z="test") assert 1 == c2.x assert 2 == c2.y assert "test" == c2.z with pytest.raises(AttributeError): c2.t = "test" # We have slots now. assert 1 == c2.method() assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() assert {"__weakref__", "x", "y", "z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 c2_ = C2Slots(x=1, y=2, z="test") assert c2 == c2_ assert "SimpleOrdinaryClass(x=1, y=2, z='test')" == repr(c2) hash(c2) # Just to assert it doesn't raise. assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) def test_inheritance_from_slots(): """ Inheriting from an attrs slotted class works. """ @attr.s(slots=True, unsafe_hash=True) class C2Slots(C1Slots): z = attr.ib() @attr.s(slots=True, unsafe_hash=True) class C2(C1): z = attr.ib() c2 = C2Slots(x=1, y=2, z="test") assert 1 == c2.x assert 2 == c2.y assert "test" == c2.z assert {"z"} == set(C2Slots.__slots__) assert 1 == c2.method() assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() with pytest.raises(AttributeError): c2.t = "test" non_slot_instance = C2(x=1, y=2, z="test") if has_pympler: assert asizeof(c2) < asizeof(non_slot_instance) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 c2_ = C2Slots(x=1, y=2, z="test") assert c2 == c2_ assert "C2Slots(x=1, y=2, z='test')" == repr(c2) hash(c2) # Just to assert it doesn't raise. assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) def test_inheritance_from_slots_with_attribute_override(): """ Inheriting from a slotted class doesn't re-create existing slots """ class HasXSlot: __slots__ = ("x",) @attr.s(slots=True, unsafe_hash=True) class C2Slots(C1Slots): # y re-defined here but it shouldn't get a slot y = attr.ib() z = attr.ib() @attr.s(slots=True, unsafe_hash=True) class NonAttrsChild(HasXSlot): # Parent class has slot for "x" already, so we skip it x = attr.ib() y = attr.ib() z = attr.ib() c2 = C2Slots(1, 2, "test") assert 1 == c2.x assert 2 == c2.y assert "test" == c2.z assert {"z"} == set(C2Slots.__slots__) na = NonAttrsChild(1, 2, "test") assert 1 == na.x assert 2 == na.y assert "test" == na.z assert {"__weakref__", "y", "z"} == set(NonAttrsChild.__slots__) def test_inherited_slot_reuses_slot_descriptor(): """ We reuse slot descriptor for an attr.ib defined in a slotted attr.s """ class HasXSlot: __slots__ = ("x",) class OverridesX(HasXSlot): @property def x(self): return None @attr.s(slots=True) class Child(OverridesX): x = attr.ib() assert Child.x is not OverridesX.x assert Child.x is HasXSlot.x c = Child(1) assert 1 == c.x assert set() == set(Child.__slots__) ox = OverridesX() assert ox.x is None def test_bare_inheritance_from_slots(): """ Inheriting from a bare attrs slotted class works. """ @attr.s( init=False, eq=False, order=False, unsafe_hash=False, repr=False, slots=True, ) class C1BareSlots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() def method(self): return self.x @classmethod def classmethod(cls): return "clsmethod" @staticmethod def staticmethod(): return "staticmethod" @attr.s(init=False, eq=False, order=False, unsafe_hash=False, repr=False) class C1Bare: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() def method(self): return self.x @classmethod def classmethod(cls): return "clsmethod" @staticmethod def staticmethod(): return "staticmethod" @attr.s(slots=True, unsafe_hash=True) class C2Slots(C1BareSlots): z = attr.ib() @attr.s(slots=True, unsafe_hash=True) class C2(C1Bare): z = attr.ib() c2 = C2Slots(x=1, y=2, z="test") assert 1 == c2.x assert 2 == c2.y assert "test" == c2.z assert 1 == c2.method() assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() with pytest.raises(AttributeError): c2.t = "test" non_slot_instance = C2(x=1, y=2, z="test") if has_pympler: assert asizeof(c2) < asizeof(non_slot_instance) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 c2_ = C2Slots(x=1, y=2, z="test") assert c2 == c2_ assert "C2Slots(x=1, y=2, z='test')" == repr(c2) hash(c2) # Just to assert it doesn't raise. assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) class TestClosureCellRewriting: def test_closure_cell_rewriting(self): """ Slotted classes support proper closure cell rewriting. This affects features like `__class__` and the no-arg super(). """ non_slot_instance = C1(x=1, y="test") slot_instance = C1Slots(x=1, y="test") assert non_slot_instance.my_class() is C1 assert slot_instance.my_class() is C1Slots # Just assert they return something, and not an exception. assert non_slot_instance.my_super() assert slot_instance.my_super() def test_inheritance(self): """ Slotted classes support proper closure cell rewriting when inheriting. This affects features like `__class__` and the no-arg super(). """ @attr.s class C2(C1): def my_subclass(self): return __class__ @attr.s class C2Slots(C1Slots): def my_subclass(self): return __class__ non_slot_instance = C2(x=1, y="test") slot_instance = C2Slots(x=1, y="test") assert non_slot_instance.my_class() is C1 assert slot_instance.my_class() is C1Slots # Just assert they return something, and not an exception. assert non_slot_instance.my_super() assert slot_instance.my_super() assert non_slot_instance.my_subclass() is C2 assert slot_instance.my_subclass() is C2Slots def test_cls_static(self, slots): """ Slotted classes support proper closure cell rewriting for class- and static methods. """ # Python can reuse closure cells, so we create new classes just for # this test. @attr.s(slots=slots) class C: @classmethod def clsmethod(cls): return __class__ assert C.clsmethod() is C @attr.s(slots=slots) class D: @staticmethod def statmethod(): return __class__ assert D.statmethod() is D @pytest.mark.skipif(PYPY, reason="__slots__ only block weakref on CPython") def test_not_weakrefable(): """ Instance is not weak-referenceable when `weakref_slot=False` in CPython. """ @attr.s(slots=True, weakref_slot=False) class C: pass c = C() with pytest.raises(TypeError): weakref.ref(c) @pytest.mark.skipif( not PYPY, reason="slots without weakref_slot should only work on PyPy" ) def test_implicitly_weakrefable(): """ Instance is weak-referenceable even when `weakref_slot=False` in PyPy. """ @attr.s(slots=True, weakref_slot=False) class C: pass c = C() w = weakref.ref(c) assert c is w() def test_weakrefable(): """ Instance is weak-referenceable when `weakref_slot=True`. """ @attr.s(slots=True, weakref_slot=True) class C: pass c = C() w = weakref.ref(c) assert c is w() def test_weakref_does_not_add_a_field(): """ `weakref_slot=True` does not add a field to the class. """ @attr.s(slots=True, weakref_slot=True) class C: field = attr.ib() assert [f.name for f in attr.fields(C)] == ["field"] def tests_weakref_does_not_add_when_inheriting_with_weakref(): """ `weakref_slot=True` does not add a new __weakref__ slot when inheriting one. """ @attr.s(slots=True, weakref_slot=True) class C: pass @attr.s(slots=True, weakref_slot=True) class D(C): pass d = D() w = weakref.ref(d) assert d is w() def tests_weakref_does_not_add_with_weakref_attribute(): """ `weakref_slot=True` does not add a new __weakref__ slot when an attribute of that name exists. """ @attr.s(slots=True, weakref_slot=True) class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) c = C() w = weakref.ref(c) assert c is w() def test_slots_empty_cell(): """ Tests that no `ValueError: Cell is empty` exception is raised when closure cells are present with no contents in a `slots=True` class. (issue https://github.com/python-attrs/attrs/issues/589) If a method mentions `__class__` or uses the no-arg `super()`, the compiler will bake a reference to the class in the method itself as `method.__closure__`. Since `attrs` replaces the class with a clone, `_ClassBuilder._create_slots_class(self)` will rewrite these references so it keeps working. This method was not properly covering the edge case where the closure cell was empty, we fixed it and this is the non-regression test. """ @attr.s(slots=True) class C: field = attr.ib() def f(self, a): super(C, self).__init__() # noqa: UP008 C(field=1) @attr.s(getstate_setstate=True) class C2: x = attr.ib() @attr.s(slots=True, getstate_setstate=True) class C2Slots: x = attr.ib() class TestPickle: @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL)) def test_pickleable_by_default(self, protocol): """ If nothing else is passed, slotted classes can be pickled and unpickled with all supported protocols. """ i1 = C1Slots(1, 2) i2 = pickle.loads(pickle.dumps(i1, protocol)) assert i1 == i2 assert i1 is not i2 def test_no_getstate_setstate_for_dict_classes(self): """ As long as getstate_setstate is None, nothing is done to dict classes. """ assert getattr(object, "__getstate__", None) is getattr( C1, "__getstate__", None ) assert getattr(object, "__setstate__", None) is getattr( C1, "__setstate__", None ) def test_no_getstate_setstate_if_option_false(self): """ Don't add getstate/setstate if getstate_setstate is False. """ @attr.s(slots=True, getstate_setstate=False) class C: x = attr.ib() assert getattr(object, "__getstate__", None) is getattr( C, "__getstate__", None ) assert getattr(object, "__setstate__", None) is getattr( C, "__setstate__", None ) @pytest.mark.parametrize("cls", [C2(1), C2Slots(1)]) def test_getstate_set_state_force_true(self, cls): """ If getstate_setstate is True, add them unconditionally. """ assert None is not getattr(cls, "__getstate__", None) assert None is not getattr(cls, "__setstate__", None) def test_slots_super_property_get(): """ Both `super()` and `super(self.__class__, self)` work. """ @attr.s(slots=True) class A: x = attr.ib() @property def f(self): return self.x @attr.s(slots=True) class B(A): @property def f(self): return super().f ** 2 @attr.s(slots=True) class C(A): @property def f(self): return super(C, self).f ** 2 # noqa: UP008 assert B(11).f == 121 assert B(17).f == 289 assert C(11).f == 121 assert C(17).f == 289 def test_slots_super_property_get_shortcut(): """ The `super()` shortcut is allowed. """ @attr.s(slots=True) class A: x = attr.ib() @property def f(self): return self.x @attr.s(slots=True) class B(A): @property def f(self): return super().f ** 2 assert B(11).f == 121 assert B(17).f == 289 def test_slots_cached_property_allows_call(): """ cached_property in slotted class allows call. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x assert A(11).f == 11 def test_slots_cached_property_class_does_not_have__dict__(): """ slotted class with cached property has no __dict__ attribute. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x assert set(A.__slots__) == {"x", "f", "__weakref__"} assert "__dict__" not in dir(A) def test_slots_cached_property_works_on_frozen_isntances(): """ Infers type of cached property. """ @attrs.frozen(slots=True) class A: x: int @functools.cached_property def f(self) -> int: return self.x assert A(x=1).f == 1 @pytest.mark.xfail( PY_3_14_PLUS, reason="3.14 returns weird annotation for cached_properies" ) def test_slots_cached_property_infers_type(): """ Infers type of cached property. """ @attrs.frozen(slots=True) class A: x: int @functools.cached_property def f(self) -> int: return self.x assert A.__annotations__ == {"x": int, "f": int} def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requested(): """ Ensures error information is not lost. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x a = A(1) with pytest.raises( AttributeError, match="'A' object has no attribute 'z'" ): a.z def test_slots_cached_property_raising_attributeerror(): """ Ensures AttributeError raised by a property is preserved by __getattr__() implementation. Regression test for issue https://github.com/python-attrs/attrs/issues/1230 """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.p @property def p(self): raise AttributeError("I am a property") @functools.cached_property def g(self): return self.q @property def q(self): return 2 a = A(1) with pytest.raises(AttributeError, match=r"^I am a property$"): a.p with pytest.raises(AttributeError, match=r"^I am a property$"): a.f assert a.g == 2 assert a.q == 2 def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes(): """ Ensure __getattr__ implementation is maintained for non cached_properties. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x def __getattr__(self, item): return item a = A(1) assert a.f == 1 assert a.z == "z" def test_slots_getattr_in_superclass__is_called_for_missing_attributes_when_cached_property_present(): """ Ensure __getattr__ implementation is maintained in subclass. """ @attr.s(slots=True) class A: x = attr.ib() def __getattr__(self, item): return item @attr.s(slots=True) class B(A): @functools.cached_property def f(self): return self.x b = B(1) assert b.f == 1 assert b.z == "z" def test_slots_getattr_in_subclass_gets_superclass_cached_property(): """ Ensure super() in __getattr__ is not broken through cached_property re-write. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x def __getattr__(self, item): return item @attr.s(slots=True) class B(A): @functools.cached_property def g(self): return self.x def __getattr__(self, item): return super().__getattr__(item) b = B(1) assert b.f == 1 assert b.z == "z" def test_slots_sub_class_with_independent_cached_properties_both_work(): """ Subclassing shouldn't break cached properties. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x @attr.s(slots=True) class B(A): @functools.cached_property def g(self): return self.x * 2 assert B(1).f == 1 assert B(1).g == 2 def test_slots_with_multiple_cached_property_subclasses_works(): """ Multiple sub-classes shouldn't break cached properties. """ @attr.s(slots=True) class A: x = attr.ib(kw_only=True) @functools.cached_property def f(self): return self.x @attr.s(slots=False) class B: @functools.cached_property def g(self): return self.x * 2 def __getattr__(self, item): if hasattr(super(), "__getattr__"): return super().__getattr__(item) return item @attr.s(slots=True) class AB(A, B): pass ab = AB(x=1) assert ab.f == 1 assert ab.g == 2 assert ab.h == "h" def test_slotted_cached_property_can_access_super(): """ Multiple sub-classes shouldn't break cached properties. """ @attr.s(slots=True) class A: x = attr.ib(kw_only=True) @attr.s(slots=True) class B(A): @functools.cached_property def f(self): return super().x * 2 assert B(x=1).f == 2 def test_slots_sub_class_avoids_duplicated_slots(): """ Duplicating the slots is a waste of memory. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x @attr.s(slots=True) class B(A): @functools.cached_property def f(self): return self.x * 2 assert B(1).f == 2 assert B.__slots__ == () def test_slots_sub_class_with_actual_slot(): """ A sub-class can have an explicit attrs field that replaces a cached property. """ @attr.s(slots=True) class A: # slots : (x, f) x = attr.ib() @functools.cached_property def f(self): return self.x @attr.s(slots=True) class B(A): f: int = attr.ib() assert B(1, 2).f == 2 assert B.__slots__ == () def test_slots_cached_property_is_not_called_at_construction(): """ A cached property function should only be called at property access point. """ call_count = 0 @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): nonlocal call_count call_count += 1 return self.x A(1) assert call_count == 0 def test_slots_cached_property_repeat_call_only_once(): """ A cached property function should be called only once, on repeated attribute access. """ call_count = 0 @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): nonlocal call_count call_count += 1 return self.x obj = A(1) obj.f obj.f assert call_count == 1 def test_slots_cached_property_called_independent_across_instances(): """ A cached property value should be specific to the given instance. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f(self): return self.x obj_1 = A(1) obj_2 = A(2) assert obj_1.f == 1 assert obj_2.f == 2 def test_slots_cached_properties_work_independently(): """ Multiple cached properties should work independently. """ @attr.s(slots=True) class A: x = attr.ib() @functools.cached_property def f_1(self): return self.x @functools.cached_property def f_2(self): return self.x * 2 obj = A(1) assert obj.f_1 == 1 assert obj.f_2 == 2 @attr.s(slots=True) class A: x = attr.ib() b = attr.ib() c = attr.ib() def test_slots_unpickle_after_attr_removed(): """ We don't assign attributes we don't have anymore if the class has removed it. """ a = A(1, 2, 3) a_pickled = pickle.dumps(a) a_unpickled = pickle.loads(a_pickled) assert a_unpickled == a @attr.s(slots=True) class NEW_A: x = attr.ib() c = attr.ib() with mock.patch(f"{__name__}.A", NEW_A): new_a = pickle.loads(a_pickled) assert new_a.x == 1 assert new_a.c == 3 assert not hasattr(new_a, "b") def test_slots_unpickle_after_attr_added(frozen): """ We don't assign attribute we haven't had before if the class has one added. """ a = A(1, 2, 3) a_pickled = pickle.dumps(a) a_unpickled = pickle.loads(a_pickled) assert a_unpickled == a @attr.s(slots=True, frozen=frozen) class NEW_A: x = attr.ib() b = attr.ib() d = attr.ib() c = attr.ib() with mock.patch(f"{__name__}.A", NEW_A): new_a = pickle.loads(a_pickled) assert new_a.x == 1 assert new_a.b == 2 assert new_a.c == 3 assert not hasattr(new_a, "d") def test_slots_unpickle_is_backward_compatible(frozen): """ Ensure object pickled before v22.2.0 can still be unpickled. """ a = A(1, 2, 3) a_pickled = ( b"\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x10" + a.__module__.encode() + b"\x94\x8c\x01A\x94\x93\x94)\x81\x94K\x01K\x02K\x03\x87\x94b." ) a_unpickled = pickle.loads(a_pickled) assert a_unpickled == a python-attrs-attrs-bd2446d/tests/test_utils.py000066400000000000000000000006701476453530700216700ustar00rootroot00000000000000from .utils import simple_class class TestSimpleClass: """ Tests for the testing helper function `make_class`. """ def test_returns_class(self): """ Returns a class object. """ assert type is simple_class().__class__ def test_returns_distinct_classes(self): """ Each call returns a completely new class. """ assert simple_class() is not simple_class() python-attrs-attrs-bd2446d/tests/test_validators.py000066400000000000000000001041401476453530700226750ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Tests for `attr.validators`. """ import re import pytest import attr from attr import _config, fields, has from attr import validators as validator_module from attr.validators import ( _subclass_of, and_, deep_iterable, deep_mapping, ge, gt, in_, instance_of, is_callable, le, lt, matches_re, max_len, min_len, not_, optional, or_, ) from .utils import simple_attr class TestDisableValidators: @pytest.fixture(autouse=True) def _reset_default(self): """ Make sure validators are always enabled after a test. """ yield _config._run_validators = True def test_default(self): """ Run validators by default. """ assert _config._run_validators is True @pytest.mark.parametrize( ("value", "expected"), [(True, False), (False, True)] ) def test_set_validators_disabled(self, value, expected): """ Sets `_run_validators`. """ validator_module.set_disabled(value) assert _config._run_validators is expected @pytest.mark.parametrize( ("value", "expected"), [(True, False), (False, True)] ) def test_disabled(self, value, expected): """ Returns `_run_validators`. """ _config._run_validators = value assert validator_module.get_disabled() is expected def test_disabled_ctx(self): """ The `disabled` context manager disables running validators, but only within its context. """ assert _config._run_validators is True with validator_module.disabled(): assert _config._run_validators is False assert _config._run_validators is True def test_disabled_ctx_with_errors(self): """ Running validators is re-enabled even if an error is raised. """ assert _config._run_validators is True with pytest.raises(ValueError), validator_module.disabled(): assert _config._run_validators is False raise ValueError("haha!") assert _config._run_validators is True class TestInstanceOf: """ Tests for `instance_of`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert instance_of.__name__ in validator_module.__all__ def test_success(self): """ Nothing happens if types match. """ v = instance_of(int) v(None, simple_attr("test"), 42) def test_subclass(self): """ Subclasses are accepted too. """ v = instance_of(int) # yep, bools are a subclass of int :( v(None, simple_attr("test"), True) def test_fail(self): """ Raises `TypeError` on wrong types. """ v = instance_of(int) a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, "42") assert ( "'test' must be (got '42' that is a ).", a, int, "42", ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = instance_of(int) assert (">") == repr(v) class TestMatchesRe: """ Tests for `matches_re`. """ def test_in_all(self): """ validator is in ``__all__``. """ assert matches_re.__name__ in validator_module.__all__ def test_match(self): """ Silent on matches, raises ValueError on mismatches. """ @attr.s class ReTester: str_match = attr.ib(validator=matches_re("a|ab")) ReTester("ab") # shouldn't raise exceptions with pytest.raises(TypeError): ReTester(1) with pytest.raises(ValueError): ReTester("1") with pytest.raises(ValueError): ReTester("a1") def test_flags(self): """ Flags are propagated to the match function. """ @attr.s class MatchTester: val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match)) MatchTester("A1") # test flags and using re.match def test_precompiled_pattern(self): """ Pre-compiled patterns are accepted. """ pattern = re.compile("a") @attr.s class RePatternTester: val = attr.ib(validator=matches_re(pattern)) RePatternTester("a") def test_precompiled_pattern_no_flags(self): """ A pre-compiled pattern cannot be combined with a 'flags' argument. """ pattern = re.compile("") with pytest.raises( TypeError, match="can only be used with a string pattern" ): matches_re(pattern, flags=re.IGNORECASE) def test_different_func(self): """ Changing the match functions works. """ @attr.s class SearchTester: val = attr.ib(validator=matches_re("a", 0, re.search)) SearchTester("bab") # re.search will match def test_catches_invalid_func(self): """ Invalid match functions are caught. """ with pytest.raises(ValueError) as ei: matches_re("a", 0, lambda: None) assert ( "'func' must be one of None, fullmatch, match, search." == ei.value.args[0] ) @pytest.mark.parametrize( "func", [None, getattr(re, "fullmatch", None), re.match, re.search] ) def test_accepts_all_valid_func(self, func): """ Every valid match function is accepted. """ matches_re("a", func=func) def test_repr(self): """ __repr__ is meaningful. """ assert repr(matches_re("a")).startswith( " (got '42' that is a ).", a, int, "42", ) == e.value.args def test_repr(self, validator): """ Returned validator has a useful `__repr__`. """ v = optional(validator) if isinstance(validator, list): repr_s = ( f">]) or None>" ) elif isinstance(validator, tuple): repr_s = ( f">)) or None>" ) else: repr_s = ( "> or None>" ) assert repr_s == repr(v) class TestIn_: """ Tests for `in_`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert in_.__name__ in validator_module.__all__ def test_success_with_value(self): """ If the value is in our options, nothing happens. """ v = in_([1, 2, 3]) a = simple_attr("test") v(1, a, 3) def test_fail(self): """ Raise ValueError if the value is outside our options. """ v = in_([1, 2, 3]) a = simple_attr("test") with pytest.raises(ValueError) as e: v(None, a, None) assert ( "'test' must be in [1, 2, 3] (got None)", a, [1, 2, 3], None, ) == e.value.args def test_fail_with_string(self): """ Raise ValueError if the value is outside our options when the options are specified as a string and the value is not a string. """ v = in_("abc") a = simple_attr("test") with pytest.raises(ValueError) as e: v(None, a, None) assert ( "'test' must be in 'abc' (got None)", a, "abc", None, ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = in_([3, 4, 5]) assert ("") == repr(v) def test_is_hashable(self): """ `in_` is hashable, so fields using it can be used with the include and exclude filters. """ @attr.s class C: x: int = attr.ib(validator=attr.validators.in_({1, 2})) i = C(2) attr.asdict(i, filter=attr.filters.include(lambda val: True)) attr.asdict(i, filter=attr.filters.exclude(lambda val: True)) @pytest.fixture( name="member_validator", params=( instance_of(int), [always_pass, instance_of(int)], (always_pass, instance_of(int)), ), scope="module", ) def _member_validator(request): """ Provides sample `member_validator`s for some tests in `TestDeepIterable` """ return request.param class TestDeepIterable: """ Tests for `deep_iterable`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert deep_iterable.__name__ in validator_module.__all__ def test_success_member_only(self, member_validator): """ If the member validator succeeds and the iterable validator is not set, nothing happens. """ v = deep_iterable(member_validator) a = simple_attr("test") v(None, a, [42]) def test_success_member_and_iterable(self, member_validator): """ If both the member and iterable validators succeed, nothing happens. """ iterable_validator = instance_of(list) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") v(None, a, [42]) @pytest.mark.parametrize( ("member_validator", "iterable_validator"), [ (instance_of(int), 42), (42, instance_of(list)), (42, 42), (42, None), ([instance_of(int), 42], 42), ([42, instance_of(int)], 42), ], ) def test_noncallable_validators( self, member_validator, iterable_validator ): """ Raise `TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_iterable(member_validator, iterable_validator) value = 42 message = ( f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] assert value == e.value.args[1] assert message in e.value.msg assert value == e.value.value def test_fail_invalid_member(self, member_validator): """ Raise member validator error if an invalid member is found. """ v = deep_iterable(member_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) def test_fail_invalid_iterable(self, member_validator): """ Raise iterable validator error if an invalid iterable is found. """ member_validator = instance_of(int) iterable_validator = instance_of(tuple) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42]) def test_fail_invalid_member_and_iterable(self, member_validator): """ Raise iterable validator error if both the iterable and a member are invalid. """ iterable_validator = instance_of(tuple) v = deep_iterable(member_validator, iterable_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, [42, "42"]) def test_repr_member_only(self): """ Returned validator has a useful `__repr__` when only member validator is set. """ member_validator = instance_of(int) member_repr = ">" v = deep_iterable(member_validator) expected_repr = ( f"" ) assert expected_repr == repr(v) def test_repr_member_only_sequence(self): """ Returned validator has a useful `__repr__` when only member validator is set and the member validator is a list of validators """ member_validator = [always_pass, instance_of(int)] member_repr = ( f"_AndValidator(_validators=({always_pass!r}, " ">))" ) v = deep_iterable(member_validator) expected_repr = ( f"" ) assert expected_repr == repr(v) def test_repr_member_and_iterable(self): """ Returned validator has a useful `__repr__` when both member and iterable validators are set. """ member_validator = instance_of(int) member_repr = ">" iterable_validator = instance_of(list) iterable_repr = ">" v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "" ) assert expected_repr == repr(v) def test_repr_sequence_member_and_iterable(self): """ Returned validator has a useful `__repr__` when both member and iterable validators are set and the member validator is a list of validators """ member_validator = [always_pass, instance_of(int)] member_repr = ( f"_AndValidator(_validators=({always_pass!r}, " ">))" ) iterable_validator = instance_of(list) iterable_repr = ">" v = deep_iterable(member_validator, iterable_validator) expected_repr = ( "" ) assert expected_repr == repr(v) class TestDeepMapping: """ Tests for `deep_mapping`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert deep_mapping.__name__ in validator_module.__all__ def test_success(self): """ If both the key and value validators succeed, nothing happens. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") v(None, a, {"a": 6, "b": 7}) @pytest.mark.parametrize( ("key_validator", "value_validator", "mapping_validator"), [ (42, instance_of(int), None), (instance_of(str), 42, None), (instance_of(str), instance_of(int), 42), (42, 42, None), (42, 42, 42), ], ) def test_noncallable_validators( self, key_validator, value_validator, mapping_validator ): """ Raise `TypeError` if any validators are not callable. """ with pytest.raises(TypeError) as e: deep_mapping(key_validator, value_validator, mapping_validator) value = 42 message = ( f"must be callable (got {value} that is a {value.__class__})." ) assert message in e.value.args[0] assert value == e.value.args[1] assert message in e.value.msg assert value == e.value.value def test_fail_invalid_mapping(self): """ Raise `TypeError` if mapping validator fails. """ key_validator = instance_of(str) value_validator = instance_of(int) mapping_validator = instance_of(dict) v = deep_mapping(key_validator, value_validator, mapping_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, None) def test_fail_invalid_key(self): """ Raise key validator error if an invalid key is found. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, {"a": 6, 42: 7}) def test_fail_invalid_member(self): """ Raise key validator error if an invalid member value is found. """ key_validator = instance_of(str) value_validator = instance_of(int) v = deep_mapping(key_validator, value_validator) a = simple_attr("test") with pytest.raises(TypeError): v(None, a, {"a": "6", "b": 7}) def test_repr(self): """ Returned validator has a useful `__repr__`. """ key_validator = instance_of(str) key_repr = ">" value_validator = instance_of(int) value_repr = ">" v = deep_mapping(key_validator, value_validator) expected_repr = ( "" ) assert expected_repr == repr(v) class TestIsCallable: """ Tests for `is_callable`. """ def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert is_callable.__name__ in validator_module.__all__ def test_success(self): """ If the value is callable, nothing happens. """ v = is_callable() a = simple_attr("test") v(None, a, isinstance) def test_fail(self): """ Raise TypeError if the value is not callable. """ v = is_callable() a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, None) value = None message = "'test' must be callable (got {value} that is a {type_})." expected_message = message.format(value=value, type_=value.__class__) assert expected_message == e.value.args[0] assert value == e.value.args[1] assert expected_message == e.value.msg assert value == e.value.value def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = is_callable() assert "" == repr(v) def test_exception_repr(self): """ Verify that NotCallableError exception has a useful `__str__`. """ from attr.exceptions import NotCallableError instance = NotCallableError(msg="Some Message", value=42) assert "Some Message" == str(instance) def test_hashability(): """ Validator classes are hashable. """ for obj_name in dir(validator_module): obj = getattr(validator_module, obj_name) if not has(obj): continue hash_func = getattr(obj, "__hash__", None) assert hash_func is not None assert hash_func is not object.__hash__ class TestLtLeGeGt: """ Tests for `Lt, Le, Ge, Gt`. """ BOUND = 4 def test_in_all(self): """ validator is in ``__all__``. """ assert all( f.__name__ in validator_module.__all__ for f in [lt, le, ge, gt] ) @pytest.mark.parametrize("v", [lt, le, ge, gt]) def test_retrieve_bound(self, v): """ The configured bound for the comparison can be extracted from the Attribute. """ @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) assert fields(Tester).value.validator.bound == self.BOUND @pytest.mark.parametrize( ("v", "value"), [ (lt, 3), (le, 3), (le, 4), (ge, 4), (ge, 5), (gt, 5), ], ) def test_check_valid(self, v, value): """Silent if value {op} bound.""" @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( ("v", "value"), [ (lt, 4), (le, 5), (ge, 3), (gt, 4), ], ) def test_check_invalid(self, v, value): """Raise ValueError if value {op} bound.""" @attr.s class Tester: value = attr.ib(validator=v(self.BOUND)) with pytest.raises(ValueError): Tester(value) @pytest.mark.parametrize("v", [lt, le, ge, gt]) def test_repr(self, v): """ __repr__ is meaningful. """ nv = v(23) assert repr(nv) == f"" class TestMaxLen: """ Tests for `max_len`. """ MAX_LENGTH = 4 def test_in_all(self): """ validator is in ``__all__``. """ assert max_len.__name__ in validator_module.__all__ def test_retrieve_max_len(self): """ The configured max. length can be extracted from the Attribute """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) assert fields(Tester).value.validator.max_length == self.MAX_LENGTH @pytest.mark.parametrize( "value", [ "", "foo", "spam", [], list(range(MAX_LENGTH)), {"spam": 3, "eggs": 4}, ], ) def test_check_valid(self, value): """ Silent if len(value) <= max_len. Values can be strings and other iterables. """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( "value", [ "bacon", list(range(6)), ], ) def test_check_invalid(self, value): """ Raise ValueError if len(value) > max_len. """ @attr.s class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) with pytest.raises(ValueError): Tester(value) def test_repr(self): """ __repr__ is meaningful. """ assert repr(max_len(23)) == "" class TestMinLen: """ Tests for `min_len`. """ MIN_LENGTH = 2 def test_in_all(self): """ validator is in ``__all__``. """ assert min_len.__name__ in validator_module.__all__ def test_retrieve_min_len(self): """ The configured min. length can be extracted from the Attribute """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) assert fields(Tester).value.validator.min_length == self.MIN_LENGTH @pytest.mark.parametrize( "value", [ "foo", "spam", list(range(MIN_LENGTH)), {"spam": 3, "eggs": 4}, ], ) def test_check_valid(self, value): """ Silent if len(value) => min_len. Values can be strings and other iterables. """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) Tester(value) # shouldn't raise exceptions @pytest.mark.parametrize( "value", [ "", list(range(1)), ], ) def test_check_invalid(self, value): """ Raise ValueError if len(value) < min_len. """ @attr.s class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) with pytest.raises(ValueError): Tester(value) def test_repr(self): """ __repr__ is meaningful. """ assert repr(min_len(23)) == "" class TestSubclassOf: """ Tests for `_subclass_of`. """ def test_success(self): """ Nothing happens if classes match. """ v = _subclass_of(int) v(None, simple_attr("test"), int) def test_subclass(self): """ Subclasses are accepted too. """ v = _subclass_of(int) # yep, bools are a subclass of int :( v(None, simple_attr("test"), bool) def test_fail(self): """ Raises `TypeError` on wrong types. """ v = _subclass_of(int) a = simple_attr("test") with pytest.raises(TypeError) as e: v(None, a, str) assert ( "'test' must be a subclass of (got ).", a, int, str, ) == e.value.args def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = _subclass_of(int) assert (">") == repr(v) class TestNot_: """ Tests for `not_`. """ DEFAULT_EXC_TYPES = (ValueError, TypeError) def test_not_all(self): """ The validator is in ``__all__``. """ assert not_.__name__ in validator_module.__all__ def test_repr(self): """ Returned validator has a useful `__repr__`. """ wrapped = in_([3, 4, 5]) v = not_(wrapped) assert ( f"" ) == repr(v) def test_success_because_fails(self): """ If the wrapped validator fails, we're happy. """ def always_fails(inst, attr, value): raise ValueError("always fails") v = not_(always_fails) a = simple_attr("test") input_value = 3 v(1, a, input_value) def test_fails_because_success(self): """ If the wrapped validator doesn't fail, not_ should fail. """ def always_passes(inst, attr, value): pass v = not_(always_passes) a = simple_attr("test") input_value = 3 with pytest.raises(ValueError) as e: v(1, a, input_value) assert ( ( f"not_ validator child '{always_passes!r}' did not raise a captured error" ), a, always_passes, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_in_pass(self): """ Check something is ``not in`` something else. """ v = not_(in_("abc")) a = simple_attr("test") input_value = "d" v(None, a, input_value) def test_composable_with_in_fail(self): """ Check something is ``not in`` something else, but it is, so fail. """ wrapped = in_("abc") v = not_(wrapped) a = simple_attr("test") input_value = "b" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( "not_ validator child '{!r}' did not raise a captured error" ).format(in_("abc")), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_matches_re_pass(self): """ Check something does not match a regex. """ v = not_(matches_re("[a-z]{3}")) a = simple_attr("test") input_value = "spam" v(None, a, input_value) def test_composable_with_matches_re_fail(self): """ Check something does not match a regex, but it does, so fail. """ wrapped = matches_re("[a-z]{3}") v = not_(wrapped) a = simple_attr("test") input_value = "egg" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( f"not_ validator child '{wrapped!r}' did not raise a captured error" ), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_composable_with_instance_of_pass(self): """ Check something is not a type. This validator raises a TypeError, rather than a ValueError like the others. """ v = not_(instance_of((int, float))) a = simple_attr("test") v(None, a, "spam") def test_composable_with_instance_of_fail(self): """ Check something is not a type, but it is, so fail. """ wrapped = instance_of((int, float)) v = not_(wrapped) a = simple_attr("test") input_value = 2.718281828 with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( ( f"not_ validator child '{instance_of((int, float))!r}' did not raise a captured error" ), a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_custom_capture_match(self): """ Match a custom exception provided to `not_` """ v = not_(in_("abc"), exc_types=ValueError) a = simple_attr("test") v(None, a, "d") def test_custom_capture_miss(self): """ If the exception doesn't match, the underlying raise comes through """ class MyError(Exception): """:(""" wrapped = in_("abc") v = not_(wrapped, exc_types=MyError) a = simple_attr("test") input_value = "d" with pytest.raises(ValueError) as e: v(None, a, input_value) # get the underlying exception to compare with pytest.raises(Exception) as e_from_wrapped: wrapped(None, a, input_value) assert e_from_wrapped.value.args == e.value.args def test_custom_msg(self): """ If provided, use the custom message in the raised error """ custom_msg = "custom message!" wrapped = in_("abc") v = not_(wrapped, msg=custom_msg) a = simple_attr("test") input_value = "a" with pytest.raises(ValueError) as e: v(None, a, input_value) assert ( custom_msg, a, wrapped, input_value, self.DEFAULT_EXC_TYPES, ) == e.value.args def test_bad_exception_args(self): """ Malformed exception arguments """ wrapped = in_("abc") with pytest.raises(TypeError) as e: not_(wrapped, exc_types=(str, int)) assert ( "'exc_types' must be a subclass of " "(got )." ) == e.value.args[0] class TestOr: def test_in_all(self): """ Verify that this validator is in ``__all__``. """ assert or_.__name__ in validator_module.__all__ def test_success(self): """ Succeeds if at least one of wrapped validators succeed. """ v = or_(instance_of(str), always_pass) v(None, simple_attr("test"), 42) def test_fail(self): """ Fails if all wrapped validators fail. """ v = or_(instance_of(str), always_fail) with pytest.raises(ValueError): v(None, simple_attr("test"), 42) def test_repr(self): """ Returned validator has a useful `__repr__`. """ v = or_(instance_of(int), instance_of(str)) assert ( ">, >)>" ) == repr(v) python-attrs-attrs-bd2446d/tests/test_version_info.py000066400000000000000000000026711476453530700232330ustar00rootroot00000000000000# SPDX-License-Identifier: MIT import pytest from attr import VersionInfo @pytest.fixture(name="vi") def fixture_vi(): return VersionInfo(19, 2, 0, "final") class TestVersionInfo: def test_from_string_no_releaselevel(self, vi): """ If there is no suffix, the releaselevel becomes "final" by default. """ assert vi == VersionInfo._from_version_string("19.2.0") def test_suffix_is_preserved(self): """ If there is a suffix, it's preserved. """ assert ( "dev0" == VersionInfo._from_version_string("19.2.0.dev0").releaselevel ) @pytest.mark.parametrize("other", [(), (19, 2, 0, "final", "garbage")]) def test_wrong_len(self, vi, other): """ Comparing with a tuple that has the wrong length raises an error. """ assert vi != other with pytest.raises(TypeError): vi < other @pytest.mark.parametrize("other", [[19, 2, 0, "final"]]) def test_wrong_type(self, vi, other): """ Only compare to other VersionInfos or tuples. """ assert vi != other def test_order(self, vi): """ Ordering works as expected. """ assert vi < (20,) assert vi < (19, 2, 1) assert vi > (0,) assert vi <= (19, 2) assert vi >= (19, 2) assert vi > (19, 2, 0, "dev0") assert vi < (19, 2, 0, "post1") python-attrs-attrs-bd2446d/tests/typing_example.py000066400000000000000000000221521476453530700225150ustar00rootroot00000000000000# SPDX-License-Identifier: MIT from __future__ import annotations import re from typing import Any, Dict, List, Tuple import attr import attrs # Typing via "type" Argument --- @attr.s class C: a = attr.ib(type=int) c = C(1) C(a=1) @attr.s class D: x = attr.ib(type=List[int]) @attr.s class E: y = attr.ib(type="List[int]") @attr.s class F: z = attr.ib(type=Any) # Typing via Annotations --- @attr.s class CC: a: int = attr.ib() cc = CC(1) CC(a=1) @attr.s class DD: x: list[int] = attr.ib() @attr.s class EE: y: "list[int]" = attr.ib() @attr.s class FF: z: Any = attr.ib() @attrs.define class FFF: z: int FFF(1) # Inheritance -- @attr.s class GG(DD): y: str = attr.ib() GG(x=[1], y="foo") @attr.s class HH(DD, EE): z: float = attr.ib() HH(x=[1], y=[], z=1.1) # same class c == cc # Exceptions @attr.s(auto_exc=True) class Error(Exception): x: int = attr.ib() try: raise Error(1) except Error as e: e.x e.args str(e) @attrs.define class Error2(Exception): x: int try: raise Error2(1) except Error as e: e.x e.args str(e) # Field aliases @attrs.define class AliasExample: without_alias: int _with_alias: int = attr.ib(alias="_with_alias") attr.fields(AliasExample).without_alias.alias attr.fields(AliasExample)._with_alias.alias # Converters @attr.s class ConvCOptional: x: int | None = attr.ib(converter=attr.converters.optional(int)) ConvCOptional(1) ConvCOptional(None) # XXX: Fails with E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] # See https://github.com/python/mypy/issues/15736 # # @attr.s # class ConvCPipe: # x: str = attr.ib(converter=attr.converters.pipe(int, str)) # # # ConvCPipe(3.4) # ConvCPipe("09") # # # @attr.s # class ConvCDefaultIfNone: # x: int = attr.ib(converter=attr.converters.default_if_none(42)) # # # ConvCDefaultIfNone(1) # ConvCDefaultIfNone(None) @attr.s class ConvCToBool: x: int = attr.ib(converter=attr.converters.to_bool) ConvCToBool(1) ConvCToBool(True) ConvCToBool("on") ConvCToBool("yes") ConvCToBool(0) ConvCToBool(False) ConvCToBool("n") # Validators @attr.s class Validated: a = attr.ib( type=List[C], validator=attr.validators.deep_iterable( attr.validators.instance_of(C), attr.validators.instance_of(list) ), ) aa = attr.ib( type=Tuple[C], validator=attr.validators.deep_iterable( attr.validators.instance_of(C), attr.validators.instance_of(tuple) ), ) b = attr.ib( type=List[C], validator=attr.validators.deep_iterable( attr.validators.instance_of(C) ), ) c = attr.ib( type=Dict[C, D], validator=attr.validators.deep_mapping( attr.validators.instance_of(C), attr.validators.instance_of(D), attr.validators.instance_of(dict), ), ) d = attr.ib( type=Dict[C, D], validator=attr.validators.deep_mapping( attr.validators.instance_of(C), attr.validators.instance_of(D) ), ) e: str = attr.ib(validator=attr.validators.matches_re(re.compile(r"foo"))) f: str = attr.ib( validator=attr.validators.matches_re(r"foo", flags=42, func=re.search) ) # Test different forms of instance_of g: int = attr.ib(validator=attr.validators.instance_of(int)) h: int = attr.ib(validator=attr.validators.instance_of((int,))) j: int | str = attr.ib(validator=attr.validators.instance_of((int, str))) k: int | str | C = attr.ib( validator=attrs.validators.instance_of((int, C, str)) ) kk: int | str | C = attr.ib( validator=attrs.validators.instance_of(int | C | str) ) l: Any = attr.ib( validator=attr.validators.not_(attr.validators.in_("abc")) ) m: Any = attr.ib( validator=attr.validators.not_( attr.validators.in_("abc"), exc_types=ValueError ) ) n: Any = attr.ib( validator=attr.validators.not_( attr.validators.in_("abc"), exc_types=(ValueError,) ) ) o: Any = attr.ib( validator=attr.validators.not_(attr.validators.in_("abc"), msg="spam") ) p: Any = attr.ib( validator=attr.validators.not_(attr.validators.in_("abc"), msg=None) ) q: Any = attr.ib( validator=attrs.validators.optional(attrs.validators.instance_of(C)) ) r: Any = attr.ib( validator=attrs.validators.optional([attrs.validators.instance_of(C)]) ) s: Any = attr.ib( validator=attrs.validators.optional((attrs.validators.instance_of(C),)) ) @attr.define class Validated2: num: int = attr.field(validator=attr.validators.ge(0)) @attrs.define class Validated3: num: int = attrs.field(validator=attrs.validators.ge(0)) with attr.validators.disabled(): Validated2(num=-1) with attrs.validators.disabled(): Validated3(num=-1) try: attr.validators.set_disabled(True) Validated2(num=-1) finally: attr.validators.set_disabled(False) # Custom repr() @attr.s class WithCustomRepr: a: int = attr.ib(repr=True) b: str = attr.ib(repr=False) c: str = attr.ib(repr=lambda value: "c is for cookie") d: bool = attr.ib(repr=str) @attrs.define class WithCustomRepr2: a: int = attrs.field(repr=True) b: str = attrs.field(repr=False) c: str = attrs.field(repr=lambda value: "c is for cookie") d: bool = attrs.field(repr=str) # Check some of our own types @attr.s(eq=True, order=False) class OrderFlags: a: int = attr.ib(eq=False, order=False) b: int = attr.ib(eq=True, order=True) # on_setattr hooks @attr.s(on_setattr=attr.setters.validate) class ValidatedSetter: a: int b: str = attr.ib(on_setattr=attr.setters.NO_OP) c: bool = attr.ib(on_setattr=attr.setters.frozen) d: int = attr.ib(on_setattr=[attr.setters.convert, attr.setters.validate]) e: bool = attr.ib( on_setattr=attr.setters.pipe( attr.setters.convert, attr.setters.validate ) ) @attrs.define(on_setattr=attr.setters.validate) class ValidatedSetter2: a: int b: str = attrs.field(on_setattr=attrs.setters.NO_OP) c: bool = attrs.field(on_setattr=attrs.setters.frozen) d: int = attrs.field( on_setattr=[attrs.setters.convert, attrs.setters.validate] ) e: bool = attrs.field( on_setattr=attrs.setters.pipe( attrs.setters.convert, attrs.setters.validate ) ) # field_transformer def ft_hook(cls: type, attribs: list[attr.Attribute]) -> list[attr.Attribute]: return attribs # field_transformer def ft_hook2( cls: type, attribs: list[attrs.Attribute] ) -> list[attrs.Attribute]: return attribs @attr.s(field_transformer=ft_hook) class TransformedAttrs: x: int @attrs.define(field_transformer=ft_hook2) class TransformedAttrs2: x: int # Auto-detect @attr.s(auto_detect=True) class AutoDetect: x: int def __init__(self, x: int): self.x = x # Provisional APIs @attr.define(order=True) class NGClass: x: int = attr.field(default=42) ngc = NGClass(1) @attr.mutable(slots=False) class NGClass2: x: int ngc2 = NGClass2(1) @attr.frozen(str=True) class NGFrozen: x: int ngf = NGFrozen(1) attr.fields(NGFrozen).x.evolve(eq=False) a = attr.fields(NGFrozen).x a.evolve(repr=False) attrs.fields(NGFrozen).x.evolve(eq=False) a = attrs.fields(NGFrozen).x a.evolve(repr=False) @attr.s(collect_by_mro=True) class MRO: pass @attr.s class FactoryTest: a: list[int] = attr.ib(default=attr.Factory(list)) b: list[Any] = attr.ib(default=attr.Factory(list, False)) c: list[int] = attr.ib(default=attr.Factory((lambda s: s.a), True)) @attrs.define class FactoryTest2: a: list[int] = attrs.field(default=attrs.Factory(list)) b: list[Any] = attrs.field(default=attrs.Factory(list, False)) c: list[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True)) attrs.asdict(FactoryTest2()) attr.asdict(FactoryTest(), tuple_keys=True) # Check match_args stub @attr.s(match_args=False) class MatchArgs: a: int = attr.ib() b: int = attr.ib() attr.asdict(FactoryTest()) attr.asdict(FactoryTest(), retain_collection_types=False) # Check match_args stub @attrs.define(match_args=False) class MatchArgs2: a: int b: int # NG versions of asdict/astuple attrs.asdict(MatchArgs2(1, 2)) attrs.astuple(MatchArgs2(1, 2)) def accessing_from_attr() -> None: """ Use a function to keep the ns clean. """ attr.converters.optional attr.exceptions.FrozenError attr.filters.include attr.filters.exclude attr.setters.frozen attr.validators.and_ attr.cmp_using def accessing_from_attrs() -> None: """ Use a function to keep the ns clean. """ attrs.converters.optional attrs.exceptions.FrozenError attrs.filters.include attrs.filters.exclude attrs.setters.frozen attrs.validators.and_ attrs.cmp_using foo = object if attrs.has(foo) or attr.has(foo): foo.__attrs_attrs__ @attrs.define(unsafe_hash=True) class Hashable: pass def test(cls: type) -> None: if attr.has(cls): attr.resolve_types(cls) python-attrs-attrs-bd2446d/tests/utils.py000066400000000000000000000023401476453530700206250ustar00rootroot00000000000000# SPDX-License-Identifier: MIT """ Common helper functions for tests. """ from attr import Attribute from attr._make import NOTHING, _default_init_alias_for, make_class def simple_class( eq=False, order=False, repr=False, unsafe_hash=False, str=False, slots=False, frozen=False, cache_hash=False, ): """ Return a new simple class. """ return make_class( "C", ["a", "b"], eq=eq or order, order=order, repr=repr, unsafe_hash=unsafe_hash, init=True, slots=slots, str=str, frozen=frozen, cache_hash=cache_hash, ) def simple_attr( name, default=NOTHING, validator=None, repr=True, eq=True, hash=None, init=True, converter=None, kw_only=False, inherited=False, ): """ Return an attribute with a name and no other bells and whistles. """ return Attribute( name=name, default=default, validator=validator, repr=repr, cmp=None, eq=eq, hash=hash, init=init, converter=converter, kw_only=kw_only, inherited=inherited, alias=_default_init_alias_for(name), ) python-attrs-attrs-bd2446d/tox.ini000066400000000000000000000070651476453530700172750ustar00rootroot00000000000000[tox] min_version = 4 env_list = pre-commit, py3{8,9,10,11,12,13}-tests, py3{10,11,12,13}-mypy, pypy3-tests, pyright, docs-{sponsors,doctests}, changelog, coverage-report [pkgenv] pass_env = SETUPTOOLS_SCM_PRETEND_VERSION [testenv] package = wheel wheel_build_env = .pkg extras = tests: tests mypy: tests-mypy commands = tests: pytest {posargs:-n auto} mypy: mypy tests/typing_example.py mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi [testenv:pypy3-tests] extras = tests commands = pytest tests/test_functional.py [testenv:py3{8,10,13}-tests] extras = cov # Python 3.6+ has a number of compile-time warnings on invalid string escapes. # PYTHONWARNINGS=d makes them visible during the tox run. set_env = COVERAGE_PROCESS_START={toxinidir}/pyproject.toml PYTHONWARNINGS=d commands_pre = python -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")' # We group xdist execution by file because otherwise the Mypy tests have race conditions. commands = coverage run -m pytest {posargs:-n auto --dist loadfile} [testenv:coverage-report] # Keep base_python in-sync with .python-version-default base_python = py313 # Keep depends in-sync with testenv above that has cov extra. depends = py3{8,10,13}-tests skip_install = true deps = coverage[toml]>=5.3 commands = coverage combine coverage report [testenv:codspeed] extras = benchmark pass_env = CODSPEED_TOKEN CODSPEED_ENV ARCH PYTHONHASHSEED PYTHONMALLOC commands = pytest --codspeed -n auto bench/test_benchmarks.py [testenv:docs-{build,doctests,linkcheck}] # Keep base_python in sync with .readthedocs.yaml. base_python = py313 extras = docs commands = build: sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html doctests: sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html linkcheck: sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/html [testenv:docs-watch] package = editable base_python = {[testenv:docs-build]base_python} extras = {[testenv:docs-build]extras} deps = watchfiles commands = watchfiles \ --ignore-paths docs/_build/ \ 'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs docs/_build/html' \ src \ docs [testenv:docs-sponsors] description = Ensure sponsor logos are up to date. deps = cogapp commands = cog -rP README.md docs/index.md [testenv:pre-commit] skip_install = true deps = pre-commit-uv commands = pre-commit run --all-files [testenv:changelog] # See https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92 # Pin also present in pyproject.toml deps = towncrier skip_install = true commands = towncrier --version towncrier build --version main --draft [testenv:pyright] extras = tests deps = pyright>=1.1.380 commands = pytest tests/test_pyright.py -vv [testenv:docset] deps = doc2dash extras = docs allowlist_externals = rm cp tar commands = rm -rf attrs.docset attrs.tgz docs/_build sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html doc2dash --index-page index.html --icon docs/_static/docset-icon.png --icon-2x docs/_static/docset-icon@2x.png --online-redirect-url https://www.attrs.org/en/latest/ docs/_build/html tar --exclude='.DS_Store' -cvzf attrs.tgz attrs.docset