[
  {
    "path": ".editorconfig",
    "content": "root = True\n\n[*]\nend_of_line = lf\ninsert_final_newline = True\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = True\n\n[*.py]\ncharset = utf-8\n\n[*.{yml,yaml,md,toml}]\nindent_size = 2\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Citizen Code of Conduct\n\n## 1. Purpose\n\nA primary goal of Phantom Types is to be inclusive to the largest number of\ncontributors, with the most varied and diverse backgrounds possible. As such, we are\ncommitted to providing a friendly, safe and welcoming environment for all, regardless of\ngender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or\nlack thereof).\n\nThis code of conduct outlines our expectations for all those who participate in our\ncommunity, as well as the consequences for unacceptable behavior.\n\nWe invite all those who participate in Phantom Types to help us create safe and positive\nexperiences for everyone.\n\n## 2. Open [Source/Culture/Tech] Citizenship\n\nA supplemental goal of this Code of Conduct is to increase open [source/culture/tech]\ncitizenship by encouraging participants to recognize and strengthen the relationships\nbetween our actions and their effects on our community.\n\nCommunities mirror the societies in which they exist and positive action is essential to\ncounteract the many forms of inequality and abuses of power that exist in society.\n\nIf you see someone who is making an extra effort to ensure our community is welcoming,\nfriendly, and encourages all participants to contribute to the fullest extent, we want\nto know.\n\n## 3. Expected Behavior\n\nThe following behaviors are expected and requested of all community members:\n\n- Participate in an authentic and active way. In doing so, you contribute to the health\n  and longevity of this community.\n- Exercise consideration and respect in your speech and actions.\n- Attempt collaboration before conflict.\n- Refrain from demeaning, discriminatory, or harassing behavior and speech.\n- Be mindful of your surroundings and of your fellow participants. Alert community\n  leaders if you notice a dangerous situation, someone in distress, or violations of\n  this Code of Conduct, even if they seem inconsequential.\n- Remember that community event venues may be shared with members of the public; please\n  be respectful to all patrons of these locations.\n\n## 4. Unacceptable Behavior\n\nThe following behaviors are considered harassment and are unacceptable within our\ncommunity:\n\n- Violence, threats of violence or violent language directed against another person.\n- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and\n  language.\n- Posting or displaying sexually explicit or violent material.\n- Posting or threatening to post other people's personally identifying information\n  (\"doxing\").\n- Personal insults, particularly those related to gender, sexual orientation, race,\n  religion, or disability.\n- Inappropriate photography or recording.\n- Inappropriate physical contact. You should have someone's consent before touching\n  them.\n- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate\n  touching, groping, and unwelcomed sexual advances.\n- Deliberate intimidation, stalking or following (online or in person).\n- Advocating for, or encouraging, any of the above behavior.\n- Sustained disruption of community events, including talks and presentations.\n\n## 5. Weapons Policy\n\nNo weapons will be allowed at Phantom Types events, community spaces, or in other spaces\ncovered by the scope of this Code of Conduct. Weapons include but are not limited to\nguns, explosives (including fireworks), and large knives such as those used for hunting\nor display, as well as any other item used for the purpose of causing injury or harm to\nothers. Anyone seen in possession of one of these items will be asked to leave\nimmediately, and will only be allowed to return without the weapon. Community members\nare further expected to comply with all state and local laws on this matter.\n\n## 6. Consequences of Unacceptable Behavior\n\nUnacceptable behavior from any community member, including sponsors and those with\ndecision-making authority, will not be tolerated.\n\nAnyone asked to stop unacceptable behavior is expected to comply immediately.\n\nIf a community member engages in unacceptable behavior, the community organizers may\ntake any action they deem appropriate, up to and including a temporary ban or permanent\nexpulsion from the community without warning (and without refund in the case of a paid\nevent).\n\n## 7. Addressing Grievances\n\nIf you feel you have been falsely or unfairly accused of violating this Code of Conduct,\nyou should notify with a concise description of your grievance. Your grievance will be\nhandled in accordance with our existing governing policies.\n\n## 8. Scope\n\nWe expect all community participants (contributors, paid or otherwise; sponsors; and\nother guests) to abide by this Code of Conduct in all community venues--online and\nin-person--as well as in all one-on-one communications pertaining to community business.\n\nThis code of conduct and its related procedures also applies to unacceptable behavior\noccurring outside the scope of community activities when such behavior has the potential\nto adversely affect the safety and well-being of community members.\n\n## 9. License and attribution\n\nThe Citizen Code of Conduct is distributed by\n[Stumptown Syndicate](http://stumptownsyndicate.org) under a\n[Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).\n\nPortions of text derived from the\n[Django Code of Conduct](https://www.djangoproject.com/conduct/) and the\n[Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).\n\n_Revision 2.3. Posted 6 March 2017._\n\n_Revision 2.2. Posted 4 February 2016._\n\n_Revision 2.1. Posted 23 June 2014._\n\n_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board\non 10 January 2013. Posted 17 March 2013._\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## Contributing Guidelines\n\nAll sorts of contributions are welcome, ranging from raising ideas, reporting bugs,\nhelping review open PRs, pointing out problematic design decisions et c, and of course\nby contributing code and documentation changes via pull requests.\n\nIf your inquiry is less concrete, or you want to ask a question, please [feel free to\nstart a new discussion][discussion]. There aren't any stupid questions, and if your\ninquiry has already been answered elsewhere, I'll do my best to direct you there.\n\n[discussion]: https://github.com/antonagestam/phantom-types/discussions\n\nI'll look at all PRs, but it's worth noting that the project has a high bar for merging\ncontributions, and all features are expected to be well tested and documented. If your\nnot sure what this means, it's totally OK to open draft PRs and ask questions.\n\nThe [README][readme] has instructions for how to set up a development environment and\ntesting code locally.\n\n[readme]: https://github.com/antonagestam/phantom-types/blob/main/README.md#development\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "content": "# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: pip\n    directory: \"/\"\n    schedule:\n      interval: monthly\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: monthly\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\n# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    name: Static analysis\n    uses: antonagestam/goose/.github/workflows/run.yaml@0.10.1\n\n  type-check:\n    name: Type check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.12\n          cache: pip\n          cache-dependency-path: typing-requirements.txt\n          check-latest: true\n      - name: mypy cache\n        uses: actions/cache@v4\n        with:\n          path: .mypy_cache\n          key: \"${{ runner.os }}-mypy-3.12-${{ hashFiles('typing-requirements.txt') }}\"\n          restore-keys: |\n            ${{ runner.os }}-mypy-3.12\n            ${{ runner.os }}-mypy\n      - run: pip install --require-hashes --no-dependencies -r typing-requirements.txt\n      - run: pip install --no-dependencies .\n      - run: mypy\n\n  check-build:\n    name: Check packaging metadata\n    uses: less-action/reusables/.github/workflows/python-test-build.yaml@main\n\n  docs:\n    name: Build Sphinx Docs\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          # Keep in sync with version in .readthedocs.yml.\n          python-version: \"3.11\"\n          cache: pip\n          cache-dependency-path: docs-requirements.txt\n      - name: Install dependencies\n        run: pip install --require-hashes --no-dependencies -r docs-requirements.txt\n      - name: Install package\n        run: pip install --no-dependencies .\n      - name: Build docs\n        run: sphinx-build -W -b html docs docs/_build\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n          cache: pip\n          cache-dependency-path: pyproject.toml\n      - name: Install minimum requirements\n        run: pip install --upgrade '.[test]'\n      - name: Run all tests that don't require extra dependencies\n        run: >-\n          coverage run --append -m pytest\n          -m \"no_external or not external\"\n          --ignore=src/phantom/ext\n          --ignore=tests/pydantic\n          --ignore=tests/ext\n      - name: Install extra requirements\n        run: pip install --upgrade '.[all,test]'\n      - name: Run all tests that require extra dependencies\n        run: >-\n          coverage run --append -m pytest\n          -m \"external\"\n      - name: Collect coverage\n        run: |\n          coverage report\n          coverage xml\n      - name: Report coverage\n        uses: codecov/codecov-action@v5\n        with:\n          files: \"coverage.xml\"\n          fail_ci_if_error: true\n          name: codecov-py${{ matrix.python-version }}\n          token: ${{ secrets.CODECOV_TOKEN }}\n          verbose: true\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n\njobs:\n  build-and-publish:\n    name: Build and publish\n    runs-on: ubuntu-latest\n    permissions:\n      # permission required for trusted publishing\n      id-token: write\n    environment:\n      name: pypi\n      url: https://pypi.org/p/immoney\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.12\n          cache: pip\n          cache-dependency-path: pyproject.toml\n          check-latest: true\n      - name: Install dependencies\n        run: python3 -m pip install --upgrade build pkginfo\n      - name: Build\n        run: python3 -m build --sdist --wheel .\n      - name: Inspect built wheel version\n        id: inspect-wheel-version\n        run: |\n          python3 << 'EOF' >> $GITHUB_OUTPUT\n          from pathlib import Path\n          from pkginfo import Wheel\n          [wheel_path] = Path(\"dist\").glob(\"*.whl\")\n          wheel = Wheel(wheel_path)\n          print(f\"version={wheel.version}\")\n          EOF\n      - name: Fail on version mismatch\n        if: ${{ steps.inspect-wheel-version.outputs.version != github.event.release.tag_name }}\n        run: |\n          echo \"💥 The version of the built wheel does not match the release tag.\"\n          echo\n          echo \"Release tag: '${{ github.event.release.tag_name }}'\"\n          echo \"Packaged version: '${{ steps.inspect-wheel-version.outputs.version }}'\"\n          exit 1\n      - name: Publish\n        uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\ncoverage.md\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Dynamic version file\nsrc/phantom/_version.py\n"
  },
  {
    "path": ".goose/check-manifest/manifest.json",
    "content": "{\"source_ecosystem\":{\"language\":\"python\",\"version\":\"3.13\"},\"source_dependencies\":[\"check-manifest\",\"setuptools-scm==8.3.1\",\"setuptools==80.9.0\",\"wheel==0.45.1\"],\"lock_files\":[{\"path\":\"requirements.txt\",\"checksum\":\"sha256:b77b12f702e9b9cc47fb51a7b3d507e4f102209f4b4ff7982edc70f558ee14f6\"}],\"checksum\":\"sha256:76f610fd622fe0781ea867ed95ad9ef365f69148c7713661b62c92c4e05ba04a\",\"ecosystem_version\":\"3.13.7\"}\n"
  },
  {
    "path": ".goose/check-manifest/requirements.txt",
    "content": "build==1.3.0 \\\n    --hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \\\n    --hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4\ncheck-manifest==0.51 \\\n    --hash=sha256:9801c7637675755a563f33e3c48ee59a59b37a7677297c05c910c16c5b9b6d67 \\\n    --hash=sha256:f5f35ed561012fc2115bb070e42a748ac2e034cf8904ab4dfaae893859085ca4\npackaging==25.0 \\\n    --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \\\n    --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f\npyproject-hooks==1.2.0 \\\n    --hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \\\n    --hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913\nsetuptools==80.9.0 \\\n    --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \\\n    --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c\nsetuptools-scm==8.3.1 \\\n    --hash=sha256:332ca0d43791b818b841213e76b1971b7711a960761c5bea5fc5cdb5196fbce3 \\\n    --hash=sha256:3d555e92b75dacd037d32bafdf94f97af51ea29ae8c7b234cf94b7a5bd242a63\nwheel==0.45.1 \\\n    --hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \\\n    --hash=sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248\n"
  },
  {
    "path": ".goose/node/manifest.json",
    "content": "{\"source_ecosystem\":\"node\",\"source_dependencies\":[\"prettier\"],\"lock_files\":[{\"path\":\"package-lock.json\",\"checksum\":\"sha256:800168958486b7fda644290aed3749c91b62334f78e691cb6f4bd3c1e562c8dc\"},{\"path\":\"package.json\",\"checksum\":\"sha256:3eae71af3c5bec28e9719c4cc2371efe5e4d149cf6317f46feb949148680d19f\"}],\"checksum\":\"sha256:6767446d081cac0f5cbd56c320e6952bf30741ca89498267c898088281a29694\",\"ecosystem_version\":\"24.0.2\"}\n"
  },
  {
    "path": ".goose/node/package.json",
    "content": "{\"lockfileVersion\":3,\"dependencies\":{\"prettier\":\"*\"}}\n"
  },
  {
    "path": ".goose/python/manifest.json",
    "content": "{\"source_ecosystem\":{\"language\":\"python\",\"version\":\"3.13\"},\"source_dependencies\":[\"blacken-docs\",\"check-jsonschema\",\"editorconfig-checker\",\"pre-commit-hooks\",\"ruff\"],\"lock_files\":[{\"path\":\"requirements.txt\",\"checksum\":\"sha256:c17077e77d3ce109826345fca16d1a5ca132f991f9297f99ad6514f5f5e8cd7d\"}],\"checksum\":\"sha256:3ad1408c2471b7ba9c2e1979543cca3a9d600822c7d79f15a5a9bd95b7d50ab3\",\"ecosystem_version\":\"3.13.7\"}\n"
  },
  {
    "path": ".goose/python/requirements.txt",
    "content": "attrs==25.4.0 \\\n    --hash=sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11 \\\n    --hash=sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373\nblack==25.9.0 \\\n    --hash=sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175 \\\n    --hash=sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619 \\\n    --hash=sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140 \\\n    --hash=sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0 \\\n    --hash=sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92 \\\n    --hash=sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f \\\n    --hash=sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa \\\n    --hash=sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae \\\n    --hash=sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a \\\n    --hash=sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357 \\\n    --hash=sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4 \\\n    --hash=sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e \\\n    --hash=sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d \\\n    --hash=sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608 \\\n    --hash=sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831 \\\n    --hash=sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f \\\n    --hash=sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7 \\\n    --hash=sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1 \\\n    --hash=sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823 \\\n    --hash=sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933 \\\n    --hash=sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47 \\\n    --hash=sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713\nblacken-docs==1.20.0 \\\n    --hash=sha256:2d5b6caf6e7da5694b1eba97f9132c1ab9f14f221c82205ec473a6e74fbb2c6d \\\n    --hash=sha256:a0d842811ee07802dec920d3cf831e21f6eb017712748b488489aa3688770f1e\ncertifi==2025.10.5 \\\n    --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \\\n    --hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43\ncharset-normalizer==3.4.4 \\\n    --hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \\\n    --hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \\\n    --hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \\\n    --hash=sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89 \\\n    --hash=sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc \\\n    --hash=sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 \\\n    --hash=sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63 \\\n    --hash=sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d \\\n    --hash=sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f \\\n    --hash=sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8 \\\n    --hash=sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0 \\\n    --hash=sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505 \\\n    --hash=sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161 \\\n    --hash=sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af \\\n    --hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \\\n    --hash=sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318 \\\n    --hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \\\n    --hash=sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4 \\\n    --hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \\\n    --hash=sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3 \\\n    --hash=sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576 \\\n    --hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \\\n    --hash=sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1 \\\n    --hash=sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8 \\\n    --hash=sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1 \\\n    --hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \\\n    --hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \\\n    --hash=sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26 \\\n    --hash=sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88 \\\n    --hash=sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 \\\n    --hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \\\n    --hash=sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf \\\n    --hash=sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a \\\n    --hash=sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc \\\n    --hash=sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0 \\\n    --hash=sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84 \\\n    --hash=sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db \\\n    --hash=sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1 \\\n    --hash=sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7 \\\n    --hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \\\n    --hash=sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 \\\n    --hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \\\n    --hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \\\n    --hash=sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef \\\n    --hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \\\n    --hash=sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2 \\\n    --hash=sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0 \\\n    --hash=sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d \\\n    --hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \\\n    --hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \\\n    --hash=sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf \\\n    --hash=sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6 \\\n    --hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \\\n    --hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \\\n    --hash=sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa \\\n    --hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \\\n    --hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \\\n    --hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \\\n    --hash=sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc \\\n    --hash=sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a \\\n    --hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \\\n    --hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \\\n    --hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \\\n    --hash=sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e \\\n    --hash=sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313 \\\n    --hash=sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569 \\\n    --hash=sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3 \\\n    --hash=sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d \\\n    --hash=sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525 \\\n    --hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \\\n    --hash=sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3 \\\n    --hash=sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9 \\\n    --hash=sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a \\\n    --hash=sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9 \\\n    --hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \\\n    --hash=sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 \\\n    --hash=sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50 \\\n    --hash=sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf \\\n    --hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \\\n    --hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \\\n    --hash=sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac \\\n    --hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \\\n    --hash=sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815 \\\n    --hash=sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c \\\n    --hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \\\n    --hash=sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6 \\\n    --hash=sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e \\\n    --hash=sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4 \\\n    --hash=sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84 \\\n    --hash=sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69 \\\n    --hash=sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15 \\\n    --hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \\\n    --hash=sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0 \\\n    --hash=sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897 \\\n    --hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \\\n    --hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \\\n    --hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \\\n    --hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \\\n    --hash=sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074 \\\n    --hash=sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3 \\\n    --hash=sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224 \\\n    --hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \\\n    --hash=sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a \\\n    --hash=sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d \\\n    --hash=sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d \\\n    --hash=sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f \\\n    --hash=sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8 \\\n    --hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \\\n    --hash=sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966 \\\n    --hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9 \\\n    --hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \\\n    --hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \\\n    --hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608\ncheck-jsonschema==0.34.1 \\\n    --hash=sha256:024ca6b1d645fdc33025f915ea4aa72a84df002b12209ee172a7bfd089f34832 \\\n    --hash=sha256:39f3faea89a26e7de6c8090bc53dceecd30029a6daf04db7e5807a2142ef54b6\nclick==8.3.0 \\\n    --hash=sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc \\\n    --hash=sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4\neditorconfig-checker==3.4.1 \\\n    --hash=sha256:11468139bd1545b96a8ee54ed7dbc9f065082e0a0d6575a22050773bbcfed6c1\nidna==3.11 \\\n    --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \\\n    --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902\njsonschema==4.25.1 \\\n    --hash=sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 \\\n    --hash=sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85\njsonschema-specifications==2025.9.1 \\\n    --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \\\n    --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d\nmypy-extensions==1.1.0 \\\n    --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \\\n    --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558\npackaging==25.0 \\\n    --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \\\n    --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f\npathspec==0.12.1 \\\n    --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \\\n    --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712\nplatformdirs==4.5.0 \\\n    --hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \\\n    --hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3\npre-commit-hooks==6.0.0 \\\n    --hash=sha256:76161b76d321d2f8ee2a8e0b84c30ee8443e01376121fd1c90851e33e3bd7ee2 \\\n    --hash=sha256:76d8370c006f5026cdd638a397a678d26dda735a3c88137e05885a020f824034\npytokens==0.3.0 \\\n    --hash=sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a \\\n    --hash=sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3\nreferencing==0.37.0 \\\n    --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \\\n    --hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8\nregress==2025.10.1 \\\n    --hash=sha256:02da99e41c6a97f6d2146d326541b4035ed2139c92b2f56ca7e464ceb84fe24f \\\n    --hash=sha256:05d02b4d7179b85acf28d7329a488901d6baef5f5b337dcd52f53ea0ff980bc3 \\\n    --hash=sha256:05e6c68021dff89fee7bcdab25e0819cff4c7f0761dc41f0fb609b0e9ebb6272 \\\n    --hash=sha256:0680ac0b0a0058acb55b64aa56956732cf56b0baa84ea95e3fa124ab16da58aa \\\n    --hash=sha256:089b6c5f0965962e8046493e5a1222a24e88d6f235fa8fe2424ec869e6ff613c \\\n    --hash=sha256:08e99c44e4c3860352400af96b25a4ebb673c16d53c6367153631ec77d5130b8 \\\n    --hash=sha256:0d1f3c465f415521ca259209dd2587c9a35785b35e98d62d4a7fba3c2bf1cfc8 \\\n    --hash=sha256:0ee12c4e1c4e6e3609f41dd58065bc241945e912772f7320238d6544f0745950 \\\n    --hash=sha256:102c4627f026db8d361ab61155e0f1093176555d60ddb1cc4c9b6f5bbe255c1f \\\n    --hash=sha256:12430b2c7263a7b359d30dd0f2b179426d489df30da78cd21023376eb2fe2682 \\\n    --hash=sha256:1460d95d39d956ba0fab8a7b614b7ee5486473b1b210f65d6a3043dc08462f38 \\\n    --hash=sha256:148f15530807a63e24ca2dea3795546723d84c0b3a8e4152a24b58318f841b3c \\\n    --hash=sha256:15a7c2777e8eeb153fe87327871669c3bd1aab4fe20c1fccf448616bc298350d \\\n    --hash=sha256:15efcb8d568e712919cb56b78fffe08a09d5ed1844b43cccc3235c2da90d0e59 \\\n    --hash=sha256:1624855927d72bb0f8281785fc110eb89702078abe1c42e2e6cbe872ec374277 \\\n    --hash=sha256:1809ac971207bb2e098a46e08c2f241a259ac3bda48228f295a3c2cba49c6c0e \\\n    --hash=sha256:189899161133e7c56e733a3fc939642611585c5599626352ee0849cd34d7f436 \\\n    --hash=sha256:19b875513ebecc2b37a125d5794ce91fb4c203b06cb187e15d13b2a761df4621 \\\n    --hash=sha256:212fe8c4f823730d1578d0fe8b5edf21b393074b90a8ec704d6d6c1c96ffe7dc \\\n    --hash=sha256:25d8518285aa3ce67ddbede90a1a9ca6a5d23a1b8275dd5a9722af0c64b37b2a \\\n    --hash=sha256:287f86b5c0bf3bc9c0abd45bf6745ba9c6a5624c3132b07631bac4403b45143f \\\n    --hash=sha256:28941c80252119ef051ad67195fa8d155a0c8dc9ecb801786d94eeea6738e4c3 \\\n    --hash=sha256:290a15652c3fafe387db02061d4ed9a100804ed5a162d069b5a0ac28b5df162a \\\n    --hash=sha256:293be370961c6887efb82e466a15523ff24a702d444f44917ce318b222ffd229 \\\n    --hash=sha256:30e70b966015beef7e9287feaf37ed9606744ceaa88be24b10824f5b4c354498 \\\n    --hash=sha256:337ef62f785dc6e05c4a09be7a902980cf4d4f15346f4eca9eaf02745485440c \\\n    --hash=sha256:3605ef64ab64658856ce8d6fda730dfb62c01002e2283bd6400df8e912d1b56a \\\n    --hash=sha256:389c2278848cbfed81753a04ce8ea6c037271179cb9ef4decac7d3c65ae3330b \\\n    --hash=sha256:39600db4e404155168d6f75c3dbb2ae03ef3e288b4a57c7a6562a1144bf07682 \\\n    --hash=sha256:3d12e6a834ed5f6d9dc7e86ea8fd77d37bb13900129930151d87c3261c3a799e \\\n    --hash=sha256:3d48ae257483ff7da43c15b8071b132a1e75e4be2242a44e2587b8492afae32e \\\n    --hash=sha256:415dd30885dfdb57f281f7c284e4495e85a90aa66965e3f10cb1bd862f40c2e4 \\\n    --hash=sha256:45695cd7ddc6a919863f243a09a9e737257f958c0d2af0e71e349c9d0f3048ad \\\n    --hash=sha256:469b03b1deffb6d20ea4f95aa44ef0e0e5a9b47d56f709276b06a96338b570a0 \\\n    --hash=sha256:46b92e1ec6092e6e989e4ad5f52f0a358e88355f70cf4dba9abce84f3cd513cf \\\n    --hash=sha256:47c03bfd853651241fc11436fb14ef7c9b312bb9a9c2828aa5c93943e945f1ef \\\n    --hash=sha256:4c559282471f4f0c9bf7b588985c4bec16de0a85e1a3e017dca39640e15118ce \\\n    --hash=sha256:4d0bf23a6d996655ed88c822bb0123cc2e92a1df95079ce7408552c35ec05d47 \\\n    --hash=sha256:5541da1f581377bc20d2f77e017453aa8f2c2f4bfe7679dee00e139ec700abe8 \\\n    --hash=sha256:56123dbe783a2bab04d1a1850605c483d40f36196bc52d249aa245d06f866f78 \\\n    --hash=sha256:56ef2f8ef9a102a7d42cbbc2f3c0c5e1b186bd8eaa78d564da566b0bf20653dc \\\n    --hash=sha256:590abc9fa10255dcf84b4469f08cad9787001c38600080a033cd7a71f51cae02 \\\n    --hash=sha256:5a2ddb0d1c0821b70dd50daa773c5f3fbb2155398c57809a2f54447958c9569f \\\n    --hash=sha256:5c147e4d3799022bac9fc49fd042a51b7f746c41ed231fa6b496720dab0d2f9d \\\n    --hash=sha256:5d961e81b169fce4f6c0e35f52bbfca9e20abf9674dd391c75708f0665ef4f6f \\\n    --hash=sha256:63503a8f601a10e5d9d72ea6efb415a2838a4766736775322578ce5fe18cb233 \\\n    --hash=sha256:6553c8ba57fa92ab3e9ef5c811d6214c80131bba06496bd5920e6e5a3d53ca8e \\\n    --hash=sha256:6b52096ecbf39f50756e51efa9286f47c598572f6b8bb2119f855de817f38b8e \\\n    --hash=sha256:6c1c968ee4dca933e0cd6cea51d9c6494c3d82650ce826e7a5d007c9de720da2 \\\n    --hash=sha256:6c4c40a8c9f3e0119d2384a52b55dcc770461e1ead6ae7b41314999223116a15 \\\n    --hash=sha256:6d26daee7d46905c8d4232f44c39ea788f10d39c166735177f603783b4ace4e8 \\\n    --hash=sha256:6d451a88292c5a93f57cf754c71b3aa8b570ed159f9fd481554948c467e6105d \\\n    --hash=sha256:701d1e4f2b30abfb39b27759a52e68cab8b76484b3d9b51b2d7f368807613f77 \\\n    --hash=sha256:707e2846e784ecef666a6892753cf5d8441e4cf02bcc7fa10fd21527429815fc \\\n    --hash=sha256:70c78c6cf679da47fbde85b75407fdfb4642477315ee94d6cdd62a0606941b83 \\\n    --hash=sha256:722c408a3bc92b4904005e68244c28fa6df943290df8d670faf349414c86aabb \\\n    --hash=sha256:7343ef7eae795e1449308d6d05131195f5af31ab1b2b6b405ff9370b64c1e7e1 \\\n    --hash=sha256:77d63b338c2a4e56b4f05632d3fd94061ad47ee2b272b158b9c2e09545a4c6bb \\\n    --hash=sha256:783b9c50760aab988e4d60dcb7c54eba3fe730d80f9a877f1dc52d14263f86b1 \\\n    --hash=sha256:7ac0e197c7f1b5ffca42341518b6a03e2ea3cdd66516af9278492d2d2bfc9ce2 \\\n    --hash=sha256:8051fac3696730bb84d7675c62c7073792a0e105233d4f8e1055f2cab9b04fce \\\n    --hash=sha256:81733f0e7f583d181bc9fc187b3d766489bcf7e0c85eff26f1e067c942e4e45a \\\n    --hash=sha256:8405a31f0a1475e1c9aa20c4d6e1465ef2f7259581c018a2e273083494ca9a61 \\\n    --hash=sha256:877e05e7c570ee1e077e8b587cca8a318b7675f3c94c6c4e25d0d145abf7c0b6 \\\n    --hash=sha256:8a9c00ede347a5a431e36d4fcf75e2ec9094feac2f6170f2ffbd5e32f7f7a4b4 \\\n    --hash=sha256:8d725e10a99ff65b0ad83b8c20f05645d9c67dbc809e2bf3b1db230d3e15081b \\\n    --hash=sha256:8e337ce3b150ca0ff599b1150b995ff6a2e32b5940e17ac3f30a29133960709e \\\n    --hash=sha256:8f42578d920fc878fe19ed8e2fbe38edd212c451bb2fc5e0084716d5acd26c4c \\\n    --hash=sha256:905c4e364526f89b33db8e69468b15d9c294a56af4d533f2700e0e1a363fbae7 \\\n    --hash=sha256:9100da34f69e18ad6d545f5d74f8ee729e42ce200a73752dd6d94f8a373d0e71 \\\n    --hash=sha256:933561ea90b2ac9a826e956a994b8a66635cd96467374281da992ceea8b0de4c \\\n    --hash=sha256:966dcad04fe1821c7af9bf6dd33bbeca5c2649b7a3c5b2af57700e6d93335fd4 \\\n    --hash=sha256:9670d957d156fa90e5582c4337ca1757b643859780896c8d853b015cd01456dc \\\n    --hash=sha256:97307f87b128389d8b3f385c8e431fc318263281d1a1c0394606bc813aea05fc \\\n    --hash=sha256:9e25096697b034848d8fc7910cdb38b7abc2bd2d7ade8767893359918ed4efd0 \\\n    --hash=sha256:9fdbbea49bf2fe65f7272b0316e1343aff1ccbb85b58fab325778c416d648ed9 \\\n    --hash=sha256:a542e38c8bb95f618674a5d2d248f1010547d7ff2e46a6cf4fa4b851459ba440 \\\n    --hash=sha256:a67216efa72bb27db170f637d67eb9acdc167da5d617163b057803de7aed1e6d \\\n    --hash=sha256:a6f0320f53e8fd8722211c0620fcd9bfecc0db05bf0d59385ee301f86b815671 \\\n    --hash=sha256:a97649f21c875b7e95e10a59f0f487a518735f51a7aec7fc95fb2c3d9a3914d5 \\\n    --hash=sha256:aaf5d102e05b109dde13363e705969b3ffdbbfeb880270187eed0871e75f0c8f \\\n    --hash=sha256:ac04243bf4ba86196b2491bdacd9450b339b3e5e97192aab82234baac1f0a74f \\\n    --hash=sha256:adaa80c97927d623ff72b920bcc637568f124eabe84559c7927a91253ff55d5e \\\n    --hash=sha256:adf331c8938e0d8705fc5d05d08fab09c11cb4bcdf8a64fa21902972c4cd38f4 \\\n    --hash=sha256:af290cfb3b7d4b14319a88feab4e86f54a2c43bd8189be5a0dcd8f847f832847 \\\n    --hash=sha256:afee501f000666afe18531132edcaf0dc0178dd591cccf5b9596563e7456c118 \\\n    --hash=sha256:b4bc2008b59e5124c1672d7fd9f203e5d4a4ff88ebaf4666e3281141e2d8db20 \\\n    --hash=sha256:b5254f758206ba45776aefbd3c4223898890209b4dd3a743f2dc5cd1ebc5e9fd \\\n    --hash=sha256:b6b5aa9f9408fdf260c73071282a28d29efbb4c30a4b95ab29863bea31987621 \\\n    --hash=sha256:b99a73cfbdc5d99681aeb3aeaa2d88369023c96648cc785433d6a92c8d3a8394 \\\n    --hash=sha256:be96403b244e3d6925e225b4da5fbc4f5dd6f06b07f751f00047aa48fd2c7fe3 \\\n    --hash=sha256:bf743b00cac20f8e8d271f275df7bc192ebb4faa7aee8fe98484df338786dec6 \\\n    --hash=sha256:c0ed3b4d0df960aeccb685f8de5001c19f426f1ab09fde715e5abdbf9c59b26a \\\n    --hash=sha256:c34b13e7785554997e18725b896d404ba992cee5b691768476f14b48de0c393a \\\n    --hash=sha256:c35c8cd42900d9195e5bf48d701dda28c204854fba7cc27d18f309745f57b8b5 \\\n    --hash=sha256:c3962e4b980bfb844518beb1a3afce069674377ba99189fe339918fe7e7cbb7a \\\n    --hash=sha256:c7ecba827aff7f951db40be777c32608a1b16bbeb7f02fcc97a2e9fc6702641f \\\n    --hash=sha256:cb23c4ab28e75e35f033e4e392f01f6a9344a961e2f4ded56af5520b5841fe8d \\\n    --hash=sha256:cba77808a756f1f117916c0afb0e79f01be1cb46ee51c77780e8afc59ddac76b \\\n    --hash=sha256:cbf1371ae2cfe1a2d6e1bb21f73a557138bc45347021f1701f196b484789639a \\\n    --hash=sha256:ccfc11e2b632a21d82a56b52a70fc002b990fcf4f6b30661724732776bea6f1d \\\n    --hash=sha256:ce4e38ada2a39159d8a2523084e2b5ed94b3f7f7b196ba6b99c2d6c4ff634a87 \\\n    --hash=sha256:d0f63f4c3f2e701c832e54434ee10bd0bd2c850b0ae4029829d2dd0c9a340d83 \\\n    --hash=sha256:d48e91d14a366570685f76adaf9d108e3abc5522572e7e1d0d78c3cc3ebf0833 \\\n    --hash=sha256:daece9f2fbaaadd23ef2cb31ffbaecd74f946938ff9b70d22c891c66d1435ad2 \\\n    --hash=sha256:dc9046f25971e9d6dc3b57028de8991d0d7c346efcb0c15acbfecbfb8e4c1813 \\\n    --hash=sha256:dcc0a8af0cdbc3d6e0d4725f113335d0a5ffbba86ae3ca18d2b5b352c5f2c8ed \\\n    --hash=sha256:de16665fcc008db88729f52bab92e36d0c0534bcb3f334ff6d2c7574b16a3d6d \\\n    --hash=sha256:e2a0a7da1d42fdff8068ab976a66beea572621514a130b37592489ae134a0e27 \\\n    --hash=sha256:e5c441a6017a5a29bb38c573892d882485cc26937cb1ee12da8593723bf6c041 \\\n    --hash=sha256:e5f35e04fe6382c236d60c98b3f0a4a22dea75398b99c9c9bf3fb9d386cd7ebb \\\n    --hash=sha256:e7cc153fabb47b6f8dfc2903186934e07aa57ee1debe9b3569ac4779b43708ae \\\n    --hash=sha256:ee3b7325ea849097d674020c72b2e6deb8f1018f085a7ecbd22831cd217b7f80 \\\n    --hash=sha256:f0599f9cb12722300b6d4fcd6bd9b2be5bf233bc567c3ca503d8e392de23798a \\\n    --hash=sha256:f3afe24e6474f5dbc448f865641c29bcbed4eb3b87ac9eb0e6755c4eff4f7111 \\\n    --hash=sha256:f418c11a6bae820fad1334e0569336e2e7848cf4b81e503bfb038daf0e57ec12 \\\n    --hash=sha256:faa057895de8e41301f9367b286bb3fd0cd80bc8523c8c80866fe746a33d13b6 \\\n    --hash=sha256:fb61e653a325aea4681cf7b96ba9bbabc1aeb3f3d8fe877a07800024907398b9 \\\n    --hash=sha256:fdcd839a87fae1e47bf376248589ebcb2e58d3a0fad66837d69856ae99ef3c93\nrequests==2.32.5 \\\n    --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \\\n    --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf\nrpds-py==0.28.0 \\\n    --hash=sha256:03065002fd2e287725d95fbc69688e0c6daf6c6314ba38bdbaa3895418e09296 \\\n    --hash=sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1 \\\n    --hash=sha256:05cf1e74900e8da73fa08cc76c74a03345e5a3e37691d07cfe2092d7d8e27b04 \\\n    --hash=sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d \\\n    --hash=sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7 \\\n    --hash=sha256:0d3259ea9ad8743a75a43eb7819324cdab393263c91be86e2d1901ee65c314e0 \\\n    --hash=sha256:1571ae4292649100d743b26d5f9c63503bb1fedf538a8f29a98dce2d5ba6b4e6 \\\n    --hash=sha256:1a4c6b05c685c0c03f80dabaeb73e74218c49deea965ca63f76a752807397207 \\\n    --hash=sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1 \\\n    --hash=sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd \\\n    --hash=sha256:23690b5827e643150cf7b49569679ec13fe9a610a15949ed48b85eb7f98f34ec \\\n    --hash=sha256:2374e16cc9131022e7d9a8f8d65d261d9ba55048c78f3b6e017971a4f5e6353c \\\n    --hash=sha256:24743a7b372e9a76171f6b69c01aedf927e8ac3e16c474d9fe20d552a8cb45c7 \\\n    --hash=sha256:25dbade8fbf30bcc551cb352376c0ad64b067e4fc56f90e22ba70c3ce205988c \\\n    --hash=sha256:28ea02215f262b6d078daec0b45344c89e161eab9526b0d898221d96fdda5f27 \\\n    --hash=sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5 \\\n    --hash=sha256:2e8456b6ee5527112ff2354dd9087b030e3429e43a74f480d4a5ca79d269fd85 \\\n    --hash=sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed \\\n    --hash=sha256:31eb671150b9c62409a888850aaa8e6533635704fe2b78335f9aaf7ff81eec4d \\\n    --hash=sha256:389c29045ee8bbb1627ea190b4976a310a295559eaf9f1464a1a6f2bf84dde78 \\\n    --hash=sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342 \\\n    --hash=sha256:3c03002f54cc855860bfdc3442928ffdca9081e73b5b382ed0b9e8efe6e5e205 \\\n    --hash=sha256:46959ef2e64f9e4a41fc89aa20dbca2b85531f9a72c21099a3360f35d10b0d5a \\\n    --hash=sha256:48b55c1f64482f7d8bd39942f376bfdf2f6aec637ee8c805b5041e14eeb771db \\\n    --hash=sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b \\\n    --hash=sha256:4c6c4db5d73d179746951486df97fd25e92396be07fc29ee8ff9a8f5afbdfb27 \\\n    --hash=sha256:4e27d3a5709cc2b3e013bf93679a849213c79ae0573f9b894b284b55e729e120 \\\n    --hash=sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527 \\\n    --hash=sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592 \\\n    --hash=sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa \\\n    --hash=sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370 \\\n    --hash=sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41 \\\n    --hash=sha256:5cfa9af45e7c1140af7321fa0bef25b386ee9faa8928c80dc3a5360971a29e8c \\\n    --hash=sha256:5d0145edba8abd3db0ab22b5300c99dc152f5c9021fab861be0f0544dc3cbc5f \\\n    --hash=sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728 \\\n    --hash=sha256:5ee514e0f0523db5d3fb171f397c54875dbbd69760a414dccf9d4d7ad628b5bd \\\n    --hash=sha256:5f3fa06d27fdcee47f07a39e02862da0100cb4982508f5ead53ec533cd5fe55e \\\n    --hash=sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1 \\\n    --hash=sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01 \\\n    --hash=sha256:6897bebb118c44b38c9cb62a178e09f1593c949391b9a1a6fe777ccab5934ee7 \\\n    --hash=sha256:6aa1bfce3f83baf00d9c5fcdbba93a3ab79958b4c7d7d1f55e7fe68c20e63912 \\\n    --hash=sha256:6b4f28583a4f247ff60cd7bdda83db8c3f5b05a7a82ff20dd4b078571747708f \\\n    --hash=sha256:6e32dd207e2c4f8475257a3540ab8a93eff997abfa0a3fdb287cae0d6cd874b8 \\\n    --hash=sha256:6f0c9266c26580e7243ad0d72fc3e01d6b33866cfab5084a6da7576bcf1c4f72 \\\n    --hash=sha256:735f8495a13159ce6a0d533f01e8674cec0c57038c920495f87dcb20b3ddb48a \\\n    --hash=sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515 \\\n    --hash=sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578 \\\n    --hash=sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9 \\\n    --hash=sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b \\\n    --hash=sha256:7b0f9dceb221792b3ee6acb5438eb1f02b0cb2c247796a72b016dcc92c6de829 \\\n    --hash=sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37 \\\n    --hash=sha256:7b6013db815417eeb56b2d9d7324e64fcd4fa289caeee6e7a78b2e11fc9b438a \\\n    --hash=sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907 \\\n    --hash=sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3 \\\n    --hash=sha256:8455933b4bcd6e83fde3fefc987a023389c4b13f9a58c8d23e4b3f6d13f78c84 \\\n    --hash=sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa \\\n    --hash=sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733 \\\n    --hash=sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f \\\n    --hash=sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc \\\n    --hash=sha256:8f60c7ea34e78c199acd0d3cda37a99be2c861dd2b8cf67399784f70c9f8e57d \\\n    --hash=sha256:961ca621ff10d198bbe6ba4957decca61aa2a0c56695384c1d6b79bf61436df5 \\\n    --hash=sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe \\\n    --hash=sha256:9a7548b345f66f6695943b4ef6afe33ccd3f1b638bd9afd0f730dd255c249c9e \\\n    --hash=sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a \\\n    --hash=sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399 \\\n    --hash=sha256:a3b695a8fa799dd2cfdb4804b37096c5f6dba1ac7f48a7fbf6d0485bcd060316 \\\n    --hash=sha256:a410542d61fc54710f750d3764380b53bf09e8c4edbf2f9141a82aa774a04f7c \\\n    --hash=sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d \\\n    --hash=sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d \\\n    --hash=sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea \\\n    --hash=sha256:ac9f83e7b326a3f9ec3ef84cda98fb0a74c7159f33e692032233046e7fd15da2 \\\n    --hash=sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a \\\n    --hash=sha256:ad50614a02c8c2962feebe6012b52f9802deec4263946cddea37aaf28dd25a66 \\\n    --hash=sha256:ada7754a10faacd4f26067e62de52d6af93b6d9542f0df73c57b9771eb3ba9c4 \\\n    --hash=sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f \\\n    --hash=sha256:b1b553dd06e875249fd43efd727785efb57a53180e0fde321468222eabbeaafa \\\n    --hash=sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a \\\n    --hash=sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c \\\n    --hash=sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092 \\\n    --hash=sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6 \\\n    --hash=sha256:b9699fa7990368b22032baf2b2dce1f634388e4ffc03dfefaaac79f4695edc95 \\\n    --hash=sha256:b9b06fe1a75e05e0713f06ea0c89ecb6452210fd60e2f1b6ddc1067b990e08d9 \\\n    --hash=sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e \\\n    --hash=sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712 \\\n    --hash=sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91 \\\n    --hash=sha256:beb880a9ca0a117415f241f66d56025c02037f7c4efc6fe59b5b8454f1eaa50d \\\n    --hash=sha256:c2a34fd26588949e1e7977cfcbb17a9a42c948c100cab890c6d8d823f0586457 \\\n    --hash=sha256:c9a40040aa388b037eb39416710fbcce9443498d2eaab0b9b45ae988b53f5c67 \\\n    --hash=sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491 \\\n    --hash=sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e \\\n    --hash=sha256:d15431e334fba488b081d47f30f091e5d03c18527c325386091f31718952fe08 \\\n    --hash=sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724 \\\n    --hash=sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259 \\\n    --hash=sha256:d678e91b610c29c4b3d52a2c148b641df2b4676ffe47c59f6388d58b99cdc424 \\\n    --hash=sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb \\\n    --hash=sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472 \\\n    --hash=sha256:dd8d86b5d29d1b74100982424ba53e56033dc47720a6de9ba0259cf81d7cecaa \\\n    --hash=sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e \\\n    --hash=sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba \\\n    --hash=sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c \\\n    --hash=sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d \\\n    --hash=sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b \\\n    --hash=sha256:e5deca01b271492553fdb6c7fd974659dce736a15bae5dad7ab8b93555bceb28 \\\n    --hash=sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56 \\\n    --hash=sha256:e819e0e37a44a78e1383bf1970076e2ccc4dc8c2bbaa2f9bd1dc987e9afff628 \\\n    --hash=sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b \\\n    --hash=sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a \\\n    --hash=sha256:efd489fec7c311dae25e94fe7eeda4b3d06be71c68f2cf2e8ef990ffcd2cd7e8 \\\n    --hash=sha256:f0b2044fdddeea5b05df832e50d2a06fe61023acb44d76978e1b060206a8a476 \\\n    --hash=sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2 \\\n    --hash=sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c \\\n    --hash=sha256:f4794c6c3fbe8f9ac87699b131a1f26e7b4abcf6d828da46a3a52648c7930eba \\\n    --hash=sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8 \\\n    --hash=sha256:f5e7101145427087e493b9c9b959da68d357c28c562792300dd21a095118ed16 \\\n    --hash=sha256:f9174471d6920cbc5e82a7822de8dfd4dcea86eb828b04fc8c6519a77b0ee51e\nruamel-yaml==0.18.16 \\\n    --hash=sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba \\\n    --hash=sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a\nruamel-yaml-clib==0.2.14 \\\n    --hash=sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78 \\\n    --hash=sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d \\\n    --hash=sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9 \\\n    --hash=sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9 \\\n    --hash=sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792 \\\n    --hash=sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e \\\n    --hash=sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca \\\n    --hash=sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb \\\n    --hash=sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d \\\n    --hash=sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d \\\n    --hash=sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182 \\\n    --hash=sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98 \\\n    --hash=sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1 \\\n    --hash=sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8 \\\n    --hash=sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee \\\n    --hash=sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a \\\n    --hash=sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e \\\n    --hash=sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052 \\\n    --hash=sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27 \\\n    --hash=sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352 \\\n    --hash=sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83 \\\n    --hash=sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f \\\n    --hash=sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32 \\\n    --hash=sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e \\\n    --hash=sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad \\\n    --hash=sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02 \\\n    --hash=sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf \\\n    --hash=sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6 \\\n    --hash=sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a \\\n    --hash=sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31 \\\n    --hash=sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d \\\n    --hash=sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8 \\\n    --hash=sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29 \\\n    --hash=sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543 \\\n    --hash=sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27 \\\n    --hash=sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68 \\\n    --hash=sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a \\\n    --hash=sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451 \\\n    --hash=sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4 \\\n    --hash=sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6 \\\n    --hash=sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54 \\\n    --hash=sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023 \\\n    --hash=sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8 \\\n    --hash=sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853 \\\n    --hash=sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70 \\\n    --hash=sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85 \\\n    --hash=sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9 \\\n    --hash=sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179 \\\n    --hash=sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c \\\n    --hash=sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640 \\\n    --hash=sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2 \\\n    --hash=sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4 \\\n    --hash=sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4 \\\n    --hash=sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259 \\\n    --hash=sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59\nruff==0.14.4 \\\n    --hash=sha256:1043c6811c2419e39011890f14d0a30470f19d47d197c4858b2787dfa698f6c8 \\\n    --hash=sha256:26673da283b96fe35fa0c939bf8411abec47111644aa9f7cfbd3c573fb125d2c \\\n    --hash=sha256:456daa2fa1021bc86ca857f43fe29d5d8b3f0e55e9f90c58c317c1dcc2afc7b5 \\\n    --hash=sha256:5a443a83a1506c684e98acb8cb55abaf3ef725078be40237463dae4463366349 \\\n    --hash=sha256:643b69cb63cd996f1fc7229da726d07ac307eae442dd8974dbc7cf22c1e18fff \\\n    --hash=sha256:76158a7369b3979fa878612c623a7e5430c18b2fd1c73b214945c2d06337db67 \\\n    --hash=sha256:81b40d27924f1f02dfa827b9c0712a13c0e4b108421665322218fc38caf615c2 \\\n    --hash=sha256:9358d490ec030f1b51d048a7fd6ead418ed0826daf6149e95e30aa67c168af33 \\\n    --hash=sha256:95643ffd209ce78bc113266b88fba3d39e0461f0cbc8b55fb92505030fb4a850 \\\n    --hash=sha256:a9f3a936ac27fb7c2a93e4f4b943a662775879ac579a433291a6f69428722649 \\\n    --hash=sha256:aa082a8f878deeba955531f975881828fd6afd90dfa757c2b0808aadb437136e \\\n    --hash=sha256:c62da9a06779deecf4d17ed04939ae8b31b517643b26370c3be1d26f3ef7dbde \\\n    --hash=sha256:d99c0b52b6f0598acede45ee78288e5e9b4409d1ce7f661f0fa36d4cbeadf9a4 \\\n    --hash=sha256:dd09c292479596b0e6fec8cd95c65c3a6dc68e9ad17b8f2382130f87ff6a75bb \\\n    --hash=sha256:e6604613ffbcf2297cd5dcba0e0ac9bd0c11dc026442dfbb614504e87c349518 \\\n    --hash=sha256:f3b8f3b442d2b14c246e7aeca2e75915159e06a3540e2f4bed9f50d062d24469 \\\n    --hash=sha256:f459a49fe1085a749f15414ca76f61595f1a2cc8778ed7c279b6ca2e1fd19df3 \\\n    --hash=sha256:f5e649052a294fe00818650712083cddc6cc02744afaf37202c65df9ea52efa5 \\\n    --hash=sha256:f911bba769e4a9f51af6e70037bb72b70b45a16db5ce73e1f72aefe6f6d62132\nurllib3==2.5.0 \\\n    --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \\\n    --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# https://docs.readthedocs.io/en/stable/config-file/v2.html\nversion: 2\nsphinx:\n  configuration: docs/conf.py\nbuild:\n  os: ubuntu-22.04\n  tools:\n    # Keep in sync with docs build CI job.\n    python: \"3.11\"\npython:\n  install:\n    - requirements: docs-requirements.txt\n    - method: pip\n      path: .\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2020-2022, Anton Agestam\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "exclude Makefile\nexclude .editorconfig\nrecursive-exclude docs *\nrecursive-exclude .github *\nrecursive-exclude .goose *\nrecursive-exclude tests *\nrecursive-include src py.typed\nexclude *.yaml\nexclude *.yml\nexclude *.toml\ninclude README.md\ninclude LICENSE\ninclude pyproject.toml\nexclude .gitignore\nexclude setup.cfg\nexclude mypy.ini\nexclude *requirements.txt\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL := /usr/bin/env bash\n\n.PHONY: all\n\n# Currently running typeguard on all modules except:\n# - phantom.interval\n# - phantom._base\n# - phantom.ext.phonenumbers\ntypeguard_packages := \\\n\tphantom.boolean \\\n\tphantom.datetime \\\n\tphantom.fn \\\n\tphantom.iso3166 \\\n\tphantom.re \\\n\tphantom.schema \\\n\tphantom.sized \\\n\tphantom.utils \\\n\tphantom.predicates._base \\\n\tphantom.predicates.boolean \\\n\tphantom.predicates.collection \\\n\tphantom.predicates.datetime \\\n\tphantom.prediactes.generic \\\n\tphantom.predicates.interval \\\n\tphantom.predicates.numeric \\\n\tphantom.predicates.re \\\n\tphantom.predicates.utils\ntypeguard_arg := \\\n\t--typeguard-packages=$(shell echo $(typeguard_packages) | sed 's/ /,/g')\n\n.PHONY: test\ntest:\n\tpytest $(test)  -m 'not no_external'\n\n.PHONY: test-runtime\ntest-runtime:\n\tpytest $(test) -k.py -m 'not no_external'\n\n.PHONY: test-typeguard\ntest-typeguard:\n\tpytest $(typeguard_arg) $(test)\n\n.PHONY: test-typing\ntest-typing:\n\tpytest $(test) -k.yaml\n\n.PHONY: clean\nclean:\n\trm -rf {**/,}*.egg-info **{/**,}/__pycache__ build dist .coverage coverage.xml\n\n.PHONY: docs-requirements\ndocs-requirements: export UV_CUSTOM_COMPILE_COMMAND='make docs-requirements'\ndocs-requirements:\n\t@uv pip compile --generate-hashes --strip-extras --extra=docs --upgrade --output-file=docs-requirements.txt pyproject.toml\n\n\n.PHONY: docs-requirements\ntyping-requirements: export UV_CUSTOM_COMPILE_COMMAND='make typing-requirements'\ntyping-requirements:\n\t@uv pip compile --generate-hashes --strip-extras --extra=type-check --upgrade --output-file=typing-requirements.txt pyproject.toml\n"
  },
  {
    "path": "README.md",
    "content": "<p align=center><img src=https://raw.githubusercontent.com/antonagestam/phantom-types/main/docs/phantom.svg></p>\n\n<h1 align=center>phantom-types</h1>\n\n<p align=center>\n    <a href=https://github.com/antonagestam/phantom-types/actions?query=workflow%3ACI+branch%3Amain><img src=https://github.com/antonagestam/phantom-types/actions/workflows/ci.yaml/badge.svg?branch=main alt=\"CI Build Status\"></a>\n    <a href=https://phantom-types.readthedocs.io/en/stable/><img src=https://readthedocs.org/projects/phantom-types/badge/?version=main alt=\"Documentation Build Status\"></a>\n    <a href=https://codecov.io/gh/antonagestam/phantom-types><img src=https://codecov.io/gh/antonagestam/phantom-types/branch/main/graph/badge.svg?token=UE85B7IA3Q alt=\"Test coverage report\"></a>\n    <br>\n    <a href=https://pypi.org/project/phantom-types/><img src=https://img.shields.io/pypi/v/phantom-types.svg?color=informational&label=PyPI alt=\"PyPI Package\"></a>\n    <a href=https://pypi.org/project/phantom-types/><img src=https://img.shields.io/pypi/pyversions/phantom-types.svg?color=informational&label=Python alt=\"Python versions\"></a>\n</p>\n\n[Phantom types][ghosts] for Python will help you make illegal states unrepresentable and\navoid shotgun parsing by enabling you to practice [\"Parse, don't validate\"][parse].\n\n<h4 align=center>\n    <a href=https://phantom-types.readthedocs.io/en/stable/>Checkout the complete documentation on Read the Docs →</a>\n</h4>\n\n## Installation\n\n```bash\n$  python3 -m pip install phantom-types\n```\n\n#### Extras\n\nThere are a few extras available that can be used to either enable a feature or install\na compatible version of a third-party library.\n\n| Extra name       | Feature                                                                                                    |\n| ---------------- | ---------------------------------------------------------------------------------------------------------- |\n| `[dateutil]`     | Installs [python-dateutil]. Required for parsing strings with [`TZAware` and `TZNaive`][phantom-datetime]. |\n| `[phonenumbers]` | Installs [phonenumbers]. Required to use [`phantom.ext.phonenumbers`][phantom-phonenumbers].               |\n| `[pydantic]`     | Installs [pydantic].                                                                                       |\n| `[hypothesis]`   | Installs [hypothesis].                                                                                     |\n| `[all]`          | Installs all of the above.                                                                                 |\n\n[python-dateutil]: https://pypi.org/project/python-dateutil/\n[phonenumbers]: https://pypi.org/project/phonenumbers/\n[pydantic]: https://pypi.org/project/pydantic/\n[hypothesis]: https://pypi.org/project/hypothesis/\n[phantom-datetime]:\n  https://phantom-types.readthedocs.io/en/main/pages/types.html#module-phantom.datetime\n[phantom-phonenumbers]:\n  https://phantom-types.readthedocs.io/en/main/pages/external-wrappers.html#module-phantom.ext.phonenumbers\n\n```bash\n$  python3 -m pip install phantom-types[all]\n```\n\n## Examples\n\nBy introducing a phantom type we can define a pre-condition for a function argument.\n\n```python\nfrom phantom import Phantom\nfrom phantom.predicates.collection import contained\n\n\nclass Name(str, Phantom, predicate=contained({\"Jane\", \"Joe\"})): ...\n\n\ndef greet(name: Name):\n    print(f\"Hello {name}!\")\n```\n\nNow this will be a valid call.\n\n```python\ngreet(Name.parse(\"Jane\"))\n```\n\n... and so will this.\n\n```python\njoe = \"Joe\"\nassert isinstance(joe, Name)\ngreet(joe)\n```\n\nBut this will yield a static type checking error.\n\n```python\ngreet(\"bird\")\n```\n\nTo be clear, the reason the first example passes is not because the type checker somehow\nmagically knows about our predicate, but because we provided the type checker with proof\nthrough the `assert`. All the type checker cares about is that runtime cannot continue\nexecuting past the assertion, unless the variable is a `Name`. If we move the calls\naround like in the example below, the type checker would give an error for the `greet()`\ncall.\n\n```python\njoe = \"Joe\"\ngreet(joe)\nassert isinstance(joe, Name)\n```\n\n### Runtime type checking\n\nBy combining phantom types with a runtime type-checker like [beartype] or [typeguard],\nwe can achieve the same level of security as you'd gain from using [contracts][dbc].\n\n```python\nimport datetime\nfrom beartype import beartype\nfrom phantom.datetime import TZAware\n\n\n@beartype\ndef soon(dt: TZAware) -> TZAware:\n    return dt + datetime.timedelta(seconds=10)\n```\n\nThe `soon` function will now validate that both its argument and return value is\ntimezone aware, e.g. pre- and post conditions.\n\n### Pydantic support\n\nPhantom types are ready to use with [pydantic] and have [integrated\nsupport][pydantic-support] out-of-the-box. Subclasses of `Phantom` work with both\npydantic's validation and its schema generation.\n\n```python\nclass Name(str, Phantom, predicate=contained({\"Jane\", \"Joe\"})):\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return super().__schema__() | {\n            \"description\": \"Either Jane or Joe\",\n            \"format\": \"custom-name\",\n        }\n\n\nclass Person(BaseModel):\n    name: Name\n    created: TZAware\n\n\nprint(json.dumps(Person.schema(), indent=2))\n```\n\nThe code above outputs the following JSONSchema.\n\n```json\n{\n  \"title\": \"Person\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"title\": \"Name\",\n      \"description\": \"Either Jane or Joe\",\n      \"format\": \"custom-name\",\n      \"type\": \"string\"\n    },\n    \"created\": {\n      \"title\": \"TZAware\",\n      \"description\": \"A date-time with timezone data.\",\n      \"type\": \"string\",\n      \"format\": \"date-time\"\n    }\n  },\n  \"required\": [\"name\", \"created\"]\n}\n```\n\n## Development\n\nInstall development requirements, preferably in a virtualenv:\n\n```bash\n$ python3 -m pip install .[all,test,type-check]\n```\n\nRun tests:\n\n```bash\n$ pytest\n# or\n$ make test\n```\n\nRun type checker:\n\n```bash\n$ mypy\n```\n\nLinters and formatters are set up with [goose], after installing it you can run it as:\n\n```bash\n# run all checks\n$ goose run --select=all\n# or just a single hook\n$ goose run mypy --select=all\n```\n\nIn addition to static type checking, the project is set up with [pytest-mypy-plugins] to\ntest that exposed mypy types work as expected, these checks will run together with the\nrest of the test suite, but you can single them out with the following command.\n\n```bash\n$ make test-typing\n```\n\n[parse]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/\n[ghosts]: https://kataskeue.com/gdp.pdf\n[build-status]:\n  https://github.com/antonagestam/phantom-types/actions?query=workflow%3ACI+branch%3Amain\n[coverage]: https://codecov.io/gh/antonagestam/phantom-types\n[typeguard]: https://github.com/agronholm/typeguard\n[beartype]: https://github.com/beartype/beartype\n[dbc]: https://en.wikipedia.org/wiki/Design_by_contract\n[pydantic]: https://pydantic-docs.helpmanual.io/\n[pydantic-support]:\n  https://phantom-types.readthedocs.io/en/stable/pages/pydantic-support.html\n[goose]: https://github.com/antonagestam/goose\n[pytest-mypy-plugins]: https://github.com/TypedDjango/pytest-mypy-plugins\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment:\n  require_changes: true\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# This file only contains a selection of the most common options. For a full list see\n# the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\nimport os\nimport pathlib\nimport sys\n\n# If extensions (or modules to document with autodoc) are in another directory, add\n# these directories to sys.path here. If the directory is relative to the documentation\n# root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath(\"../src\"))\n\nimport phantom\n\ncurrent_dir = pathlib.Path(__file__).resolve().parent\n\n\ndef get_copyright_from_license() -> str:\n    license_path = current_dir.parent / \"LICENSE\"\n    prefix = \"Copyright (c) \"\n    for line in license_path.read_text().split(\"\\n\"):\n        if line.startswith(prefix):\n            return line[len(prefix) :]\n    raise RuntimeError(\"Couldn't parse copyright from LICENSE\")\n\n\n# Project information\nproject = \"phantom-types\"\ncopyright = get_copyright_from_license()  # noqa: A001\nauthor = \"Anton Agestam\"\nversion = phantom.__version__\nrelease = version\n\n# Add any Sphinx extension module names here, as strings. They can be extensions coming\n# with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.autosummary\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx_autodoc_typehints\",\n]  #\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# List of patterns, relative to source directory, that match files and  directories to\n# ignore when looking for source files. This pattern also affects html_static_path and\n# html_extra_path.\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n# The theme to use for HTML and HTML Help pages.\nhtml_theme = \"furo\"\n# Set typing.TYPE_CHECKING to True to enable \"expensive\" typing imports.\nset_type_checking_flag = True\ntypehints_fully_qualified = True\nalways_document_param_types = True\n\n# Add any paths that contain custom static files (such as style sheets) here, relative\n# to this directory. They are copied after the builtin static files, so a file named\n# \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = []\n\n# Keep source order instead of sorting members alphabetically.\nautodoc_member_order = \"bysource\"\n"
  },
  {
    "path": "docs/index.rst",
    "content": "phantom-types\n=============\n\nThis is the documentation of phantom-types, a library that let's you arbitrarily narrow\nbuiltin types, without any runtime overhead. Full documentation and reference is\navailable here, you will hopefully find what you're looking for in the sections below.\n\nDocumentation sections\n----------------------\n\n.. toctree::\n  :maxdepth: 1\n\n  pages/getting-started.rst\n  pages/types.rst\n  pages/predicates.rst\n  pages/composing-types.rst\n  pages/functional-composition.rst\n  pages/external-wrappers.rst\n  pages/pydantic-support.rst\n  pages/implementation.rst\n"
  },
  {
    "path": "docs/pages/composing-types.rst",
    "content": ".. _composing:\n\nComposing types\n***************\n\nBounds\n======\n\nThe bound of a phantom type is the type that its values will have at runtime, so when\nchecking if a value is an instance of a phantom type, it's first checked to be within\nits bounds, so that the value can be safely passed as argument to the predicate\nfunction of the type.\n\nWhen subclassing, the bound of the new type must be a subtype of the bound of the super\nclass.\n\nThe bound of a phantom type is exposed as :attr:`phantom.Phantom.__bound__` for\nintrospection.\n\nResolution order\n~~~~~~~~~~~~~~~~\n\nThe bound of a phantom type is resolved in the order: explicitly by class argument,\nimplicitly by base classes, or implicitly inheritance, e.g.:\n\n.. code-block:: python\n\n    # Resolved by an explicit class arg:\n    class A(Phantom, bound=str, predicate=...):\n        ...\n\n\n    # Resolved implicitly as any base classes before Phantom:\n    class B(str, Phantom, predicate=...):\n        ...\n\n\n    # Resolves to str by inheritance from B:\n    class C(B):\n        ...\n\nAbstract bounds\n~~~~~~~~~~~~~~~\n\nIt's sometimes useful to create base classes without specifying a bound type. To do so\nthe class can be made abstract by passing ``abstract=True`` as a class argument:\n\n.. code-block:: python\n\n    class Base(Phantom, abstract=True):\n        ...\n\n\n    class Concrete(str, Base):\n        ...\n\nThis is for instance used by the shipped\n:ref:`numeric interval types <numeric-intervals>`.\n\nBound erasure\n~~~~~~~~~~~~~\n\nIf a phantom type doesn't properly specify its bounds, in addition to risking passing\ninvalid arguments to its predicate function, it is also likely that a static type\nchecker might inadvertently erase the runtime type when type guarding.\n\nAs an example, this code will error on the access to ``dt.year`` because\n``UTCDateTime.parse()`` has made the type checker erase the knowledge that dt is a\n``datetime``.\n\n.. code-block:: python\n\n    class UTCDateTime(Phantom, predicate=is_utc):\n        ...\n\n\n    dt = UTCDateTime.parse(now())\n    dt.year  # Error!\n\nIn this example we could remedy this by adding ``datetime`` as a base class and bound.\n\n.. code-block:: python\n\n    class UTCDateTime(datetime.datetime, Phantom, predicate=is_utc):\n        ...\n\nMutability\n==========\n\nPhantom types are completely incompatible with mutable data and should never be used to\nnarrow a mutable type. The reason is that there is no way for a type checker to detect\nthat a mutation changes an object to no longer satisfy the predicate of a phantom\ntype. For example:\n\n.. code-block:: python\n\n    class Mutable:\n        def __init__(self, len: int):\n            self.len = len\n\n        def __len__(self) -> int:\n            return self.len\n\n\n    # A phantom type that checks that a list has more than 2 items.\n    class HasMany(Mutable, Phantom, predicate=count(greater(2))):\n        ...\n\n\n    # The check will pass because the instantiated object *currently* satisfies the\n    # predicate, e.g. has len() > 2.\n    instance = HasMany.parse(Mutable(3))\n\n    # But! The object is mutable, so nothing is stopping us from altering it's length.\n    # At this point the object will no longer satisfy the HasMany predicate.\n    instance.len = 2\n\n    # There is no way for a type checker to know that the predicate isn't fulfilled\n    # anymore, so the revealed type here will still be HasMany.\n    reveal_type(instance)  # Revealed type is HasMany\n\nWhen subclassing from :py:class:`Phantom <phantom.Phantom>`, a check is made that raises\n:py:class:`MutableType <phantom.utils.MutableType>` for known mutable types, such as\n:py:class:`list`, :py:class:`set`, :py:class:`dict` and unfrozen dataclasses. In the\ngeneral case though, it isn't possible to detect mutability and so it's up to\ndeveloper discipline to make sure not to mix mutable data types with phantom types.\n\nMetaclass conflicts\n===================\n\nPhantom types are implemented using a metaclass. When creating a phantom type that\nnarrows on a type that also uses a metaclass it's common to stumble into a metaclass\nconflict. The usual solution to such situation is to create a new metaclass that\ninherits both existing metaclasses and base the new type on it.\n\n.. code-block:: python\n\n    from phantom import PhantomMeta\n\n\n    class NewMeta(PhantomMeta, OldMeta):\n        ...\n\n\n    class New(Old, Phantom, metaclass=NewMeta):\n        ...\n"
  },
  {
    "path": "docs/pages/external-wrappers.rst",
    "content": "External wrappers\n=================\n\nA collection of phantom types that wraps functionality of well maintained\nimplementations of third-party validation libraries. Importing from ``phantom.ext.*``\nshould be a hint that more dependencies need to be installed.\n\nPhone numbers\n-------------\n\n.. automodule:: phantom.ext.phonenumbers\n\nTypes\n^^^^^\n\n.. autoclass:: phantom.ext.phonenumbers.PhoneNumber\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.ext.phonenumbers.FormattedPhoneNumber\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nFunctions\n^^^^^^^^^\n\n.. autofunction:: phantom.ext.phonenumbers.is_phone_number\n\n.. autofunction:: phantom.ext.phonenumbers.is_formatted_phone_number\n\n.. autofunction:: phantom.ext.phonenumbers.normalize_phone_number\n\n\nExceptions\n^^^^^^^^^^\n\n.. autoexception:: phantom.ext.phonenumbers.InvalidPhoneNumber\n    :show-inheritance:\n"
  },
  {
    "path": "docs/pages/functional-composition.rst",
    "content": "Functional composition\n======================\n\nWhen composing predicates for phantom types it won't take long before you reach after\nfunctional composition. Unfortunately, fully supporting a typed n-ary ``compose``\nfunctions isn't yet feasible without shipping a custom mypy plugin. In lieu of having\nthat effort in place, a simpler but fully typed :py:func:`compose2\n<phantom.fn.compose2>` function is shipped. The door, however, is left wide open for the\npossibility of shipping an n-ary, generalized compose function in the future.\n\n.. automodule:: phantom.fn\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/pages/getting-started.rst",
    "content": "Getting Started\n===============\n\nCreating phantom types\n----------------------\n\nPhantom types are created by subclassing :py:class:`Phantom <phantom.Phantom>` and\nproviding a predicate function.\n\n.. code-block:: python\n\n    from phantom import Phantom\n\n\n    # A boolean predicate that checks if a given string is a greeting. This function is\n    # of type ``Predicate[str]`` as it requires its argument to be a ``str``.\n    def is_greeting(instance: str) -> bool:\n        return instance.startswith((\"Hello\", \"Hi\"))\n\n\n    # Since our predicate requires its argument to be a ``str``, we must make the bound\n    # of the phantom type ``str`` as well. We do that by making it it's first base. Any\n    # base specified before Phantom is implicitly interpreted as its bound, unless an\n    # explicit bound is specified as a class argument.\n    class Greeting(str, Phantom, predicate=is_greeting):\n        ...\n\n\n    hello = \"Hello there\"\n    # We can narrow types using mypy's type guards\n    assert isinstance(hello, Greeting)\n    # or explicitly when we need to\n    hi = Greeting.parse(\"Hi there\")\n\n    # The runtime types are unchanged and will still be str for our greetings\n    assert type(hello) is str\n    assert type(hi) is str\n\n    # But their static types will be Greeting, retaining the information that our\n    # strings are not just any strs\n    if TYPE_CHECKING:\n        reveal_type(hello)\n        reveal_type(hi)\n\n    # As this string doesn't fulfill our __instancecheck__, it will not be an\n    # instance of Greeting.\n    assert not isinstance(\"Goodbye\", Greeting)\n\nA motivating example\n--------------------\n\nImagine that you're working on implementing a ``head()`` function that should return the\nfirst item of any given iterable. You start out with a simple implementation:\n\n.. code-block:: python\n\n    def head(iterable: Iterable[T]) -> T:\n        return next(iter(iterable))\n\nYou go ahead and use this function across your project, until suddenly you run into a\nsubtle issue that you didn't think of: this function raises ``StopIteration`` when\npassed an empty iterable. In functional programming terms this is due to the function\nbeing *partial* it specifies that it takes ``Iterable`` as argument, but in reality we\nwould need a narrower type to describe the set of valid arguments, and make the function\n*total*.\n\nYou need to deal with the problem at hand so you go ahead and adjust all the call sites\nof your function, and you now end up either asserting that the iterables are non-empty,\nor catching the `StopIteration`.\n\n.. code-block:: python\n\n    items = get_values()\n    if not len(items):\n        return \"empty\"\n    return f\"first element is: {head(items)}\"\n\nThis works, and you could move on like this from here, but, you have now introduced\nshotgun parsing into your application, since further down the processing line you need\nto check the length if the iterable for other purposes. Shotgun parsing is an\nanti-pattern that results in a program state that is hard to predict and will very\nlikely lead to bugs down the line. So how should you deal with this?\n\nUsing phantom types you can use the builtin :class:`phantom.sized.NonEmpty` type.\n\n.. code-block:: python\n\n    def head(iterable: NonEmpty[T]) -> T:\n        return next(iter(iterable))\n\nThe implementation is identical but you've now altered the signature of the function so\nthat it's total, it can deal with *all* values of its argument type without raising an\nexception.\n\nBy using the narrower type at the call sites, you avoid shotgun parsing, since the other\nlogic further down in the processing chain can rely on the type as well, and you won't\nneed to check the length of the iterable again.\n\n.. code-block:: python\n\n    items = get_values()\n    if not isinstance(items, NonEmpty):\n        return \"empty\"\n    return f\"first element is: {head(items)}\"\n\nThis strategy works in all places where a function works on a narrower type than you can\ndescribe with the builtin types of Python, not only this made-up example. You can narrow\nstrings, integers, datetimes, and any other arbitrary types to completely rid of\nduplicated validation throughout a code base.\n\nThere's a set of phantom types that ships builtin that are helpful to build on top of,\nalthough you might mostly use your own custom phantom types that describe the exact\nvalues that your implementations require.\n\nUsing predicates\n----------------\n\nThe phantom-types library relies heavily on boolean predicates. A boolean predicate is\nsimply a function that takes a single argument and returns either ``True`` or ``False``.\nWhile using boolean predicates is not necessary to create phantom types, building up a\nlibrary of types doing so allows reusing small and easily testable functions to create a\nplethora of specialized types. Boolean predicates are usually easy to reason about as\nthey are pure functions with only two possible return values.\n\nStudying the phantom types shipped in this library is recommended for gaining deeper\ninsight into how to implement more complicated types.\n\nNext steps\n----------\n\n- Check out the :ref:`builtin phantom types <types>` that is shipped with the library.\n- Check out the basis of :ref:`predicates and predicate<predicates>`\n  factories to build phantom types from.\n- Read more in-depth about :ref:`composing phantom types <composing>`.\n"
  },
  {
    "path": "docs/pages/implementation.rst",
    "content": "Implementation\n==============\n\nHow are phantom types implemented?\n----------------------------------\n\nphantom-types make use of Python's ``__instancecheck__`` protocol to make types work\nwith the same checks that are recognized as type guards by static type checkers, e.g.\n``isinstance()``. Phantom types are never instantiated at runtime and so will not add\nany processing-, or memory overhead. Instead the question of whether a value is properly\nparsed before it is processed is deferred to the static type checker.\n\nThe choice to design the library around boolean predicates, and the fact that much of\nthe initially shipped builtin types use predicates, are heavily inspired by the\n`fthomas/refined <https://github.com/fthomas/refined>`_ library.\n"
  },
  {
    "path": "docs/pages/predicates.rst",
    "content": ".. _predicates:\n\nPredicates and factories\n========================\n\nPredicates are functions that return a boolean value given a single argument. These\nmodules contain predicate functions, and functions that return predicates, that can be\ncomposed and used for phantom types.\n\nBoolean logic\n-------------\n\n.. automodule:: phantom.predicates.boolean\n    :members:\n    :undoc-members:\n\nCollection\n----------\n\n.. automodule:: phantom.predicates.collection\n    :members:\n    :undoc-members:\n\nDatetime\n--------\n\n.. automodule:: phantom.predicates.datetime\n    :members:\n    :undoc-members:\n\nGeneric\n-------\n\n.. automodule:: phantom.predicates.generic\n    :members:\n\nNumeric intervals\n-----------------\n\n.. automodule:: phantom.predicates.interval\n    :members:\n    :undoc-members:\n\nNumeric\n-------\n\n.. automodule:: phantom.predicates.numeric\n    :members:\n    :undoc-members:\n\nRegular expressions\n-------------------\n\n.. automodule:: phantom.predicates.re\n    :members:\n    :undoc-members:\n"
  },
  {
    "path": "docs/pages/pydantic-support.rst",
    "content": "Pydantic Support\n================\n\nphantom-types supports pydantic_ out of the box by providing a\n:func:`__get_validators__() <phantom.Phantom.__get_validators__>` hook\non the base :class:`Phantom <phantom.Phantom>` class. Most of the shipped types also\nimplements full JSON Schema and OpenAPI support.\n\n.. _pydantic: https://pydantic-docs.helpmanual.io/\n\nTo make a phantom type compatible with pydantic, all you need to do is override\n:func:`Phantom.__schema__() <phantom.Phantom.__schema__>`:\n\n.. code-block:: python\n\n    from phantom import Phantom\n    from phantom.schema import Schema\n\n\n    class Name(str, Phantom, predicate=...):\n        @classmethod\n        def __schema__(cls) -> Schema:\n            return super().__schema__() | Schema(\n                description=\"A type for names\",\n                format=\"name-format\",\n            )\n\nAs can be seen in the example, ``__schema__()`` implementations are expected to return a\ndict extending its ``super().__schema__()``, however this is not a requirement and any\n:class:`Schema <phantom.schema.Schema>`-compatible ``dict`` can be returned.\n"
  },
  {
    "path": "docs/pages/types.rst",
    "content": ".. _types:\n\nTypes\n=====\n\nBase classes\n------------\n\n.. automodule:: phantom\n\n.. autoclass:: Phantom\n    :members:\n    :undoc-members:\n    :show-inheritance:\n    :inherited-members:\n    :special-members: __schema__, __modify_schema__, __get_validators__, __bound__\n\nBoolean\n-------\n\n.. automodule:: phantom.boolean\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nCountry codes\n-------------\n\n.. automodule:: phantom.iso3166\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nDatetime\n--------\n\n.. automodule:: phantom.datetime\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nNegated types\n-------------\n\n.. automodule:: phantom.negated\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. _numeric-intervals:\n\nNumeric intervals\n-----------------\n\n.. automodule:: phantom.interval\n\nBase classes\n^^^^^^^^^^^^\n\n.. autoclass:: phantom.interval.Interval\n    :members: __check__\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.Exclusive\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.Inclusive\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.ExclusiveInclusive\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.InclusiveExclusive\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nConcrete interval types\n^^^^^^^^^^^^^^^^^^^^^^^\n\n.. autoclass:: phantom.interval.Natural\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.NegativeInt\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\n.. autoclass:: phantom.interval.Portion\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nRegular expressions\n-------------------\n\n.. automodule:: phantom.re\n    :members:\n    :undoc-members:\n    :show-inheritance:\n\nSized collections\n-----------------\n\n.. automodule:: phantom.sized\n    :members:\n    :undoc-members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs-requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    'make docs-requirements'\naccessible-pygments==0.0.5 \\\n    --hash=sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872 \\\n    --hash=sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7\n    # via furo\nalabaster==1.0.0 \\\n    --hash=sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e \\\n    --hash=sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b\n    # via sphinx\nbabel==2.17.0 \\\n    --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \\\n    --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2\n    # via sphinx\nbeartype==0.22.5 \\\n    --hash=sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341 \\\n    --hash=sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0\n    # via numerary\nbeautifulsoup4==4.14.2 \\\n    --hash=sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e \\\n    --hash=sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515\n    # via furo\ncertifi==2025.10.5 \\\n    --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \\\n    --hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43\n    # via requests\ncharset-normalizer==3.4.4 \\\n    --hash=sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad \\\n    --hash=sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93 \\\n    --hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \\\n    --hash=sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89 \\\n    --hash=sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc \\\n    --hash=sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 \\\n    --hash=sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63 \\\n    --hash=sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d \\\n    --hash=sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f \\\n    --hash=sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8 \\\n    --hash=sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0 \\\n    --hash=sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505 \\\n    --hash=sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161 \\\n    --hash=sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af \\\n    --hash=sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152 \\\n    --hash=sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318 \\\n    --hash=sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72 \\\n    --hash=sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4 \\\n    --hash=sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e \\\n    --hash=sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3 \\\n    --hash=sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576 \\\n    --hash=sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c \\\n    --hash=sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1 \\\n    --hash=sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8 \\\n    --hash=sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1 \\\n    --hash=sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2 \\\n    --hash=sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44 \\\n    --hash=sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26 \\\n    --hash=sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88 \\\n    --hash=sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 \\\n    --hash=sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede \\\n    --hash=sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf \\\n    --hash=sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a \\\n    --hash=sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc \\\n    --hash=sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0 \\\n    --hash=sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84 \\\n    --hash=sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db \\\n    --hash=sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1 \\\n    --hash=sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7 \\\n    --hash=sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed \\\n    --hash=sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 \\\n    --hash=sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133 \\\n    --hash=sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e \\\n    --hash=sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef \\\n    --hash=sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14 \\\n    --hash=sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2 \\\n    --hash=sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0 \\\n    --hash=sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d \\\n    --hash=sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828 \\\n    --hash=sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f \\\n    --hash=sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf \\\n    --hash=sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6 \\\n    --hash=sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328 \\\n    --hash=sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090 \\\n    --hash=sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa \\\n    --hash=sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 \\\n    --hash=sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c \\\n    --hash=sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb \\\n    --hash=sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc \\\n    --hash=sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a \\\n    --hash=sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec \\\n    --hash=sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc \\\n    --hash=sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac \\\n    --hash=sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e \\\n    --hash=sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313 \\\n    --hash=sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569 \\\n    --hash=sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3 \\\n    --hash=sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d \\\n    --hash=sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525 \\\n    --hash=sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 \\\n    --hash=sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3 \\\n    --hash=sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9 \\\n    --hash=sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a \\\n    --hash=sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9 \\\n    --hash=sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 \\\n    --hash=sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 \\\n    --hash=sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50 \\\n    --hash=sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf \\\n    --hash=sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1 \\\n    --hash=sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3 \\\n    --hash=sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac \\\n    --hash=sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e \\\n    --hash=sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815 \\\n    --hash=sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c \\\n    --hash=sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6 \\\n    --hash=sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6 \\\n    --hash=sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e \\\n    --hash=sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4 \\\n    --hash=sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84 \\\n    --hash=sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69 \\\n    --hash=sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15 \\\n    --hash=sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191 \\\n    --hash=sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0 \\\n    --hash=sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897 \\\n    --hash=sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd \\\n    --hash=sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2 \\\n    --hash=sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 \\\n    --hash=sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d \\\n    --hash=sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074 \\\n    --hash=sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3 \\\n    --hash=sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224 \\\n    --hash=sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838 \\\n    --hash=sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a \\\n    --hash=sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d \\\n    --hash=sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d \\\n    --hash=sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f \\\n    --hash=sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8 \\\n    --hash=sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490 \\\n    --hash=sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966 \\\n    --hash=sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9 \\\n    --hash=sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3 \\\n    --hash=sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e \\\n    --hash=sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608\n    # via requests\ndocutils==0.21.2 \\\n    --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \\\n    --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2\n    # via sphinx\nfuro==2025.9.25 \\\n    --hash=sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe \\\n    --hash=sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98\n    # via phantom-types (pyproject.toml)\nhypothesis==6.147.0 \\\n    --hash=sha256:72e6004ea3bd1460bdb4640b6389df23b87ba7a4851893fd84d1375635d3e507 \\\n    --hash=sha256:de588807b6da33550d32f47bcd42b1a86d061df85673aa73e6443680249d185e\n    # via phantom-types (pyproject.toml)\nidna==3.11 \\\n    --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \\\n    --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902\n    # via requests\nimagesize==1.4.1 \\\n    --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \\\n    --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a\n    # via sphinx\njinja2==3.1.6 \\\n    --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \\\n    --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67\n    # via sphinx\nmarkupsafe==3.0.3 \\\n    --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \\\n    --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \\\n    --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \\\n    --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \\\n    --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \\\n    --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \\\n    --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \\\n    --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \\\n    --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \\\n    --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \\\n    --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \\\n    --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \\\n    --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \\\n    --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \\\n    --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \\\n    --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \\\n    --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \\\n    --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \\\n    --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \\\n    --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \\\n    --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \\\n    --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \\\n    --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \\\n    --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \\\n    --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \\\n    --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \\\n    --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \\\n    --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \\\n    --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \\\n    --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \\\n    --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \\\n    --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \\\n    --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \\\n    --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \\\n    --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \\\n    --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \\\n    --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \\\n    --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \\\n    --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \\\n    --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \\\n    --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \\\n    --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \\\n    --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \\\n    --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \\\n    --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \\\n    --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \\\n    --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \\\n    --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \\\n    --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \\\n    --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \\\n    --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \\\n    --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \\\n    --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \\\n    --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \\\n    --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \\\n    --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \\\n    --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \\\n    --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \\\n    --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \\\n    --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \\\n    --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \\\n    --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \\\n    --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \\\n    --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \\\n    --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \\\n    --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \\\n    --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \\\n    --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \\\n    --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \\\n    --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \\\n    --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \\\n    --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \\\n    --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \\\n    --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \\\n    --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \\\n    --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \\\n    --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \\\n    --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \\\n    --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \\\n    --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \\\n    --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \\\n    --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \\\n    --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \\\n    --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \\\n    --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \\\n    --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \\\n    --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \\\n    --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \\\n    --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50\n    # via jinja2\nnumerary==0.4.4 \\\n    --hash=sha256:ad955ddf7f5275f8e52f5520b2d6c654cc3bf1e3ae4bfb45664c9d51b208d0c6\n    # via phantom-types (pyproject.toml)\npackaging==25.0 \\\n    --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \\\n    --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f\n    # via sphinx\nphonenumbers==9.0.18 \\\n    --hash=sha256:5537c61ba95b11b992c95e804da6e49193cc06b1224f632ade64631518a48ed1 \\\n    --hash=sha256:d3354454ac31c97f8a08121df97a7145b8dca641f734c6f1518a41c2f60c5764\n    # via phantom-types (pyproject.toml)\npydantic==1.10.24 \\\n    --hash=sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c \\\n    --hash=sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d \\\n    --hash=sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193 \\\n    --hash=sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6 \\\n    --hash=sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624 \\\n    --hash=sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667 \\\n    --hash=sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2 \\\n    --hash=sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c \\\n    --hash=sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621 \\\n    --hash=sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52 \\\n    --hash=sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816 \\\n    --hash=sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f \\\n    --hash=sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813 \\\n    --hash=sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49 \\\n    --hash=sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666 \\\n    --hash=sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a \\\n    --hash=sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a \\\n    --hash=sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683 \\\n    --hash=sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1 \\\n    --hash=sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c \\\n    --hash=sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94 \\\n    --hash=sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f \\\n    --hash=sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47 \\\n    --hash=sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702 \\\n    --hash=sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd \\\n    --hash=sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70 \\\n    --hash=sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53 \\\n    --hash=sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b \\\n    --hash=sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36 \\\n    --hash=sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c \\\n    --hash=sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993 \\\n    --hash=sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b \\\n    --hash=sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8 \\\n    --hash=sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01 \\\n    --hash=sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69 \\\n    --hash=sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3 \\\n    --hash=sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00 \\\n    --hash=sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37 \\\n    --hash=sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2 \\\n    --hash=sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6 \\\n    --hash=sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d \\\n    --hash=sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def \\\n    --hash=sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574 \\\n    --hash=sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6 \\\n    --hash=sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c \\\n    --hash=sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de \\\n    --hash=sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0 \\\n    --hash=sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579 \\\n    --hash=sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4 \\\n    --hash=sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb\n    # via phantom-types (pyproject.toml)\npygments==2.19.2 \\\n    --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \\\n    --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b\n    # via\n    #   accessible-pygments\n    #   furo\n    #   sphinx\npython-dateutil==2.9.0.post0 \\\n    --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \\\n    --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427\n    # via phantom-types (pyproject.toml)\nrequests==2.32.5 \\\n    --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \\\n    --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf\n    # via sphinx\nroman-numerals-py==3.1.0 \\\n    --hash=sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c \\\n    --hash=sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d\n    # via sphinx\nsix==1.17.0 \\\n    --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \\\n    --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81\n    # via python-dateutil\nsnowballstemmer==3.0.1 \\\n    --hash=sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064 \\\n    --hash=sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895\n    # via sphinx\nsortedcontainers==2.4.0 \\\n    --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \\\n    --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0\n    # via hypothesis\nsoupsieve==2.8 \\\n    --hash=sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c \\\n    --hash=sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f\n    # via beautifulsoup4\nsphinx==8.2.3 \\\n    --hash=sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348 \\\n    --hash=sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3\n    # via\n    #   phantom-types (pyproject.toml)\n    #   furo\n    #   sphinx-autodoc-typehints\n    #   sphinx-basic-ng\nsphinx-autodoc-typehints==3.5.2 \\\n    --hash=sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c \\\n    --hash=sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb\n    # via phantom-types (pyproject.toml)\nsphinx-basic-ng==1.0.0b2 \\\n    --hash=sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9 \\\n    --hash=sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b\n    # via furo\nsphinxcontrib-applehelp==2.0.0 \\\n    --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \\\n    --hash=sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5\n    # via sphinx\nsphinxcontrib-devhelp==2.0.0 \\\n    --hash=sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad \\\n    --hash=sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2\n    # via sphinx\nsphinxcontrib-htmlhelp==2.1.0 \\\n    --hash=sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 \\\n    --hash=sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9\n    # via sphinx\nsphinxcontrib-jsmath==1.0.1 \\\n    --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \\\n    --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8\n    # via sphinx\nsphinxcontrib-qthelp==2.0.0 \\\n    --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \\\n    --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb\n    # via sphinx\nsphinxcontrib-serializinghtml==2.0.0 \\\n    --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \\\n    --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d\n    # via sphinx\ntypeguard==4.4.4 \\\n    --hash=sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74 \\\n    --hash=sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e\n    # via phantom-types (pyproject.toml)\ntyping-extensions==4.15.0 \\\n    --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \\\n    --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548\n    # via\n    #   phantom-types (pyproject.toml)\n    #   beautifulsoup4\n    #   pydantic\n    #   typeguard\nurllib3==2.5.0 \\\n    --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \\\n    --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc\n    # via requests\n"
  },
  {
    "path": "goose.yaml",
    "content": "environments:\n  - ecosystem:\n      language: python\n      version: \"3.13\"\n    dependencies:\n      - ruff\n      - pre-commit-hooks\n      - editorconfig-checker\n      - blacken-docs\n      - check-jsonschema\n\n  - id: check-manifest\n    ecosystem:\n      language: python\n      version: \"3.13\"\n    dependencies:\n      - check-manifest\n      - setuptools==80.9.0\n      - setuptools-scm==8.3.1\n      - wheel==0.45.1\n\n  - ecosystem: node\n    dependencies:\n      - prettier\n\nhooks:\n  - id: check-manifest\n    environment: check-manifest\n    command: check-manifest\n    parameterize: false\n    read_only: true\n    args: [--no-build-isolation]\n\n  - id: prettier\n    environment: node\n    command: prettier\n    types: [markdown]\n    args:\n      - --write\n      - --ignore-unknown\n      - --parser=markdown\n      - --print-width=88\n      - --prose-wrap=always\n\n  - id: check-case-conflict\n    environment: python\n    command: check-case-conflict\n    read_only: true\n\n  - id: check-merge-conflict\n    environment: python\n    command: check-merge-conflict\n    read_only: true\n    types: [text]\n\n  - id: python-debug-statements\n    environment: python\n    command: debug-statement-hook\n    read_only: true\n    types: [python]\n\n  - id: detect-private-key\n    environment: python\n    command: detect-private-key\n    read_only: true\n    types: [text]\n\n  - id: end-of-file-fixer\n    environment: python\n    command: end-of-file-fixer\n    types: [text]\n\n  - id: trailing-whitespace-fixer\n    environment: python\n    command: trailing-whitespace-fixer\n    types: [text]\n\n  - id: editorconfig-checker\n    environment: python\n    command: ec\n    args: [-disable-indent-size]\n    types: [text]\n    read_only: true\n\n  - id: ruff-check\n    environment: python\n    command: ruff\n    args: [check, --force-exclude, --fix]\n    types: [python]\n\n  - id: ruff-format\n    environment: python\n    command: ruff\n    args: [format, --force-exclude]\n    types: [python]\n\n  - id: blacken-docs\n    environment: python\n    command: blacken-docs\n    types: [markdown, python]\n\n  - id: check-github-workflows\n    environment: python\n    command: check-jsonschema\n    read_only: true\n    args: [\"--builtin-schema\", \"vendor.github-workflows\"]\n    types: [yaml]\n    limit:\n      - \"^.github/workflows/\"\n"
  },
  {
    "path": "mypy.ini",
    "content": "[mypy]\npython_version = 3.10\npretty = True\nfiles = src, tests\nshow_error_codes = True\nshow_error_context = True\nshow_error_code_links = True\nstrict = True\nwarn_unreachable = True\ndisallow_any_generics = False\n\n[mypy-tests.*]\ndisallow_untyped_defs = False\ndisallow_untyped_calls = False\ndisallow_any_expr = False\ndisallow_untyped_decorators = False\ndisallow_incomplete_defs = False\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n  \"setuptools==80.9.0\",\n  \"setuptools-scm==8.3.1\",\n  \"wheel==0.45.1\",\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.setuptools_scm]\nversion_file = \"src/phantom/_version.py\"\n\n[tool.setuptools]\ninclude-package-data = true\n\n[tool.setuptools.dynamic]\nreadme = {file = \"README.md\", content-type = \"text/markdown; charset=UTF-8\"}\n\n[tool.setuptools.packages.find]\nwhere = [\"src\"]\nnamespaces = false\n\n\n[project]\nname = \"phantom-types\"\ndescription = \"Phantom types for Python\"\nrequires-python = \">=3.10\"\nauthors = [\n  { name=\"Anton Agestam\", email=\"git@antonagestam.se\" },\n]\nlicense = {text = \"BSD-3-Clause\"}\nclassifiers = [\n  \"Intended Audience :: Developers\",\n  \"Operating System :: OS Independent\",\n  \"Programming Language :: Python\",\n  \"Programming Language :: Python :: 3\",\n  \"Programming Language :: Python :: 3 :: Only\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: 3.13\",\n  \"Development Status :: 5 - Production/Stable\",\n]\ndynamic = [\"version\", \"readme\"]\ndependencies = [\n  # typeguard 4.3.0 breaks \"intersection\" protocols, see linked issue. I didn't figure\n  # out a way to work around this at the moment, so it needs to be pinned.\n  # https://github.com/antonagestam/phantom-types/issues/299\n  \"typeguard>=4,!=4.3.*\",\n  \"typing_extensions>=4.3.0\",\n  \"numerary>=0.4.3\",\n]\n\n[project.optional-dependencies]\nphonenumbers = [\"phonenumbers>=8.12.41\"]\npydantic = [\"pydantic>=1.9.0,<2\"]\ndateutil = [\"python-dateutil>=2.8.2\"]\nhypothesis = [\"hypothesis[zoneinfo]>=6.68.0\"]\nall = [\n  \"phantom-types[phonenumbers]\",\n  \"phantom-types[pydantic]\",\n  \"phantom-types[dateutil]\",\n  \"phantom-types[hypothesis]\",\n]\ntest = [\n  \"mypy>=1.16.1\",\n  \"pytest\",\n  \"pytest-mypy-plugins>=1.9.3\",\n  \"coverage\",\n]\ntype-check = [\n  \"phantom-types[all]\",\n  \"mypy\",\n  \"pytest\",\n  \"types-python-dateutil\",\n]\ndocs = [\n  \"phantom-types[all]\",\n  \"sphinx\",\n  \"sphinx-autodoc-typehints\",\n  \"furo\",\n]\n\n[project.urls]\n\"Source Repository\" = \"https://github.com/antonagestam/phantom-types/\"\n\"Documentation\" = \"https://phantom-types.readthedocs.io/en/stable/\"\n\n\n[tool.check-manifest]\nignore = [\"src/phantom/_version.py\"]\n\n\n[tool.pip-tools]\ngenerate-hashes = true\nstrip-extras = true\nupgrade = true\nunsafe-package = [\"phantom-types\"]\n\n\n[tool.black]\ntarget-version = [\"py39\"]\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\", \"src\", \"docs\"]\naddopts = \"--mypy-ini-file=mypy.ini --mypy-only-local-stub --doctest-modules --import-mode=importlib\"\nmarkers = [\n  \"external: mark tests that require extra dependencies\",\n  \"no_external: mark tests that will fail if run with extra dependencies\",\n]\n"
  },
  {
    "path": "ruff.toml",
    "content": "fix = true\ntarget-version = \"py310\"\n\n[lint]\nextend-select = [\n  # bugbear\n  \"B\",\n  # comprehensions\n  \"C4\",\n  # mccabe\n  \"C90\",\n  # bandit\n  \"S\",\n  # blind exception\n  # Bare excepts are caught without this, but this also catches `except Exception: ...`.\n  \"BLE\",\n  # builtins\n  \"A\",\n  # Enforce valid noqa comments.\n  \"RUF100\",\n  # isort\n  \"I\",\n  # pycodestyle\n  \"W\",\n  # pyupgrade\n  \"UP\",\n  # debugger\n  \"T10\",\n  # print\n  \"T20\",\n  # quotes\n  \"Q\",\n  # return\n  # This gives 3 false positives, would be nice otherwise probably.\n  # \"RET\",\n  # simplify\n  \"SIM\",\n  # tidy imports\n  # We use this to only outlaw relative parent imports.\n  \"TID\",\n]\nextend-ignore = [\n  # There's no reason to outlaw asserts.\n  \"S101\",\n  # False positives.\n  \"A005\",\n]\nisort.force-single-line = true\nisort.known-first-party = [\"phantom\", \"tests\"]\nflake8-tidy-imports.ban-relative-imports = \"parents\"\nmccabe.max-complexity = 10\n"
  },
  {
    "path": "setup.cfg",
    "content": "[coverage:run]\nsource = phantom\nbranch = True\n\n[coverage:report]\nskip_covered = True\nshow_missing = True\nexclude_lines =\n    pragma: no cover\n    # ignore non-implementations\n    ^\\s*\\.\\.\\.\n    ^\\s*if TYPE_CHECKING:\n    ^\\s*def [\\w_]+\\(.*?\\) -> [\\w_]+: ...$\n"
  },
  {
    "path": "src/phantom/__init__.py",
    "content": "\"\"\"\nUse ``Phantom`` to create arbitrary phantom types using boolean predicates.\n\n.. code-block:: python\n\n    from phantom import Phantom\n\n\n    def is_big(value: int) -> bool:\n        return value > 5\n\n\n    class Big(int, Phantom, predicate=is_big): ...\n\n\n    assert isinstance(10, Big)  # this passes\n\"\"\"\n\nfrom ._base import Phantom\nfrom ._base import PhantomBase\nfrom ._base import PhantomMeta\nfrom ._version import __version__\nfrom ._version import __version_tuple__\nfrom .bounds import get_bound_parser\nfrom .errors import BoundError\nfrom .predicates import Predicate\n\n__all__ = (\n    \"__version__\",\n    \"__version_tuple__\",\n    \"BoundError\",\n    \"Phantom\",\n    \"PhantomBase\",\n    \"PhantomMeta\",\n    \"get_bound_parser\",\n    \"Predicate\",\n)\n"
  },
  {
    "path": "src/phantom/_base.py",
    "content": "from __future__ import annotations\n\nimport abc\nfrom collections.abc import Callable\nfrom collections.abc import Iterable\nfrom collections.abc import Iterator\nfrom typing import Any\nfrom typing import ClassVar\nfrom typing import Generic\nfrom typing import Protocol\nfrom typing import TypeVar\nfrom typing import runtime_checkable\n\nfrom typing_extensions import Self\n\nfrom . import _hypothesis\nfrom ._utils.misc import BoundType\nfrom ._utils.misc import UnresolvedClassAttribute\nfrom ._utils.misc import fully_qualified_name\nfrom ._utils.misc import is_not_known_mutable_type\nfrom ._utils.misc import is_subtype\nfrom ._utils.misc import resolve_class_attr\nfrom .bounds import Parser\nfrom .bounds import get_bound_parser\nfrom .errors import BoundError\nfrom .predicates import Predicate\nfrom .schema import SchemaField\n\n\n@runtime_checkable\nclass InstanceCheckable(Protocol):\n    @classmethod\n    @abc.abstractmethod\n    def __instancecheck__(cls, instance: object) -> bool: ...\n\n\nclass SupportsParse(Protocol):\n    @classmethod\n    @abc.abstractmethod\n    def parse(cls, instance: object) -> Self: ...\n\n\nV = TypeVar(\"V\", bound=SupportsParse)\n\n\nclass PhantomMeta(abc.ABCMeta):\n    \"\"\"\n    Metaclass that defers __instancecheck__ to derived classes and prevents actual\n    instance creation.\n    \"\"\"\n\n    def __instancecheck__(self, instance: object) -> bool:\n        if not issubclass(self, InstanceCheckable):\n            return False\n        return self.__instancecheck__(instance)\n\n    def __call__(cls: type[V], instance: object) -> V:\n        return cls.parse(instance)\n\n\nT = TypeVar(\"T\", covariant=True)\nU = TypeVar(\"U\")\n\n\nDerived = TypeVar(\"Derived\", bound=\"PhantomBase\")\n\n\nclass PhantomBase(SchemaField, metaclass=PhantomMeta):\n    @classmethod\n    def parse(cls: type[Derived], instance: object) -> Derived:\n        \"\"\"\n        Parse an arbitrary value into a phantom type.\n\n        :raises TypeError:\n        \"\"\"\n        if not isinstance(instance, cls):\n            raise TypeError(\n                f\"Could not parse {fully_qualified_name(cls)} from {instance!r}\"\n            )\n        return instance\n\n    @classmethod\n    @abc.abstractmethod\n    def __instancecheck__(cls, instance: object) -> bool: ...\n\n    @classmethod\n    def __get_validators__(cls: type[Derived]) -> Iterator[Callable[[object], Derived]]:\n        \"\"\"Hook that makes phantom types compatible with pydantic.\"\"\"\n        yield cls.parse\n\n\nclass AbstractInstanceCheck(TypeError): ...\n\n\nclass MutableType(TypeError): ...\n\n\nclass Phantom(PhantomBase, Generic[T]):\n    \"\"\"\n    Base class for predicate-based phantom types.\n\n    **Class arguments**\n\n    * ``predicate: Predicate[T] | None`` - Predicate function used for instance checks.\n      Can be ``None`` if the type is abstract.\n    * ``bound: type[T] | None`` - Bound used to check values before passing them to the\n      type's predicate function. This will often but not always be the same as the\n      runtime type that values of the phantom type are represented as. If this is not\n      provided as a class argument, it's attempted to be resolved in order from an\n      implicit bound (any bases of the type that come before ``Phantom``), or inherited\n      from super phantom types that provide a bound. Can be ``None`` if the type is\n      abstract.\n    * ``abstract: bool`` - Set to ``True`` to create an abstract phantom type. This\n      allows deferring definitions of ``predicate`` and ``bound`` to concrete subtypes.\n    \"\"\"\n\n    __predicate__: Predicate[T]\n    # The bound of a phantom type is the type that its values will have at\n    # runtime, so when checking if a value is an instance of a phantom type,\n    # it's first checked to be within its bounds, so that the value can be\n    # safely passed as argument to the predicate function.\n    #\n    # When subclassing, the bound of the new type must be a subtype of the bound\n    # of the super class.\n    __bound__: ClassVar[type]\n    __abstract__: ClassVar[bool]\n\n    def __init_subclass__(\n        cls,\n        predicate: Predicate[T] | None = None,\n        bound: type[T] | None = None,\n        abstract: bool = False,\n        **kwargs: Any,\n    ) -> None:\n        super().__init_subclass__(**kwargs)\n        resolve_class_attr(cls, \"__abstract__\", abstract)\n        resolve_class_attr(cls, \"__predicate__\", predicate)\n        cls._resolve_bound(bound)\n\n        if _hypothesis.register_type_strategy is not None and not cls.__abstract__:\n            strategy = cls.__register_strategy__()\n            if strategy is not None:\n                _hypothesis.register_type_strategy(cls, strategy)\n\n    @classmethod\n    def _interpret_implicit_bound(cls) -> BoundType:\n        def discover_bounds() -> Iterable[type]:\n            for type_ in cls.__mro__:\n                if type_ is cls:\n                    continue\n                if issubclass(type_, Phantom):\n                    break\n                yield type_\n            else:  # pragma: no cover\n                raise RuntimeError(f\"{cls} is not a subclass of Phantom\")\n\n        types = tuple(discover_bounds())\n        if len(types) == 1:\n            return types[0]\n        return types\n\n    @classmethod\n    def _resolve_bound(cls, class_arg: Any) -> None:\n        inherited = getattr(cls, \"__bound__\", None)\n        implicit = cls._interpret_implicit_bound()\n        if class_arg is not None:\n            bound = class_arg\n        elif implicit:\n            bound = implicit\n        elif inherited is not None:\n            bound = inherited\n        elif not getattr(cls, \"__abstract__\", False):\n            raise UnresolvedClassAttribute(\n                f\"Concrete phantom type {cls.__qualname__} must define class attribute \"\n                f\"__bound__.\"\n            )\n        else:\n            return\n\n        if inherited is not None and not is_subtype(bound, inherited):\n            raise BoundError(\n                f\"The bound of {cls.__qualname__} is not compatible with its \"\n                f\"inherited bounds.\"\n            )\n\n        if not is_not_known_mutable_type(bound):\n            raise MutableType(f\"The bound of {cls.__qualname__} is mutable.\")\n\n        cls.__bound__ = bound\n\n    @classmethod\n    def __instancecheck__(cls, instance: object) -> bool:\n        if cls.__abstract__:\n            raise AbstractInstanceCheck(\n                \"Abstract phantom types cannot be used in instance checks\"\n            )\n        bound_parser: Parser[T] = get_bound_parser(cls.__bound__)\n        try:\n            instance = bound_parser(instance)\n        except BoundError:\n            return False\n        return cls.__predicate__(instance)\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.HypothesisStrategy | None:\n        return None\n"
  },
  {
    "path": "src/phantom/_hypothesis.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable\nfrom typing import TYPE_CHECKING\nfrom typing import TypeAlias\nfrom typing import TypeVar\n\nif TYPE_CHECKING:\n    from hypothesis.strategies import SearchStrategy\nelse:\n    SearchStrategy = None\n\n\n__all__ = (\"HypothesisStrategy\", \"register_type_strategy\", \"SearchStrategy\")\n\nT = TypeVar(\"T\")\nHypothesisStrategy: TypeAlias = (\n    \"SearchStrategy | Callable[[type[T]], SearchStrategy[T] | None]\"\n)\nregister_type_strategy: Callable[[type, HypothesisStrategy], None] | None\n\n\ntry:\n    from hypothesis.strategies import register_type_strategy  # type: ignore[assignment]\nexcept ImportError:\n    register_type_strategy = None\n"
  },
  {
    "path": "src/phantom/_utils/__init__.py",
    "content": ""
  },
  {
    "path": "src/phantom/_utils/misc.py",
    "content": "# Workaround for something that looks like this bug\n# https://github.com/pytest-dev/pytest/issues/4386\nfrom __future__ import annotations\n\nfrom collections.abc import MutableMapping\nfrom collections.abc import MutableSequence\nfrom collections.abc import MutableSet\nfrom dataclasses import is_dataclass\nfrom itertools import product\nfrom types import UnionType\nfrom typing import Final\nfrom typing import NewType\nfrom typing import TypeAlias\nfrom typing import TypeGuard\nfrom typing import Union\nfrom typing import get_args\nfrom typing import get_origin\n\n\nclass UnresolvedClassAttribute(NotImplementedError): ...\n\n\ndef resolve_class_attr(\n    cls: type,\n    name: str,\n    argument: object | None,\n    required: bool = True,\n) -> None:\n    argument = getattr(cls, name, None) if argument is None else argument\n    if argument is not None:\n        setattr(cls, name, argument)\n    elif required and not getattr(cls, \"__abstract__\", False):\n        raise UnresolvedClassAttribute(\n            f\"Concrete phantom type {cls.__qualname__} must define class attribute \"\n            f\"{name}.\"\n        )\n\n\nBoundType: TypeAlias = type | tuple[type, ...]\n\n\ndef _is_union(type_: BoundType) -> bool:\n    return get_origin(type_) in (Union, UnionType)\n\n\ndef _is_intersection(type_: BoundType) -> bool:\n    return isinstance(type_, tuple)\n\n\ndef is_subtype(a: BoundType, b: BoundType) -> bool:  # noqa: C901\n    \"\"\"\n    Return True if ``a`` is a subtype of ``b``. Supports single-level typing.Unions\n    and intersections represented as tuples respectively without nesting.\n\n    The cases that this function deals with can be divided into the following cases,\n    where T is neither a union or intersection:\n\n    1. Union, Union: Success if all types in a have a subclass in b.\n    2. T, Union: Success if a is a subclass of one or more types in b.\n    3. Union, T: Always fails (except when a single-type union, see 9).\n    4. T, Intersection: Success if a is a subclass of all types in b.\n    5. Intersection, T: Success if one type in a is a subclass of b.\n    6. Intersection, Union: Success if one type in a is a subclass of one type in b.\n    7. Union, Intersection: Always fails (except when a is a single-type union see 4).\n    8. Intersection, Intersection: Success if all items in b have a subclass in a.\n    9. T, T: Success if a is a subclass of b.\n    \"\"\"\n\n    if _is_union(a) and _is_union(b):\n        for a_part in get_args(a):\n            for b_part in get_args(b):\n                if issubclass(a_part, b_part):\n                    break\n            else:\n                return False\n        return True\n    elif _is_intersection(a) and _is_union(b):\n        assert isinstance(a, tuple)\n        for a_part, b_part in product(a, get_args(b)):\n            if issubclass(a_part, b_part):\n                return True\n        return False\n    elif _is_intersection(a) and _is_intersection(b):\n        assert isinstance(a, tuple)\n        assert isinstance(b, tuple)\n        for b_part in b:\n            for a_part in a:\n                if issubclass(a_part, b_part):\n                    break\n            else:\n                return False\n        return True\n    elif _is_union(a):\n        return False\n    elif _is_union(b):\n        assert isinstance(a, type)\n        return any(issubclass(a, b_part) for b_part in get_args(b))\n    elif _is_intersection(b):\n        assert isinstance(a, type)\n        assert isinstance(b, tuple)\n        return all(issubclass(a, b_part) for b_part in b)\n    elif _is_intersection(a):\n        assert isinstance(a, tuple)\n        return any(issubclass(a_part, b) for a_part in a)\n    assert isinstance(a, type)\n    return issubclass(a, b)\n\n\ndef fully_qualified_name(cls: type) -> str:\n    return f\"{cls.__module__}.{cls.__qualname__}\"\n\n\nmutable: Final = (MutableSequence, MutableSet, MutableMapping)\nNotKnownMutableType = NewType(\"NotKnownMutableType\", type)\n\"\"\"\nInternal type to mark types that are not known to be mutable. The term immutable is\navoided here because there is no way to guarantee that a checked type isn't actually\nmutable, so we don't want to communicate in strong terms here.\n\"\"\"\n\n\ndef is_not_known_mutable_type(type_: BoundType) -> TypeGuard[NotKnownMutableType]:\n    return not (\n        any(is_subtype(type_, mutable_type) for mutable_type in mutable)\n        or (\n            is_dataclass(type_) and not type_.__dataclass_params__.frozen  # type: ignore[attr-defined]\n        )\n    )\n\n\ndef is_not_known_mutable_instance(value: object) -> bool:\n    return not (\n        isinstance(value, mutable)\n        or (\n            is_dataclass(value)\n            # https://github.com/python/typeshed/pull/9947#issue-1641078469\n            and not value.__dataclass_params__.frozen  # type: ignore[union-attr]\n        )\n    )\n\n\ndef is_union(value: object) -> TypeGuard[type]:\n    return get_origin(value) == Union or isinstance(value, UnionType)\n"
  },
  {
    "path": "src/phantom/_utils/types.py",
    "content": "from typing import Protocol\nfrom typing import TypeVar\nfrom typing import runtime_checkable\n\nfrom numerary.protocol import CachingProtocolMeta\n\nT_contra = TypeVar(\"T_contra\", contravariant=True)\nU_co = TypeVar(\"U_co\", covariant=True)\n\n\n@runtime_checkable\nclass _SupportsLt(Protocol[T_contra]):\n    def __lt__(self, other: T_contra) -> bool: ...\n\n\nclass SupportsLt(\n    _SupportsLt[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsLe(Protocol[T_contra]):\n    def __le__(self, other: T_contra) -> bool: ...\n\n\nclass SupportsLe(\n    _SupportsLe[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsGt(Protocol[T_contra]):\n    def __gt__(self, other: T_contra) -> bool: ...\n\n\nclass SupportsGt(\n    _SupportsGt[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsGe(Protocol[T_contra]):\n    def __ge__(self, other: T_contra) -> bool: ...\n\n\nclass SupportsGe(\n    _SupportsGe[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsEq(Protocol):\n    def __eq__(self, other: object) -> bool: ...\n    def __hash__(self) -> int: ...\n\n\nclass SupportsEq(\n    _SupportsEq,\n    Protocol,\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _Comparable(\n    SupportsLt[T_contra],\n    SupportsLe[T_contra],\n    SupportsGt[T_contra],\n    SupportsGe[T_contra],\n    SupportsEq,\n    Protocol[T_contra],\n): ...\n\n\nclass Comparable(\n    _Comparable[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsFloat(Protocol):\n    def __float__(self) -> float: ...\n\n\nclass SupportsFloat(_SupportsFloat, Protocol, metaclass=CachingProtocolMeta): ...\n\n\n@runtime_checkable\nclass _FloatComparable(\n    SupportsFloat,\n    Comparable[T_contra],\n    Protocol[T_contra],\n): ...\n\n\nclass FloatComparable(\n    _FloatComparable[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsLeGe(SupportsLe[T_contra], SupportsGe[T_contra], Protocol[T_contra]): ...\n\n\nclass SupportsLeGe(\n    _SupportsLeGe[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsLeGt(SupportsLe[T_contra], SupportsGt[T_contra], Protocol[T_contra]): ...\n\n\nclass SupportsLeGt(\n    _SupportsLeGt[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsLtGe(SupportsLt[T_contra], SupportsGe[T_contra], Protocol[T_contra]): ...\n\n\nclass SupportsLtGe(\n    _SupportsLtGe[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\nclass _SupportsLtGt(SupportsLt[T_contra], SupportsGt[T_contra], Protocol[T_contra]): ...\n\n\nclass SupportsLtGt(\n    _SupportsLtGt[T_contra],\n    Protocol[T_contra],\n    metaclass=CachingProtocolMeta,\n): ...\n\n\n@runtime_checkable\nclass _SupportsMod(Protocol[T_contra, U_co]):\n    def __mod__(self, other: T_contra) -> U_co: ...\n\n\nclass SupportsMod(\n    _SupportsMod[T_contra, U_co],\n    Protocol[T_contra, U_co],\n    metaclass=CachingProtocolMeta,\n): ...\n"
  },
  {
    "path": "src/phantom/boolean.py",
    "content": "\"\"\"\nTypes describing objects that coerce to either ``True`` or ``False`` respectively when\ncalling ``bool()`` on them.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom . import Phantom\nfrom .predicates import boolean\n\nif TYPE_CHECKING:\n    from hypothesis.strategies import SearchStrategy\n\n\nclass Truthy(Phantom[object], predicate=boolean.truthy, bound=object):\n    \"\"\"\n    >>> isinstance(\"Huzzah!\", Truthy)\n    True\n    >>> isinstance((), Truthy)\n    False\n    \"\"\"\n\n    @classmethod\n    def __register_strategy__(cls) -> SearchStrategy:\n        from hypothesis.strategies import integers\n        from hypothesis.strategies import just\n        from hypothesis.strategies import lists\n        from hypothesis.strategies import text\n\n        return (\n            just(True)\n            | integers(min_value=1)\n            | integers(max_value=-1)\n            | lists(elements=integers(), min_size=1)\n            | text(min_size=1)\n        )\n\n\nclass Falsy(Phantom[object], predicate=boolean.falsy, bound=object):\n    \"\"\"\n    >>> isinstance((), Falsy)\n    True\n    >>> isinstance(\"Hej!\", Falsy)\n    False\n    \"\"\"\n\n    @classmethod\n    def __register_strategy__(cls) -> SearchStrategy:\n        from hypothesis.strategies import just\n\n        return just(False) | just(0) | just(()) | just(\"\") | just(b\"\")\n"
  },
  {
    "path": "src/phantom/bounds.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable\nfrom collections.abc import Iterable\nfrom collections.abc import Sequence\nfrom typing import Any\nfrom typing import Final\nfrom typing import TypeAlias\nfrom typing import TypeVar\nfrom typing import cast\nfrom typing import get_args\n\nfrom ._utils.misc import is_union\nfrom .errors import BoundError\nfrom .predicates.boolean import all_of\nfrom .predicates.generic import of_complex_type\nfrom .predicates.generic import of_type\n\n__all__ = (\"get_bound_parser\", \"parse_str\", \"Parser\")\n\nT = TypeVar(\"T\", covariant=True)\nParser: TypeAlias = Callable[[object], T]\n\n\ndef display_bound(bound: Any) -> str:\n    if isinstance(bound, Iterable):\n        return f\"Intersection[{', '.join(display_bound(part) for part in bound)}]\"\n    if is_union(bound):\n        return (\n            f\"typing.Union[\"\n            f\"{', '.join(display_bound(part) for part in get_args(bound))}\"\n            f\"]\"\n        )\n    return str(getattr(bound, \"__name__\", bound))\n\n\ndef get_bound_parser(bound: type[T] | Any) -> Parser[T]:\n    within_bound = (\n        # Interpret sequence as intersection\n        all_of(of_type(t) for t in bound)\n        if isinstance(bound, Sequence)\n        else of_complex_type(bound)\n    )\n\n    def parser(instance: object) -> T:\n        if not within_bound(instance):\n            raise BoundError(\n                f\"Value is not within bound of {display_bound(bound)!r}: {instance!r}\"\n            )\n        return cast(T, instance)\n\n    return parser\n\n\nparse_str: Final[Callable[[object], str]] = get_bound_parser(str)\n"
  },
  {
    "path": "src/phantom/datetime.py",
    "content": "\"\"\"\nTypes for narrowing on the builtin datetime types.\n\nThese types can be used without installing any extra dependencies, however, to parse\nstrings, python-dateutil must be installed or a\n:py:class:`phantom.errors.MissingDependency` error will be raised when calling parse.\n\nYou can install python-dateutil by using the ``[dateutil]`` or ``[all]`` extras.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\n\nfrom . import Phantom\nfrom . import _hypothesis\nfrom .bounds import parse_str\nfrom .errors import MissingDependency\nfrom .predicates.datetime import is_tz_aware\nfrom .predicates.datetime import is_tz_naive\nfrom .schema import Schema\n\ntry:\n    import dateutil.parser\n\n    parse_datetime_str = dateutil.parser.parse\n    DateutilParseError = dateutil.parser.ParserError\nexcept ImportError as e:\n    exception = e\n\n    def parse_datetime_str(\n        *_: object,\n        **__: object,\n    ) -> datetime.datetime:\n        raise MissingDependency(\n            \"python-dateutil needs to be installed to use this type for parsing. It \"\n            \"can be installed with the phantom-types[dateutil] extra.\"\n        ) from exception\n\n    class DateutilParseError(Exception):  # type: ignore[no-redef]\n        ...\n\n\n__all__ = (\"TZAware\", \"TZNaive\")\n\n\ndef parse_datetime(value: object) -> datetime.datetime:\n    if isinstance(value, datetime.datetime):\n        return value\n    str_value = parse_str(value)\n    try:\n        return parse_datetime_str(str_value)\n    except DateutilParseError as exc:\n        raise TypeError(\"Could not parse datetime from given string\") from exc\n\n\nclass TZAware(datetime.datetime, Phantom, predicate=is_tz_aware):\n    \"\"\"\n    A type for helping ensure that ``datetime`` objects are always timezone aware.\n\n    >>> isinstance(datetime.datetime.now(), TZAware)\n    False\n    >>> isinstance(datetime.datetime.now(tz=datetime.timezone.utc), TZAware)\n    True\n    \"\"\"\n\n    # A property of being aware is (dt.tzinfo != None), so we can safely narrow this\n    # attribute to not include None.\n    tzinfo: datetime.tzinfo\n\n    @classmethod\n    def parse(cls, instance: object) -> TZAware:\n        return super().parse(parse_datetime(instance))\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A date-time with timezone data.\",\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy:\n        from hypothesis.strategies import datetimes\n        from hypothesis.strategies import timezones\n\n        return datetimes(timezones=timezones())\n\n\nclass TZNaive(datetime.datetime, Phantom, predicate=is_tz_naive):\n    \"\"\"\n    >>> isinstance(datetime.datetime.now(), TZNaive)\n    True\n    >>> isinstance(datetime.datetime.now(tz=datetime.timezone.utc), TZNaive)\n    False\n    \"\"\"\n\n    @classmethod\n    def parse(cls, instance: object) -> TZNaive:\n        return super().parse(parse_datetime(instance))\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A date-time without timezone data.\",\n            \"format\": \"date-time-naive\",\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy:\n        from hypothesis.strategies import datetimes\n        from hypothesis.strategies import none\n\n        return datetimes(timezones=none())\n"
  },
  {
    "path": "src/phantom/errors.py",
    "content": "class BoundError(TypeError): ...\n\n\nclass MissingDependency(Exception): ...\n"
  },
  {
    "path": "src/phantom/ext/__init__.py",
    "content": ""
  },
  {
    "path": "src/phantom/ext/phonenumbers.py",
    "content": "\"\"\"\nRequires the phonenumbers_ package which can be installed with:\n\n.. _phonenumbers: https://pypi.org/project/phonenumbers/\n\n.. code-block:: bash\n\n    $ python3 -m pip install phantom-types[phonenumbers]\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Final\nfrom typing import TypeGuard\nfrom typing import cast\n\nimport phonenumbers\n\nfrom phantom import Phantom\nfrom phantom.bounds import parse_str\nfrom phantom.fn import excepts\nfrom phantom.schema import Schema\n\n__all__ = (\n    \"InvalidPhoneNumber\",\n    \"normalize_phone_number\",\n    \"is_phone_number\",\n    \"is_formatted_phone_number\",\n    \"PhoneNumber\",\n    \"FormattedPhoneNumber\",\n)\n\n\nclass InvalidPhoneNumber(phonenumbers.NumberParseException, TypeError):\n    INVALID: Final = 99\n\n    def __init__(self, error_type: int = INVALID, msg: str = \"Invalid number\") -> None:\n        super().__init__(error_type, msg)\n\n\ndef _deconstruct_phone_number(\n    phone_number: str, country_code: str | None = None\n) -> phonenumbers.PhoneNumber:\n    try:\n        parsed_number = phonenumbers.parse(phone_number, region=country_code)\n    except phonenumbers.NumberParseException as e:\n        raise InvalidPhoneNumber(e.error_type, e._msg) from e\n    if not phonenumbers.is_valid_number(parsed_number):\n        raise InvalidPhoneNumber\n    return parsed_number\n\n\ndef normalize_phone_number(\n    phone_number: str,\n    country_code: str | None = None,\n) -> FormattedPhoneNumber:\n    \"\"\"\n    Normalize ``phone_number`` using :py:const:`phonenumbers.PhoneNumberFormat.E164`.\n\n    :raises InvalidPhoneNumber:\n    \"\"\"\n    normalized = phonenumbers.format_number(\n        _deconstruct_phone_number(phone_number, country_code),\n        phonenumbers.PhoneNumberFormat.E164,\n    )\n    return cast(FormattedPhoneNumber, normalized)\n\n\nis_phone_number = excepts(InvalidPhoneNumber)(_deconstruct_phone_number)\n\n\ndef is_formatted_phone_number(number: str) -> TypeGuard[FormattedPhoneNumber]:\n    try:\n        return number == normalize_phone_number(number)\n    except InvalidPhoneNumber:\n        return False\n\n\nclass PhoneNumber(str, Phantom, predicate=is_phone_number):\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A valid E.164 phone number.\",\n            \"type\": \"string\",\n            \"format\": \"E.164\",\n        }\n\n\nclass FormattedPhoneNumber(PhoneNumber, predicate=is_formatted_phone_number):\n    @classmethod\n    def parse(cls, instance: object) -> FormattedPhoneNumber:\n        \"\"\"\n        Normalize number using :py:const:`phonenumbers.PhoneNumberFormat.E164`.\n\n        :raises InvalidPhoneNumber:\n        \"\"\"\n        return normalize_phone_number(parse_str(instance))\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"title\": \"PhoneNumber\",\n        }\n"
  },
  {
    "path": "src/phantom/fn.py",
    "content": "from __future__ import annotations\n\nimport functools\nfrom collections.abc import Callable\nfrom functools import partial\nfrom typing import Any\nfrom typing import TypeVar\n\n\ndef _name(fn: Callable) -> str:\n    if isinstance(fn, partial):\n        fn = fn.func\n    try:\n        return fn.__qualname__\n    except AttributeError:\n        return str(fn)\n\n\nAA = TypeVar(\"AA\")\nAR = TypeVar(\"AR\")\nBA = TypeVar(\"BA\")\n\n\ndef compose2(a: Callable[[AA], AR], b: Callable[[BA], AA]) -> Callable[[BA], AR]:\n    \"\"\"\n    Returns a function composed from the two given functions ``a`` and ``b`` such that\n    calling ``compose2(a, b)(x)`` is equivalent to calling ``a(b(x))``.\n\n    >>> compose2(\"\".join, reversed)(\"!olleH\")\n    'Hello!'\n    \"\"\"\n    a_name = _name(a)\n    b_name = _name(b)\n\n    def c(arg: BA) -> AR:\n        return a(b(arg))\n\n    c.__name__ = f\"{a_name}∘{b_name}\"\n    c.__doc__ = f\"Function composed as {a_name}({b_name}(_)).\"\n    return c\n\n\nA = TypeVar(\"A\")\n\n\ndef excepts(\n    exception: tuple[type[Exception], ...] | type[Exception],\n    negate: bool = False,\n) -> Callable[[Callable[[A], Any]], Callable[[A], bool]]:\n    \"\"\"\n    Turn a unary function that raises an exception into a boolean predicate.\n\n    >>> def validate_positive(number: int) -> None:\n    ...     if number < 0: raise ValueError\n    >>> is_positive = excepts(ValueError)(validate_positive)\n    >>> is_positive(0), is_positive(-1)\n    (True, False)\n    \"\"\"\n\n    def decorator(fn: Callable[[A], Any]) -> Callable[[A], bool]:\n        @functools.wraps(fn)\n        def wrapper(arg: A) -> bool:\n            try:\n                fn(arg)\n            except exception:\n                return negate\n            return not negate\n\n        return wrapper\n\n    return decorator\n"
  },
  {
    "path": "src/phantom/interval.py",
    "content": "\"\"\"\nTypes for describing narrower sets of numbers than builtin numeric types like ``int``\nand ``float``. Use the provided base classes to build custom intervals. For example, to\nrepresent number in the closed range ``[0, 100]`` for a volume control you would define\na type like this:\n\n.. code-block:: python\n\n    class VolumeLevel(int, Inclusive, low=0, high=100): ...\n\nThere is also a set of concrete ready-to-use interval types provided, that use predicate\nfunctions from :py:mod:`phantom.predicates.interval`.\n\n.. code-block:: python\n\n    def take_portion(portion: Portion, whole: Natural) -> float:\n        return portion * whole\n\nAll interval types fully support pydantic and appropriately adds inclusive or exclusive\nminimums and maximums to their schema representations.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom contextlib import suppress\nfrom typing import Any\nfrom typing import Final\nfrom typing import Protocol\nfrom typing import TypeVar\n\nfrom . import Phantom\nfrom . import Predicate\nfrom . import _hypothesis\nfrom ._utils.misc import resolve_class_attr\nfrom ._utils.types import Comparable\nfrom ._utils.types import FloatComparable\nfrom ._utils.types import SupportsEq\nfrom .predicates import interval\nfrom .schema import Schema\n\nN = TypeVar(\"N\", bound=Comparable)\nDerived = TypeVar(\"Derived\", bound=\"Interval\")\n\n\nclass IntervalCheck(Protocol):\n    def __call__(self, a: N, b: N) -> Predicate[N]: ...\n\n\ninf: Final = float(\"inf\")\nneg_inf: Final = float(\"-inf\")\n\n\nclass _NonScalarBounds(Exception): ...\n\n\ndef _get_scalar_int_bounds(\n    type_: type[Interval],\n    exclude_min: bool = False,\n    exclude_max: bool = False,\n) -> tuple[int | None, int | None]:\n    low = type_.__low__ if type_.__low__ != neg_inf else None\n    high = type_.__high__ if type_.__high__ != inf else None\n\n    if low is not None:\n        try:\n            scalar_low = int(low)  # type: ignore[call-overload]\n        except TypeError as exception:\n            raise _NonScalarBounds from exception\n\n        if exclude_min:\n            scalar_low += 1\n    else:\n        scalar_low = None\n\n    if high is not None:\n        try:\n            scalar_high = int(high)  # type: ignore[call-overload]\n        except TypeError as exception:\n            raise _NonScalarBounds from exception\n\n        if exclude_max:\n            scalar_high -= 1\n    else:\n        scalar_high = None\n\n    return scalar_low, scalar_high\n\n\ndef _get_scalar_float_bounds(\n    type_: type[Interval],\n) -> tuple[float | None, float | None]:\n    low = type_.__low__ if type_.__low__ != neg_inf else None\n    high = type_.__high__ if type_.__high__ != inf else None\n\n    if low is not None:\n        try:\n            low = float(low)\n        except TypeError as excpetion:\n            raise _NonScalarBounds from excpetion\n\n    if high is not None:\n        try:\n            high = float(high)\n        except TypeError as exception:\n            raise _NonScalarBounds from exception\n\n    return low, high\n\n\ndef _resolve_bound(\n    cls: type,\n    name: str,\n    argument: Comparable | None,\n    default: Comparable,\n) -> None:\n    inherited = getattr(cls, name, None)\n\n    if argument is not None:\n        resolved = argument\n    elif inherited is not None:\n        resolved = inherited\n    else:\n        resolved = default\n\n    setattr(cls, name, resolved)\n\n\nclass Interval(Phantom[Comparable], bound=Comparable, abstract=True):\n    \"\"\"\n    Base class for all interval types, providing the following class arguments:\n\n    * ``check: IntervalCheck``\n    * ``low: Comparable`` (defaults to negative infinity)\n    * ``high: Comparable`` (defaults to positive infinity)\n\n    Concrete subclasses must specify their runtime type bound as their first base.\n    \"\"\"\n\n    __check__: IntervalCheck\n    __low__: FloatComparable\n    __high__: FloatComparable\n\n    def __init_subclass__(\n        cls,\n        check: IntervalCheck | None = None,\n        low: FloatComparable | None = None,\n        high: FloatComparable | None = None,\n        **kwargs: Any,\n    ) -> None:\n        _resolve_bound(cls, \"__low__\", low, neg_inf)\n        _resolve_bound(cls, \"__high__\", high, inf)\n        resolve_class_attr(cls, \"__check__\", check)\n        if getattr(cls, \"__check__\", None) is None:\n            raise TypeError(f\"{cls.__qualname__} must define an interval check\")\n        super().__init_subclass__(\n            predicate=cls.__check__(cls.__low__, cls.__high__),\n            **kwargs,\n        )\n\n    @classmethod\n    def parse(cls: type[Derived], instance: object) -> Derived:\n        return super().parse(\n            cls.__bound__(instance) if isinstance(instance, str) else instance\n        )\n\n\ndef _format_limit(value: SupportsEq) -> str:\n    if value == inf:\n        return \"∞\"\n    if value == neg_inf:\n        return \"-∞\"\n    return str(value)\n\n\nclass Exclusive(Interval, check=interval.exclusive, abstract=True):\n    \"\"\"Uses :py:func:`phantom.predicates.interval.exclusive` as ``check``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": (\n                f\"A value in the exclusive range ({_format_limit(cls.__low__)}, \"\n                f\"{_format_limit(cls.__high__)}).\"\n            ),\n            \"exclusiveMinimum\": float(cls.__low__) if cls.__low__ != neg_inf else None,\n            \"exclusiveMaximum\": float(cls.__high__) if cls.__high__ != inf else None,\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import floats\n        from hypothesis.strategies import integers\n\n        with suppress(_NonScalarBounds):  # pragma: no cover\n            if issubclass(cls.__bound__, int):\n                return integers(\n                    *_get_scalar_int_bounds(cls, exclude_min=True, exclude_max=True)\n                )\n            if issubclass(cls.__bound__, float):\n                return floats(\n                    *_get_scalar_float_bounds(cls), exclude_min=True, exclude_max=True\n                )\n        return None\n\n\nclass Inclusive(Interval, check=interval.inclusive, abstract=True):\n    \"\"\"Uses :py:func:`phantom.predicates.interval.inclusive` as ``check``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": (\n                f\"A value in the inclusive range [{_format_limit(cls.__low__)}, \"\n                f\"{_format_limit(cls.__high__)}].\"\n            ),\n            \"minimum\": float(cls.__low__) if cls.__low__ != neg_inf else None,\n            \"maximum\": float(cls.__high__) if cls.__high__ != inf else None,\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import floats\n        from hypothesis.strategies import integers\n\n        with suppress(_NonScalarBounds):  # pragma: no cover\n            if issubclass(cls.__bound__, int):\n                return integers(*_get_scalar_int_bounds(cls))\n            if issubclass(cls.__bound__, float):\n                return floats(*_get_scalar_float_bounds(cls))\n        return None\n\n\nclass ExclusiveInclusive(Interval, check=interval.exclusive_inclusive, abstract=True):\n    \"\"\"Uses :py:func:`phantom.predicates.interval.exclusive_inclusive` as ``check``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": (\n                f\"A value in the half-open range ({_format_limit(cls.__low__)}, \"\n                f\"{_format_limit(cls.__high__)}].\"\n            ),\n            \"exclusiveMinimum\": float(cls.__low__) if cls.__low__ != neg_inf else None,\n            \"maximum\": float(cls.__high__) if cls.__high__ != inf else None,\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import floats\n        from hypothesis.strategies import integers\n\n        with suppress(_NonScalarBounds):  # pragma: no cover\n            if issubclass(cls.__bound__, int):\n                return integers(*_get_scalar_int_bounds(cls, exclude_min=True))\n            if issubclass(cls.__bound__, float):\n                return floats(*_get_scalar_float_bounds(cls), exclude_min=True)\n        return None\n\n\nclass InclusiveExclusive(Interval, check=interval.inclusive_exclusive, abstract=True):\n    \"\"\"Uses :py:func:`phantom.predicates.interval.inclusive_exclusive` as ``check``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": (\n                f\"A value in the half-open range [{_format_limit(cls.__low__)}, \"\n                f\"{_format_limit(cls.__high__)}).\"\n            ),\n            \"minimum\": float(cls.__low__) if cls.__low__ != neg_inf else None,\n            \"exclusiveMaximum\": float(cls.__high__) if cls.__high__ != inf else None,\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import floats\n        from hypothesis.strategies import integers\n\n        with suppress(_NonScalarBounds):  # pragma: no cover\n            if issubclass(cls.__bound__, int):\n                return integers(*_get_scalar_int_bounds(cls, exclude_max=True))\n            if issubclass(cls.__bound__, float):\n                return floats(*_get_scalar_float_bounds(cls), exclude_max=True)\n        return None\n\n\nclass Natural(int, InclusiveExclusive, low=0):\n    \"\"\"Represents integer values in the inclusive range ``[0, ∞)``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"An integer value in the inclusive range [0, ∞).\",\n        }\n\n\nclass NegativeInt(int, ExclusiveInclusive, high=0):\n    \"\"\"Represents integer values in the inclusive range ``(-∞, 0]``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"An integer value in the inclusive range (-∞, 0].\",\n        }\n\n\nclass Portion(float, Inclusive, low=0, high=1):\n    \"\"\"Represents float values in the inclusive range ``[0, 1]``.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A float value in the inclusive range [0, 1].\",\n        }\n"
  },
  {
    "path": "src/phantom/iso3166.py",
    "content": "\"\"\"\nExposes a :py:class:`CountryCode` type that is a union of a :py:class:`Literal`\ncontaining all ISO3166 alpha-2 codes, and a phantom type that parses alpha-2 codes at\nruntime. This allows mixing statically known values with runtime-parsed values, like\nso:\n\n.. code-block:: python\n\n    countries: tuple[CountryCode] = (\"SE\", \"DK\", ParsedAlpha2.parse(\"FR\"))\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Final\nfrom typing import Literal\nfrom typing import TypeAlias\nfrom typing import cast\nfrom typing import get_args\n\nfrom phantom import Phantom\nfrom phantom import _hypothesis\nfrom phantom.bounds import parse_str\nfrom phantom.predicates.collection import contained\nfrom phantom.schema import Schema\n\n__all__ = (\n    \"LiteralAlpha2\",\n    \"ParsedAlpha2\",\n    \"Alpha2\",\n    \"CountryCode\",\n    \"is_alpha2_country_code\",\n    \"normalize_alpha2_country_code\",\n    \"InvalidCountryCode\",\n)\n\nLiteralAlpha2: TypeAlias = Literal[\n    \"AF\",\n    \"AX\",\n    \"AL\",\n    \"DZ\",\n    \"AS\",\n    \"AD\",\n    \"AO\",\n    \"AI\",\n    \"AQ\",\n    \"AG\",\n    \"AR\",\n    \"AM\",\n    \"AW\",\n    \"AU\",\n    \"AT\",\n    \"AZ\",\n    \"BS\",\n    \"BH\",\n    \"BD\",\n    \"BB\",\n    \"BY\",\n    \"BE\",\n    \"BZ\",\n    \"BJ\",\n    \"BM\",\n    \"BT\",\n    \"BO\",\n    \"BQ\",\n    \"BA\",\n    \"BW\",\n    \"BV\",\n    \"BR\",\n    \"IO\",\n    \"BN\",\n    \"BG\",\n    \"BF\",\n    \"BI\",\n    \"KH\",\n    \"CM\",\n    \"CA\",\n    \"CV\",\n    \"KY\",\n    \"CF\",\n    \"TD\",\n    \"CL\",\n    \"CN\",\n    \"CX\",\n    \"CC\",\n    \"CO\",\n    \"KM\",\n    \"CG\",\n    \"CD\",\n    \"CK\",\n    \"CR\",\n    \"CI\",\n    \"HR\",\n    \"CU\",\n    \"CW\",\n    \"CY\",\n    \"CZ\",\n    \"DK\",\n    \"DJ\",\n    \"DM\",\n    \"DO\",\n    \"EC\",\n    \"EG\",\n    \"SV\",\n    \"GQ\",\n    \"ER\",\n    \"EE\",\n    \"ET\",\n    \"FK\",\n    \"FO\",\n    \"FJ\",\n    \"FI\",\n    \"FR\",\n    \"GF\",\n    \"PF\",\n    \"TF\",\n    \"GA\",\n    \"GM\",\n    \"GE\",\n    \"DE\",\n    \"GH\",\n    \"GI\",\n    \"GR\",\n    \"GL\",\n    \"GD\",\n    \"GP\",\n    \"GU\",\n    \"GT\",\n    \"GG\",\n    \"GN\",\n    \"GW\",\n    \"GY\",\n    \"HT\",\n    \"HM\",\n    \"VA\",\n    \"HN\",\n    \"HK\",\n    \"HU\",\n    \"IS\",\n    \"IN\",\n    \"ID\",\n    \"IR\",\n    \"IQ\",\n    \"IE\",\n    \"IM\",\n    \"IL\",\n    \"IT\",\n    \"JM\",\n    \"JP\",\n    \"JE\",\n    \"JO\",\n    \"KZ\",\n    \"KE\",\n    \"KI\",\n    \"KP\",\n    \"KR\",\n    \"XK\",\n    \"KW\",\n    \"KG\",\n    \"LA\",\n    \"LV\",\n    \"LB\",\n    \"LS\",\n    \"LR\",\n    \"LY\",\n    \"LI\",\n    \"LT\",\n    \"LU\",\n    \"MO\",\n    \"MK\",\n    \"MG\",\n    \"MW\",\n    \"MY\",\n    \"MV\",\n    \"ML\",\n    \"MT\",\n    \"MH\",\n    \"MQ\",\n    \"MR\",\n    \"MU\",\n    \"YT\",\n    \"MX\",\n    \"FM\",\n    \"MD\",\n    \"MC\",\n    \"MN\",\n    \"ME\",\n    \"MS\",\n    \"MA\",\n    \"MZ\",\n    \"MM\",\n    \"NA\",\n    \"NR\",\n    \"NP\",\n    \"NL\",\n    \"NC\",\n    \"NZ\",\n    \"NI\",\n    \"NE\",\n    \"NG\",\n    \"NU\",\n    \"NF\",\n    \"MP\",\n    \"NO\",\n    \"OM\",\n    \"PK\",\n    \"PW\",\n    \"PS\",\n    \"PA\",\n    \"PG\",\n    \"PY\",\n    \"PE\",\n    \"PH\",\n    \"PN\",\n    \"PL\",\n    \"PT\",\n    \"PR\",\n    \"QA\",\n    \"RE\",\n    \"RO\",\n    \"RU\",\n    \"RW\",\n    \"BL\",\n    \"SH\",\n    \"KN\",\n    \"LC\",\n    \"MF\",\n    \"PM\",\n    \"VC\",\n    \"WS\",\n    \"SM\",\n    \"ST\",\n    \"SA\",\n    \"SN\",\n    \"RS\",\n    \"SC\",\n    \"SL\",\n    \"SG\",\n    \"SX\",\n    \"SK\",\n    \"SI\",\n    \"SB\",\n    \"SO\",\n    \"ZA\",\n    \"GS\",\n    \"SS\",\n    \"ES\",\n    \"LK\",\n    \"SD\",\n    \"SR\",\n    \"SJ\",\n    \"SZ\",\n    \"SE\",\n    \"CH\",\n    \"SY\",\n    \"TW\",\n    \"TJ\",\n    \"TZ\",\n    \"TH\",\n    \"TL\",\n    \"TG\",\n    \"TK\",\n    \"TO\",\n    \"TT\",\n    \"TN\",\n    \"TR\",\n    \"TM\",\n    \"TC\",\n    \"TV\",\n    \"UG\",\n    \"UA\",\n    \"AE\",\n    \"GB\",\n    \"US\",\n    \"UM\",\n    \"UY\",\n    \"UZ\",\n    \"VU\",\n    \"VE\",\n    \"VN\",\n    \"VG\",\n    \"VI\",\n    \"WF\",\n    \"EH\",\n    \"YE\",\n    \"ZM\",\n    \"ZW\",\n]\n\"\"\"Literal of all ISO3166 alpha-2 codes. \"\"\"\n\nALPHA2: Final = frozenset(get_args(LiteralAlpha2))\nis_alpha2_country_code = contained(ALPHA2)\n\n\nclass InvalidCountryCode(TypeError): ...\n\n\ndef normalize_alpha2_country_code(country_code: str) -> ParsedAlpha2:\n    \"\"\"\n    Normalize mixed case country code.\n\n    :raises InvalidCountryCode:\n    \"\"\"\n    normalized = country_code.upper()\n    if not is_alpha2_country_code(normalized):\n        raise InvalidCountryCode\n    return cast(ParsedAlpha2, normalized)\n\n\nclass ParsedAlpha2(str, Phantom, predicate=is_alpha2_country_code):\n    @classmethod\n    def parse(cls, instance: object) -> ParsedAlpha2:\n        \"\"\"\n        Normalize mixed case country code.\n\n        :raises InvalidCountryCode:\n        \"\"\"\n        return normalize_alpha2_country_code(parse_str(instance))\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"ISO3166-1 alpha-2 country code\",\n            \"examples\": [\"NR\", \"KZ\", \"ET\", \"VC\", \"AE\", \"NZ\", \"SX\", \"XK\", \"AX\"],\n            \"format\": \"iso3166-1 alpha-2\",\n            \"title\": \"Alpha2\",\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import sampled_from\n\n        return sampled_from(sorted(ALPHA2))\n\n\nAlpha2: TypeAlias = LiteralAlpha2 | ParsedAlpha2\nCountryCode: TypeAlias = Alpha2\n"
  },
  {
    "path": "src/phantom/negated.py",
    "content": "\"\"\"\nThis module provides a single type: :py:class:`SequenceNotStr`. This type is equivalent\nto :py:class:`typing.Sequence` except it excludes values of type :py:class:`str` and\n:py:class:`bytes` from the set of valid instances. This can be useful when you want to\neliminate the easy mistake of forgetting to wrap a string value in a containing\nsequence.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom typing import Generic\nfrom typing import TypeVar\nfrom typing import get_args\n\nfrom . import Phantom\nfrom . import _hypothesis\nfrom .predicates import boolean\nfrom .predicates.generic import of_type\n\n__all__ = (\"SequenceNotStr\",)\n\nT = TypeVar(\"T\")\n\n\nclass SequenceNotStr(\n    Sequence[T],\n    Phantom,\n    Generic[T],\n    # Note: We don't eliminate mutable types here like in PhantomSized. This is because\n    # the property of not being a str cannot change by mutation, so this specific\n    # phantom type is safe to use with mutable types.\n    predicate=boolean.negate(of_type((str, bytes))),\n):\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.HypothesisStrategy:\n        from hypothesis.strategies import from_type\n        from hypothesis.strategies import tuples\n\n        def create_strategy(\n            type_: type[T],\n        ) -> _hypothesis.SearchStrategy[tuple[T, ...]] | None:\n            (inner_type,) = get_args(type_)\n            return tuples(from_type(inner_type))\n\n        return create_strategy\n"
  },
  {
    "path": "src/phantom/predicates/__init__.py",
    "content": "from ._base import Predicate\n\n__all__ = (\"Predicate\",)\n"
  },
  {
    "path": "src/phantom/predicates/_base.py",
    "content": "from collections.abc import Callable\nfrom typing import TypeAlias\nfrom typing import TypeVar\n\nT_contra = TypeVar(\"T_contra\", bound=object, contravariant=True)\n\nPredicate: TypeAlias = Callable[[T_contra], bool]\n"
  },
  {
    "path": "src/phantom/predicates/_utils.py",
    "content": "from collections.abc import Callable\nfrom functools import partial\nfrom typing import TypeVar\n\n\ndef _explode_partial(obj: partial) -> str:\n    positional_args = \", \".join(map(repr, obj.args))\n    keyword_args = \", \".join(\n        f\"{name}={value!r}\" for name, value in obj.keywords.items()\n    )\n    args = \", \".join((positional_args, keyword_args))\n    return f\"{obj.func.__qualname__}({args})\"\n\n\ndef _name_or_repr(obj: object) -> str:\n    if isinstance(obj, partial):\n        return _explode_partial(obj)\n    try:\n        return str(obj.__qualname__)  # type: ignore[attr-defined]\n    except AttributeError:\n        return repr(obj)\n\n\nB = TypeVar(\"B\", bound=Callable)\n\n\ndef bind_name(wrapped: Callable, *values: object) -> Callable[[B], B]:\n    name = (\n        f\"{wrapped.__qualname__}({', '.join(_name_or_repr(value) for value in values)})\"\n    )\n\n    def decorator(inner: B) -> B:\n        inner.__qualname__ = inner.__name__ = name\n        return inner\n\n    return decorator\n"
  },
  {
    "path": "src/phantom/predicates/boolean.py",
    "content": "from collections.abc import Iterable\nfrom typing import Literal\nfrom typing import TypeVar\n\nfrom . import Predicate\nfrom ._utils import bind_name\n\nT_contra = TypeVar(\"T_contra\", bound=object, contravariant=True)\n\n\ndef true(_value: object) -> Literal[True]:\n    \"\"\"Always return :py:const:`True`.\"\"\"\n    return True\n\n\ndef false(_value: object) -> Literal[False]:\n    \"\"\"Always return :py:const:`False`.\"\"\"\n    return False\n\n\ndef negate(predicate: Predicate[T_contra]) -> Predicate[T_contra]:\n    \"\"\"Negate a given predicate.\"\"\"\n\n    @bind_name(negate, predicate)\n    def check(value: T_contra) -> bool:\n        return not predicate(value)\n\n    return check\n\n\ndef truthy(value: object) -> bool:\n    \"\"\"Return :py:const:`True` for truthy objects.\"\"\"\n    return bool(value)\n\n\ndef falsy(value: object) -> bool:\n    \"\"\"Return :py:const:`True` for falsy objects.\"\"\"\n    return negate(truthy)(value)\n\n\ndef both(p: Predicate[T_contra], q: Predicate[T_contra]) -> Predicate[T_contra]:\n    \"\"\"\n    Create a new predicate that succeeds when both of the given predicates succeed.\n    \"\"\"\n\n    @bind_name(both, p, q)\n    def check(value: T_contra) -> bool:\n        return p(value) and q(value)\n\n    return check\n\n\ndef either(p: Predicate[T_contra], q: Predicate[T_contra]) -> Predicate[T_contra]:\n    \"\"\"\n    Create a new predicate that succeeds when at least one of the given predicates\n    succeed.\n    \"\"\"\n\n    @bind_name(either, p, q)\n    def check(value: T_contra) -> bool:\n        return p(value) or q(value)\n\n    return check\n\n\ndef xor(p: Predicate[T_contra], q: Predicate[T_contra]) -> Predicate[T_contra]:\n    \"\"\"\n    Create a new predicate that succeeds when one of the given predicates succeed, but\n    not both.\n    \"\"\"\n\n    @bind_name(xor, p, q)\n    def check(value: T_contra) -> bool:\n        return p(value) ^ q(value)\n\n    return check\n\n\ndef all_of(predicates: Iterable[Predicate[T_contra]]) -> Predicate[T_contra]:\n    \"\"\"Create a new predicate that succeeds when all of the given predicates succeed.\"\"\"\n    predicates = tuple(predicates)\n\n    @bind_name(all_of, *predicates)\n    def check(value: T_contra) -> bool:\n        return all(p(value) for p in predicates)\n\n    return check\n\n\ndef any_of(predicates: Iterable[Predicate[T_contra]]) -> Predicate[T_contra]:\n    \"\"\"\n    Create a new predicate that succeeds when at least one of the given predicates\n    succeed.\n    \"\"\"\n    predicates = tuple(predicates)\n\n    @bind_name(any_of, *predicates)\n    def check(value: T_contra) -> bool:\n        return any(p(value) for p in predicates)\n\n    return check\n\n\ndef one_of(predicates: Iterable[Predicate[T_contra]]) -> Predicate[T_contra]:\n    \"\"\"\n    Create a new predicate that succeeds when exactly one of the given predicates\n    succeed.\n    \"\"\"\n    predicates = tuple(predicates)\n\n    @bind_name(one_of, *predicates)\n    def check(value: T_contra) -> bool:\n        return sum(p(value) for p in predicates) == 1\n\n    return check\n"
  },
  {
    "path": "src/phantom/predicates/collection.py",
    "content": "from collections.abc import Container\nfrom collections.abc import Iterable\nfrom collections.abc import Sized\nfrom typing import TypeVar\n\nfrom . import Predicate\nfrom ._utils import bind_name\n\n\ndef contains(value: object) -> Predicate[Container]:\n    \"\"\"Create a new predicate that succeeds when its argument contains ``value``.\"\"\"\n\n    @bind_name(contains, value)\n    def compare(container: Container) -> bool:\n        return value in container\n\n    return compare\n\n\ndef contained(container: Container) -> Predicate[object]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is contained by\n    ``container``.\n    \"\"\"\n\n    @bind_name(contained, container)\n    def compare(value: object) -> bool:\n        return value in container\n\n    return compare\n\n\ndef count(predicate: Predicate[int]) -> Predicate[Sized]:\n    \"\"\"\n    Create a predicate that succeeds when the size of its argument satisfies the given\n    ``predicate``.\n    \"\"\"\n\n    @bind_name(count, predicate)\n    def compare(sized: Sized) -> bool:\n        return predicate(len(sized))\n\n    return compare\n\n\n_O = TypeVar(\"_O\", bound=object)\n\n\ndef exists(predicate: Predicate[_O]) -> Predicate[Iterable]:\n    \"\"\"\n    Create a predicate that succeeds when one or more items in its argument satisfies\n    ``predicate``.\n    \"\"\"\n\n    @bind_name(exists, predicate)\n    def compare(iterable: Iterable) -> bool:\n        return any(predicate(item) for item in iterable)\n\n    return compare\n\n\ndef every(predicate: Predicate[_O]) -> Predicate[Iterable]:\n    \"\"\"\n    Create a predicate that succeeds when all items in its argument satisfy\n    ``predicate``.\n    \"\"\"\n\n    @bind_name(every, predicate)\n    def compare(iterable: Iterable) -> bool:\n        return all(predicate(item) for item in iterable)\n\n    return compare\n"
  },
  {
    "path": "src/phantom/predicates/datetime.py",
    "content": "import datetime\n\nfrom .boolean import negate\n\n\ndef is_tz_aware(dt: datetime.datetime) -> bool:\n    \"\"\"Return :py:const:`True` if ``dt`` is timezone aware.\"\"\"\n    # https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive\n    return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None\n\n\ndef is_tz_naive(dt: datetime.datetime) -> bool:\n    \"\"\"Return :py:const:`True` if ``dt`` is timezone naive.\"\"\"\n    return negate(is_tz_aware)(dt)\n"
  },
  {
    "path": "src/phantom/predicates/generic.py",
    "content": "import typeguard\nfrom typeguard import CollectionCheckStrategy\nfrom typeguard import ForwardRefPolicy\n\nfrom . import Predicate\nfrom ._utils import bind_name\n\n\ndef equal(a: object) -> Predicate[object]:\n    \"\"\"Create a new predicate that succeeds when its argument is equal to ``a``.\"\"\"\n\n    @bind_name(equal, a)\n    def check(b: object) -> bool:\n        return a == b\n\n    return check\n\n\ndef identical(a: object) -> Predicate[object]:\n    \"\"\"Create a new predicate that succeeds when its argument is identical to ``a``.\"\"\"\n\n    @bind_name(identical, a)\n    def check(b: object) -> bool:\n        return a is b\n\n    return check\n\n\ndef of_type(t: type | tuple[type, ...]) -> Predicate[object]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is an instance of ``t``.\n    \"\"\"\n\n    @bind_name(of_type, t)\n    def check(a: object) -> bool:\n        return isinstance(a, t)\n\n    return check\n\n\ndef of_complex_type(t: type) -> Predicate[object]:\n    @bind_name(of_complex_type, t)\n    def check(a: object) -> bool:\n        try:\n            typeguard.check_type(\n                value=a,\n                expected_type=t,\n                typecheck_fail_callback=None,\n                forward_ref_policy=ForwardRefPolicy.ERROR,\n                collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS,\n            )\n        except typeguard.TypeCheckError:\n            return False\n        return True\n\n    return check\n"
  },
  {
    "path": "src/phantom/predicates/interval.py",
    "content": "\"\"\"\nFunctions that create new predicates that succeed when their argument is strictly or non\nstrictly between the upper and lower bounds. There are corresponding phantom types that\nuse these predicates in :py:mod:`phantom.interval`.\n\"\"\"\n\nfrom typing import TypeVar\n\nfrom phantom._utils.types import SupportsLeGe\nfrom phantom._utils.types import SupportsLeGt\nfrom phantom._utils.types import SupportsLtGe\nfrom phantom._utils.types import SupportsLtGt\n\nfrom ._base import Predicate\nfrom ._utils import bind_name\n\nT = TypeVar(\"T\")\n\n\ndef exclusive(low: T, high: T) -> Predicate[SupportsLtGt[T]]:\n    \"\"\"\n    Create a predicate that succeeds when its argument is in the range ``(low, high)``.\n    \"\"\"\n\n    @bind_name(exclusive, low, high)\n    def check(value: SupportsLtGt[T]) -> bool:\n        return low < value < high\n\n    return check\n\n\ndef exclusive_inclusive(low: T, high: T) -> Predicate[SupportsLeGt[T]]:\n    \"\"\"\n    Create a predicate that succeeds when its argument is in the range ``(low, high]``.\n    \"\"\"\n\n    @bind_name(exclusive_inclusive, low, high)\n    def check(value: SupportsLeGt[T]) -> bool:\n        return low < value <= high\n\n    return check\n\n\ndef inclusive_exclusive(low: T, high: T) -> Predicate[SupportsLtGe[T]]:\n    \"\"\"\n    Create a predicate that succeeds when its argument is in the range ``[low, high)``.\n    \"\"\"\n\n    @bind_name(inclusive_exclusive, low, high)\n    def check(value: SupportsLtGe[T]) -> bool:\n        return low <= value < high\n\n    return check\n\n\ndef inclusive(low: T, high: T) -> Predicate[SupportsLeGe[T]]:\n    \"\"\"\n    Create a predicate that succeeds when its argument is in the range ``[low, high]``.\n    \"\"\"\n\n    @bind_name(inclusive, low, high)\n    def check(value: SupportsLeGe[T]) -> bool:\n        return low <= value <= high\n\n    return check\n"
  },
  {
    "path": "src/phantom/predicates/numeric.py",
    "content": "from typing import TypeVar\n\nfrom phantom._utils.types import SupportsGe\nfrom phantom._utils.types import SupportsGt\nfrom phantom._utils.types import SupportsLe\nfrom phantom._utils.types import SupportsLt\nfrom phantom._utils.types import SupportsMod\n\nfrom ._base import Predicate\nfrom ._utils import bind_name\nfrom .boolean import negate\nfrom .generic import equal\n\nT = TypeVar(\"T\")\nU = TypeVar(\"U\")\n\n\ndef less(n: T) -> Predicate[SupportsLt[T]]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is strictly less than ``n``.\n    \"\"\"\n\n    @bind_name(less, n)\n    def check(value: SupportsLt[T]) -> bool:\n        return value < n\n\n    return check\n\n\ndef le(n: T) -> Predicate[SupportsLe[T]]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is less than or equal to\n    ``n``.\n    \"\"\"\n\n    @bind_name(le, n)\n    def check(value: SupportsLe[T]) -> bool:\n        return value <= n\n\n    return check\n\n\ndef greater(n: T) -> Predicate[SupportsGt[T]]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is strictly greater than\n    ``n``.\n    \"\"\"\n\n    @bind_name(greater, n)\n    def check(value: SupportsGt[T]) -> bool:\n        return value > n\n\n    return check\n\n\ndef ge(n: T) -> Predicate[SupportsGe[T]]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument is greater than or equal to\n    ``n``.\n    \"\"\"\n\n    @bind_name(ge, n)\n    def check(value: SupportsGe[T]) -> bool:\n        return value >= n\n\n    return check\n\n\ndef positive(n: SupportsGt[int]) -> bool:\n    \"\"\"Return :py:const:`True` when ``n`` is strictly greater than zero.\"\"\"\n    return greater(0)(n)\n\n\ndef non_positive(n: SupportsLe[int]) -> bool:\n    \"\"\"Return :py:const:`True` when ``n``  is less than or equal to zero.\"\"\"\n    return le(0)(n)\n\n\ndef negative(n: SupportsLt[int]) -> bool:\n    \"\"\"Return :py:const:`True` when ``n`` is strictly less than zero.\"\"\"\n    return less(0)(n)\n\n\ndef non_negative(n: SupportsGe[int]) -> bool:\n    \"\"\"Return :py:const:`True` when ``n`` is greater than or equal to zero.\"\"\"\n    return ge(0)(n)\n\n\ndef modulo(n: T, p: Predicate[U]) -> Predicate[SupportsMod[T, U]]:\n    \"\"\"\n    Create a new predicate that succeeds when its argument modulo ``n`` satisfies the\n    given predicate ``p``.\n    \"\"\"\n\n    @bind_name(modulo, n, p)\n    def check(value: SupportsMod[T, U]) -> bool:\n        return p(value % n)\n\n    return check\n\n\ndef even(n: int) -> bool:\n    \"\"\"Return :py:const:`True`  when ``n`` is even.\"\"\"\n    return modulo(2, equal(0))(n)\n\n\ndef odd(n: int) -> bool:\n    \"\"\"Return :py:const:`True`  when ``n`` is odd.\"\"\"\n    return negate(even)(n)\n"
  },
  {
    "path": "src/phantom/predicates/re.py",
    "content": "from re import Pattern\n\nfrom . import Predicate\nfrom ._utils import bind_name\n\n\ndef is_match(pattern: Pattern[str]) -> Predicate[str]:\n    \"\"\"\n    Create a new predicate that succeeds when the start of its argument matches the\n    given ``pattern``.\n    \"\"\"\n\n    @bind_name(is_match, pattern.pattern)\n    def match(instance: str) -> bool:\n        return pattern.match(instance) is not None\n\n    return match\n\n\ndef is_full_match(pattern: Pattern[str]) -> Predicate[str]:\n    \"\"\"\n    Create a new predicate that succeeds when its whole argument matches the given\n    ``pattern``.\n    \"\"\"\n\n    @bind_name(is_full_match, pattern.pattern)\n    def full_match(instance: str) -> bool:\n        return pattern.fullmatch(instance) is not None\n\n    return full_match\n"
  },
  {
    "path": "src/phantom/py.typed",
    "content": ""
  },
  {
    "path": "src/phantom/re.py",
    "content": "\"\"\"\nTypes for representing strings that match a pattern.\n\n.. code-block:: python\n\n    class Greeting(Match, pattern=r\"^(Hi|Hello)\"): ...\n\n\n    assert isinstance(\"Hello Jane!\", Greeting)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nfrom re import Pattern\nfrom typing import Any\n\nfrom . import Phantom\nfrom . import _hypothesis\nfrom ._utils.misc import resolve_class_attr\nfrom .predicates.re import is_full_match\nfrom .predicates.re import is_match\nfrom .schema import Schema\n\n__all__ = (\"Match\", \"FullMatch\")\n\n\ndef _compile(pattern: Pattern[str] | str) -> Pattern[str]:\n    if not isinstance(pattern, Pattern):\n        return re.compile(pattern)\n    return pattern\n\n\nclass Match(str, Phantom, abstract=True):\n    \"\"\"\n    Takes ``pattern: Pattern[str] | str`` as class argument as either a compiled\n    :py:class:`Pattern` or a :py:class:`str` to be compiled. Uses the\n    :py:func:`phantom.predicates.re.is_match` predicate.\n    \"\"\"\n\n    __pattern__: Pattern[str]\n\n    def __init_subclass__(cls, pattern: Pattern[str] | str, **kwargs: Any) -> None:\n        resolve_class_attr(cls, \"__pattern__\", _compile(pattern))\n        super().__init_subclass__(predicate=is_match(cls.__pattern__), **kwargs)\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": (\n                \"A string starting with a match of the format regular expression.\"\n            ),\n            \"format\": str(cls.__pattern__.pattern),\n        }\n\n\nclass FullMatch(str, Phantom, abstract=True):\n    \"\"\"\n    Takes ``pattern: Pattern[str] | str`` as class argument as either a compiled\n    :py:class:`Pattern` or a :py:class:`str` to be compiled. Uses the\n    :py:func:`phantom.predicates.re.is_full_match` predicate.\n    \"\"\"\n\n    __pattern__: Pattern[str]\n\n    def __init_subclass__(cls, pattern: Pattern[str] | str, **kwargs: Any) -> None:\n        resolve_class_attr(cls, \"__pattern__\", _compile(pattern))\n        super().__init_subclass__(predicate=is_full_match(cls.__pattern__), **kwargs)\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A string that matches the format regular expression.\",\n            \"format\": str(cls.__pattern__.pattern),\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy | None:\n        from hypothesis.strategies import from_regex\n\n        return from_regex(cls.__pattern__, fullmatch=True)\n"
  },
  {
    "path": "src/phantom/schema.py",
    "content": "from collections.abc import Sequence\nfrom typing import Literal\n\nfrom typing_extensions import TypedDict\nfrom typing_extensions import final\n\n\nclass Schema(TypedDict, total=False):\n    title: str\n    description: str\n    type: Literal[\"array\", \"string\", \"float\", \"number\"]\n    format: str\n    examples: Sequence[object]\n    minimum: float | None\n    maximum: float | None\n    exclusiveMinimum: float | None\n    exclusiveMaximum: float | None\n    minItems: int | None\n    maxItems: int | None\n    minLength: int | None\n    maxLength: int | None\n\n\nclass SchemaField:\n    @classmethod\n    @final\n    def __modify_schema__(cls, field_schema: dict) -> None:\n        \"\"\"\n        This final method is called by pydantic and collects overrides from\n        :func:`Phantom.__schema__() <phantom.Phantom.__schema__>`. Override\n        :func:`__schema__() <phantom.Phantom.__schema__>` to provide custom schema\n        representations for phantom types.\n        \"\"\"\n        field_schema.update(\n            {key: value for key, value in cls.__schema__().items() if value is not None}\n        )\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        \"\"\"\n        Hook for providing schema metadata. Override in subclasses to customize a types\n        schema representation. See pydantic's documentation on ``__modify_schema__()``\n        for more information. This hook differs to pydantic's ``__modify_schema__()``\n        and expects subclasses to instantiate new dicts instead of mutating a given one.\n\n        Example:\n\n        .. code-block:: python\n\n            class Name(str, Phantom, predicate=...):\n                @classmethod\n                def __schema__(cls):\n                    return {**super().__schema__(), \"description\": \"A name type\"}\n        \"\"\"\n        return {\"title\": cls.__name__}\n"
  },
  {
    "path": "src/phantom/sized.py",
    "content": "\"\"\"\nTypes describing collections with size boundaries. These types should only be used with\nimmutable collections. There is a naive check that eliminates some of the most common\nmutable collections in the instance check. However, a guaranteed check is probably\nimpossible to implement, so some amount of developer discipline is required.\n\nSized types are created by subclassing :py:class:`PhantomBound` and providing a minimum,\nmaximum, or both as the ``min`` and ``max`` class arguments. For instance,\n:py:class:`NonEmpty` is implemented using ``min=1``.\n\nThis made-up type would describe sized collections with between 5 and 10 ints:\n\n.. code-block:: python\n\n    class SpecificSize(PhantomBound[int], min=5, max=10): ...\n\n\nThis example creates a type that accepts strings with 255 or less characters:\n\n.. code-block:: python\n\n    class SizedStr(str, PhantomBound[str], max=255): ...\n\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom collections.abc import Sized\n\n# This is the closest I could find to documentation of _ProtocolMeta.\n# https://github.com/python/cpython/commit/74d7f76e2c953fbfdb7ce01b7319d91d471cc5ef\nfrom typing import Any\nfrom typing import Generic\nfrom typing import Protocol\nfrom typing import TypeVar\nfrom typing import _ProtocolMeta\nfrom typing import get_args\nfrom typing import runtime_checkable\n\nfrom . import Phantom\nfrom . import PhantomMeta\nfrom . import Predicate\nfrom . import _hypothesis\nfrom ._utils.misc import is_not_known_mutable_instance\nfrom .predicates import boolean\nfrom .predicates import collection\nfrom .predicates import interval\nfrom .predicates import numeric\nfrom .schema import Schema\n\n__all__ = (\n    \"SizedIterable\",\n    \"PhantomSized\",\n    \"PhantomBound\",\n    \"NonEmpty\",\n    \"NonEmptyStr\",\n    \"Empty\",\n)\n\n\nT = TypeVar(\"T\", bound=object, covariant=True)\n\n\n@runtime_checkable\nclass SizedIterable(Sized, Iterable[T], Protocol[T]):\n    \"\"\"Intersection of :py:class:`typing.Sized` and :py:class:`typing.Iterable`.\"\"\"\n\n\nclass SizedIterablePhantomMeta(PhantomMeta, _ProtocolMeta): ...\n\n\nclass PhantomSized(\n    Phantom[Sized],\n    SizedIterable[T],\n    Generic[T],\n    metaclass=SizedIterablePhantomMeta,\n    bound=SizedIterable,\n    abstract=True,\n):\n    \"\"\"\n    Takes class argument ``len: Predicate[int]``.\n\n    Discouraged in favor of :py:class:`PhantomBound`, which better supports automatic\n    schema generation.\n    \"\"\"\n\n    def __init_subclass__(\n        cls,\n        len: Predicate[int],  # noqa: A002\n        **kwargs: Any,\n    ) -> None:\n        super().__init_subclass__(\n            predicate=boolean.both(\n                is_not_known_mutable_instance,\n                collection.count(len),\n            ),\n            **kwargs,\n        )\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"type\": \"array\",\n        }\n\n\nclass UnresolvedBounds(Exception): ...\n\n\nclass LSPViolation(Exception): ...\n\n\nclass PhantomBound(\n    Phantom[Sized],\n    SizedIterable[T],\n    Generic[T],\n    metaclass=SizedIterablePhantomMeta,\n    bound=SizedIterable,\n    abstract=True,\n):\n    \"\"\"Takes class arguments ``min: int``, ``max: int``.\"\"\"\n\n    __min__: int | None\n    __max__: int | None\n\n    def __init_subclass__(\n        cls,\n        min: int | None = None,  # noqa: A002\n        max: int | None = None,  # noqa: A002\n        abstract: bool = False,\n        **kwargs: Any,\n    ) -> None:\n        inherited_min = getattr(cls, \"__min__\", None)\n        inherited_max = getattr(cls, \"__max__\", None)\n        cls.__min__ = inherited_min if min is None else min\n        cls.__max__ = inherited_max if max is None else max\n\n        # Note: There's possibly value in generalizing this, to be able to declaratively\n        #       describe the relationship between an attribute and its inherited value.\n        if (\n            cls.__min__ is not None\n            and inherited_min is not None\n            and cls.__min__ < inherited_min\n        ):\n            raise LSPViolation(\n                f\"Cannot set a smaller min than inherited ({cls.__min__} < \"\n                f\"{inherited_min}).\"\n            )\n\n        if (\n            cls.__max__ is not None\n            and inherited_max is not None\n            and cls.__max__ > inherited_max\n        ):\n            raise LSPViolation(\n                f\"Cannot set a larger max than inherited ({cls.__max__} > \"\n                f\"{inherited_max}).\"\n            )\n\n        if cls.__min__ is not None and cls.__max__ is not None:\n            size = interval.inclusive(cls.__min__, cls.__max__)\n        elif cls.__min__ is not None:\n            size = numeric.ge(cls.__min__)\n        elif cls.__max__ is not None:\n            size = numeric.le(cls.__max__)\n        elif abstract:\n            super().__init_subclass__(abstract=abstract, **kwargs)\n            return\n        else:\n            raise UnresolvedBounds(\n                f\"Concrete type {cls.__qualname__} must provide either min or max, or \"\n                f\"both.\"\n            )\n\n        super().__init_subclass__(\n            predicate=boolean.both(\n                is_not_known_mutable_instance,\n                collection.count(size),\n            ),\n            abstract=abstract,\n            **kwargs,\n        )\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return (\n            {\n                **super().__schema__(),\n                \"type\": \"string\",\n                \"minLength\": cls.__min__,\n                \"maxLength\": cls.__max__,\n            }\n            if str in cls.__mro__\n            else {\n                **super().__schema__(),\n                \"type\": \"array\",\n                \"minItems\": cls.__min__,\n                \"maxItems\": cls.__max__,\n            }\n        )\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.HypothesisStrategy:\n        from hypothesis.strategies import DrawFn\n        from hypothesis.strategies import composite\n        from hypothesis.strategies import from_type\n        from hypothesis.strategies import lists\n        from hypothesis.strategies import text\n\n        def create_strategy(type_: type[T]) -> _hypothesis.SearchStrategy[T] | None:\n            min_size = cls.__min__ or 0\n\n            if cls.__bound__ is str:\n                return text(  # type: ignore[return-value]\n                    min_size=min_size,\n                    max_size=cls.__max__,\n                )\n\n            (inner_type,) = get_args(type_)\n\n            @composite\n            def tuples(draw: DrawFn) -> tuple:\n                strategy = lists(\n                    from_type(inner_type),\n                    min_size=min_size,\n                    max_size=cls.__max__,\n                )\n                return tuple(draw(strategy))\n\n            return tuples()  # type: ignore[return-value]\n\n        return create_strategy\n\n\nclass NonEmpty(PhantomBound[T], Generic[T], min=1):\n    \"\"\"A sized collection with at least one item.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A non-empty array.\",\n        }\n\n\nclass NonEmptyStr(str, NonEmpty[str]):\n    \"\"\"A sized str with at least one character.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"A non-empty string.\",\n        }\n\n\nclass Empty(PhantomBound[T], Generic[T], max=0):\n    \"\"\"A sized collection with exactly zero items.\"\"\"\n\n    @classmethod\n    def __schema__(cls) -> Schema:\n        return {\n            **super().__schema__(),\n            \"description\": \"An empty array.\",\n        }\n\n    @classmethod\n    def __register_strategy__(cls) -> _hypothesis.SearchStrategy:\n        from hypothesis.strategies import just\n\n        return just(())\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/ext/__init__.py",
    "content": ""
  },
  {
    "path": "tests/ext/test_hypothesis.py",
    "content": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom dataclasses import fields\nfrom functools import total_ordering\nfrom typing import Generic\nfrom typing import TypeVar\nfrom typing import get_origin\nfrom typing import get_type_hints\n\nfrom hypothesis import given\nfrom hypothesis import settings\nfrom hypothesis.strategies import builds\n\nfrom phantom.boolean import Falsy\nfrom phantom.boolean import Truthy\nfrom phantom.datetime import TZAware\nfrom phantom.datetime import TZNaive\nfrom phantom.interval import Exclusive\nfrom phantom.interval import ExclusiveInclusive\nfrom phantom.interval import Inclusive\nfrom phantom.interval import InclusiveExclusive\nfrom phantom.interval import Natural\nfrom phantom.interval import NegativeInt\nfrom phantom.interval import Portion\nfrom phantom.iso3166 import ParsedAlpha2\nfrom phantom.negated import SequenceNotStr\nfrom phantom.re import FullMatch\nfrom phantom.sized import Empty\nfrom phantom.sized import NonEmpty\nfrom phantom.sized import NonEmptyStr\nfrom phantom.sized import PhantomBound\nfrom tests.types import FloatExc\nfrom tests.types import FloatExcInc\nfrom tests.types import FloatInc\nfrom tests.types import FloatIncExc\nfrom tests.types import IntExc\nfrom tests.types import IntExcInc\nfrom tests.types import IntInc\nfrom tests.types import IntIncExc\n\n\nclass TensFloat(float, InclusiveExclusive, low=10, high=20): ...\n\n\nclass TensInt(int, InclusiveExclusive, low=10, high=20): ...\n\n\nclass Url(\n    FullMatch,\n    pattern=r\"https?://www\\.[A-z]+\\.(com|se|org)\",\n): ...\n\n\nT = TypeVar(\"T\", bound=object, covariant=True)\n\n\nclass Few(PhantomBound[T], Generic[T], min=5, max=15): ...\n\n\n@total_ordering\nclass Inf:\n    def __eq__(self, other):\n        return False\n\n    def __lt__(self, other):\n        return False\n\n\n# Test can create types that don't map to a Hypothesis strategy.\nclass InmappableInc(int, Inclusive, low=Inf(), high=100): ...\n\n\nclass InmappableExc(float, Exclusive, low=Inf(), high=100): ...\n\n\nclass InmappableIncExc(int, InclusiveExclusive, low=Inf(), high=100): ...\n\n\nclass InmappableExcInc(float, ExclusiveInclusive, low=Inf(), high=100): ...\n\n\n@dataclass\nclass Model:\n    tz_aware: TZAware\n    tz_naive: TZNaive\n    truthy: Truthy\n    falsy: Falsy\n\n    tens_float: TensFloat\n    tens_int: TensInt\n\n    natural: Natural\n    negative_int: NegativeInt\n    portion: Portion\n    float_inc: FloatInc\n    int_inc: IntInc\n    float_exc: FloatExc\n    int_exc: IntExc\n    float_inc_exc: FloatIncExc\n    int_inc_exc: IntIncExc\n    float_exc_inc: FloatExcInc\n    int_exc_inc: IntExcInc\n\n    parsed_alpha_2: ParsedAlpha2\n\n    url: Url\n\n    not_str: SequenceNotStr[str | int]\n\n    non_empty: NonEmpty[int]\n    non_empty_str: NonEmptyStr\n    empty: Empty\n\n    ints: Few[int]\n    mixed: Few[int | str]\n\n\n@given(builds(Model))\n@settings(max_examples=10)\ndef test_can_generate_hypothesis_values(model: Model) -> None:\n    hints = get_type_hints(Model)\n    for field in fields(Model):\n        type_ = hints[field.name]\n        inner_type = get_origin(type_) or type_\n        assert isinstance(getattr(model, field.name), inner_type)\n"
  },
  {
    "path": "tests/ext/test_phonenumbers.py",
    "content": "import pytest\nfrom typing_extensions import assert_type\n\nfrom phantom.errors import BoundError\nfrom phantom.ext.phonenumbers import FormattedPhoneNumber\nfrom phantom.ext.phonenumbers import InvalidPhoneNumber\nfrom phantom.ext.phonenumbers import PhoneNumber\nfrom phantom.ext.phonenumbers import _deconstruct_phone_number\nfrom phantom.ext.phonenumbers import is_formatted_phone_number\nfrom phantom.ext.phonenumbers import is_phone_number\nfrom phantom.ext.phonenumbers import normalize_phone_number\n\npytestmark = [pytest.mark.external]\n\n\nclass TestPhoneNumber:\n    def test_unparsable_number_is_not_instance(self):\n        assert not isinstance(\"+46\", PhoneNumber)\n\n    def test_invalid_number_is_not_instance(self):\n        assert not isinstance(\"+467012345678\", PhoneNumber)\n\n    def test_unformatted_number_is_instance(self):\n        assert isinstance(\"+46 (701) 234567\", PhoneNumber)\n\n\nclass TestFormattedPhoneNumber:\n    def test_unparsable_number_is_not_instance(self):\n        assert not isinstance(\"+46\", FormattedPhoneNumber)\n\n    def test_invalid_number_is_not_instance(self):\n        assert not isinstance(\"+467012345678\", FormattedPhoneNumber)\n\n    def test_unformatted_number_is_not_instance(self):\n        assert not isinstance(\"+46 (701) 234567\", FormattedPhoneNumber)\n\n    def test_formatted_number_is_instance(self):\n        assert isinstance(\"+46701234567\", FormattedPhoneNumber)\n\n    def test_normalizes_unformatted_number(self):\n        number = FormattedPhoneNumber.parse(\"+46 (701) 234567\")\n        assert number == \"+46701234567\"\n        assert isinstance(number, FormattedPhoneNumber)\n\n    def test_parse_raises_for_invalid_phone_number(self):\n        with pytest.raises(InvalidPhoneNumber):\n            FormattedPhoneNumber.parse(\"+46\")\n\n    def test_raises_type_error_for_out_of_bound_type(self):\n        \"\"\"Since we override parse we need to test the bound check\"\"\"\n        value = 123\n        with pytest.raises(\n            BoundError, match=rf\"Value is not within bound of 'str': {value}\"\n        ):\n            FormattedPhoneNumber.parse(123)\n\n\nclass TestDeconstructPhoneNumber:\n    def test_can_parse_international_phone_number_without_country_code(self):\n        result = _deconstruct_phone_number(\"123456789\", \"SE\")\n        assert result.country_code == 46\n        assert result.national_number == 123456789\n\n    def test_can_parse_international_phone_number_with_country_code(self):\n        result = _deconstruct_phone_number(\"+46123456789\", \"NO\")\n        assert result.country_code == 46\n        assert result.national_number == 123456789\n\n    def test_can_parse_national_phone_number_with_country_code(self):\n        result = _deconstruct_phone_number(\"0123456789\", \"SE\")\n        assert result.country_code == 46\n        assert result.national_number == 123456789\n\n    def test_raises_invalid_phone_number_for_insufficient_country_data(self):\n        with pytest.raises(InvalidPhoneNumber) as exc_info:\n            _deconstruct_phone_number(\"0701\")\n        assert exc_info.value.error_type == InvalidPhoneNumber.INVALID_COUNTRY_CODE\n\n    def test_raises_invalid_phone_number_for_parse_exception(self):\n        with pytest.raises(InvalidPhoneNumber) as exc_info:\n            _deconstruct_phone_number(\"0\")\n        assert exc_info.value.error_type == InvalidPhoneNumber.NOT_A_NUMBER\n\n    def test_raises_invalid_phone_number_for_invalid_phone_number(self):\n        with pytest.raises(InvalidPhoneNumber) as exc_info:\n            _deconstruct_phone_number(\"+461234567890\")\n        assert exc_info.value.error_type == InvalidPhoneNumber.INVALID\n\n\nclass TestNormalizePhoneNumber:\n    def test_can_normalize_national_number_with_country_code(self):\n        assert normalize_phone_number(\"(123) 456 789\", \"SE\") == \"+46123456789\"\n\n    def test_can_normalize_international_number_without_country_code(self):\n        assert normalize_phone_number(\"+46 (123) 456 789\") == \"+46123456789\"\n\n\nclass TestIsPhoneNumber:\n    def test_returns_true_for_valid_number(self):\n        assert is_phone_number(\"+46 (123) 456789\") is True\n\n    def test_returns_false_for_invalid_number(self):\n        assert is_phone_number(\"+461234567890\") is False\n\n\nclass TestIsFormattedPhoneNumber:\n    def test_returns_true_for_formatted_number(self) -> None:\n        value = \"+46123456789\"\n        assert is_formatted_phone_number(value)\n        assert_type(value, FormattedPhoneNumber)\n\n    def test_returns_false_for_unformatted_number(self):\n        assert is_formatted_phone_number(\"+46 (123) 456 789\") is False\n"
  },
  {
    "path": "tests/ext/test_phonenumbers.yaml",
    "content": "- case: bound_is_not_subtype\n  main: |\n    from phantom.ext.phonenumbers import PhoneNumber\n    from phantom.ext.phonenumbers import FormattedPhoneNumber\n\n    def takes_phone_number(n: PhoneNumber) -> None:\n        ...\n\n    takes_phone_number(\"\")  # E: Argument 1 to \"takes_phone_number\" has incompatible type \"str\"; expected \"PhoneNumber\"  [arg-type]\n\n    def takes_formatted_phone_number(n: FormattedPhoneNumber) -> None:\n        ...\n\n    takes_formatted_phone_number(\"\")  # E: Argument 1 to \"takes_formatted_phone_number\" has incompatible type \"str\"; expected \"FormattedPhoneNumber\"  [arg-type]\n- case: can_instantiate\n  main: |\n    from phantom.ext.phonenumbers import PhoneNumber\n    from phantom.ext.phonenumbers import FormattedPhoneNumber\n\n    def takes_phone_number(n: PhoneNumber) -> None:\n        ...\n\n    takes_phone_number(PhoneNumber.parse(\"\"))\n\n    def takes_formatted_phone_number(n: FormattedPhoneNumber) -> None:\n        ...\n\n    takes_formatted_phone_number(FormattedPhoneNumber.parse(\"\"))\n- case: can_infer\n  main: |\n    from phantom.ext.phonenumbers import PhoneNumber\n    from phantom.ext.phonenumbers import FormattedPhoneNumber\n\n    def takes_phone_number(n: PhoneNumber) -> None:\n        ...\n\n    n = \"\"\n    assert isinstance(n, PhoneNumber)\n    takes_phone_number(n)\n\n    def takes_formatted_phone_number(n: FormattedPhoneNumber) -> None:\n        ...\n\n    f = \"\"\n    assert isinstance(f, FormattedPhoneNumber)\n    takes_formatted_phone_number(f)\n"
  },
  {
    "path": "tests/predicates/__init__.py",
    "content": "import pytest\n\npytest.register_assert_rewrite(\"tests.predicates.utils\")\n"
  },
  {
    "path": "tests/predicates/test_boolean.py",
    "content": "from collections.abc import Iterable\n\nimport pytest\n\nfrom phantom import Predicate\nfrom phantom.predicates import boolean\n\nfrom .utils import assert_predicate_name_equals\n\n\nclass TestTrue:\n    @pytest.mark.parametrize(\"value\", [0, False, 1, \"test\", ()])\n    def test_returns_true_for_any_given_value(self, value: object) -> None:\n        assert boolean.true(value) is True\n\n\nclass TestFalse:\n    @pytest.mark.parametrize(\"value\", [0, False, 1, \"test\", ()])\n    def test_returns_false_for_any_given_value(self, value: object) -> None:\n        assert boolean.false(value) is False\n\n\nclass TestNegate:\n    def test_can_negate_true(self):\n        assert boolean.negate(boolean.true)(object()) is False\n\n    def test_can_negate_false(self):\n        assert boolean.negate(boolean.false)(0) is True\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(boolean.negate(boolean.true), \"negate(true)\")\n\n\nparametrize_truthy = pytest.mark.parametrize(\"value\", [1, \"a\", (0,), [\"\"], True])\nparametrize_falsy = pytest.mark.parametrize(\"value\", [0, \"\", (), [], False])\n\n\nclass TestTruthy:\n    @parametrize_truthy\n    def test_returns_true_for_truthy_value(self, value: object) -> None:\n        assert boolean.truthy(value) is True\n\n    @parametrize_falsy\n    def test_returns_false_for_falsy_value(self, value: object) -> None:\n        assert boolean.truthy(value) is False\n\n\nclass TestFalsy:\n    @parametrize_falsy\n    def test_returns_true_for_falsy_value(self, value: object) -> None:\n        assert boolean.falsy(value) is True\n\n    @parametrize_truthy\n    def test_returns_false_for_truthy_value(self, value: object) -> None:\n        assert boolean.falsy(value) is False\n\n\nclass TestBoth:\n    def test_returns_true_for_two_succeeding_predicates(self) -> None:\n        assert boolean.both(boolean.true, boolean.true)(0) is True\n\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            (boolean.false, boolean.true),\n            (boolean.true, boolean.false),\n            (boolean.false, boolean.false),\n        ],\n    )\n    def test_returns_false_for_falsy_predicate(\n        self, a: Predicate, b: Predicate\n    ) -> None:\n        assert boolean.both(a, b)(0) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.both(boolean.true, boolean.false), \"both(true, false)\"\n        )\n\n\nclass TestEither:\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            (boolean.false, boolean.true),\n            (boolean.true, boolean.false),\n            (boolean.true, boolean.true),\n        ],\n    )\n    def test_returns_true_for_truthy_predicate(\n        self, a: Predicate, b: Predicate\n    ) -> None:\n        assert boolean.either(a, b)(0) is True\n\n    def test_returns_false_for_two_falsy_predicates(self) -> None:\n        assert boolean.either(boolean.false, boolean.false)(0) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.either(boolean.false, boolean.true), \"either(false, true)\"\n        )\n\n\nclass TestXor:\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            (boolean.false, boolean.true),\n            (boolean.true, boolean.false),\n        ],\n    )\n    def test_returns_true_for_two_different_bools(\n        self, a: Predicate, b: Predicate\n    ) -> None:\n        assert boolean.xor(a, b)(0) is True\n\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            (boolean.false, boolean.false),\n            (boolean.true, boolean.true),\n        ],\n    )\n    def test_returns_false_for_two_equal_bools(\n        self, a: Predicate, b: Predicate\n    ) -> None:\n        assert boolean.xor(a, b)(0) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.xor(boolean.true, boolean.false), \"xor(true, false)\"\n        )\n\n\nparametrize_all_true = pytest.mark.parametrize(\n    \"predicates\",\n    [\n        (boolean.true,),\n        (boolean.true, boolean.true),\n        (boolean.true, boolean.true, boolean.true),\n    ],\n)\nparametrize_some_false = pytest.mark.parametrize(\n    \"predicates\",\n    [\n        (boolean.true, boolean.false),\n        (boolean.false, boolean.true),\n        (boolean.true, boolean.false, boolean.true),\n    ],\n)\nparametrize_all_false = pytest.mark.parametrize(\n    \"predicates\",\n    [\n        (boolean.false,),\n        (boolean.false, boolean.false),\n        (boolean.false, boolean.false, boolean.false),\n    ],\n)\n\n\nclass TestAllOf:\n    def test_returns_true_for_empty_set_of_predicates(self) -> None:\n        predicate: Predicate[int] = boolean.all_of(())\n        assert predicate(0) is True\n\n    @parametrize_all_true\n    def test_returns_true_for_succeeding_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.all_of(predicates)(0) is True\n\n    @parametrize_some_false\n    def test_returns_false_for_some_failing_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.all_of(predicates)(0) is False\n\n    @parametrize_all_false\n    def test_returns_false_for_only_failing_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.all_of(predicates)(0) is False\n\n    @parametrize_some_false\n    def test_materializes_generated_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        predicate = boolean.all_of(predicate for predicate in predicates)\n        assert predicate(0) is False\n        assert predicate(0) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.all_of([boolean.true, boolean.false]), \"all_of(true, false)\"\n        )\n\n\nclass TestAnyOf:\n    def test_returns_false_for_empty_set_of_predicates(self) -> None:\n        predicate: Predicate[int] = boolean.any_of(())\n        assert predicate(0) is False\n\n    @parametrize_all_true\n    def test_returns_true_for_succeeding_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.any_of(predicates)(0) is True\n\n    @parametrize_some_false\n    def test_returns_true_for_some_failing_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.any_of(predicates)(0) is True\n\n    @parametrize_all_false\n    def test_returns_false_for_only_failing_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.any_of(predicates)(0) is False\n\n    @parametrize_some_false\n    def test_materializes_generated_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        predicate = boolean.any_of(predicate for predicate in predicates)\n        assert predicate(0) is True\n        assert predicate(0) is True\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.any_of([boolean.false, boolean.true]), \"any_of(false, true)\"\n        )\n\n\nclass TestOneOf:\n    def test_returns_false_for_empty_set_of_predicates(self) -> None:\n        predicate: Predicate[int] = boolean.one_of(())\n        assert predicate(0) is False\n\n    @pytest.mark.parametrize(\n        \"predicates\",\n        [\n            (boolean.true, boolean.true),\n            (boolean.true, boolean.true, boolean.true),\n            (boolean.false, boolean.true, boolean.true),\n            (boolean.true, boolean.true, boolean.false),\n            (boolean.true, boolean.false, boolean.true),\n        ],\n    )\n    def test_returns_false_for_more_than_one_succeeding_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.one_of(predicates)(0) is False\n\n    @pytest.mark.parametrize(\n        \"predicates\",\n        [\n            (boolean.true,),\n            (boolean.true, boolean.false),\n            (boolean.false, boolean.true),\n            (boolean.true, boolean.false, boolean.false),\n            (boolean.false, boolean.true, boolean.false),\n            (boolean.false, boolean.false, boolean.true),\n        ],\n    )\n    def test_returns_true_for_one_succeeding_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.one_of(predicates)(0) is True\n\n    @parametrize_all_false\n    def test_returns_false_for_only_failing_predicate(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        assert boolean.one_of(predicates)(0) is False\n\n    @pytest.mark.parametrize(\n        \"predicates\",\n        [\n            (boolean.true,),\n            (boolean.false, boolean.true),\n            (boolean.false, boolean.true, boolean.false),\n        ],\n    )\n    def test_materializes_generated_predicates(\n        self, predicates: Iterable[Predicate]\n    ) -> None:\n        predicate = boolean.one_of(predicate for predicate in predicates)\n        assert predicate(0) is True\n        assert predicate(0) is True\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            boolean.one_of([boolean.true, boolean.false]), \"one_of(true, false)\"\n        )\n"
  },
  {
    "path": "tests/predicates/test_collection.py",
    "content": "from collections.abc import Container\nfrom collections.abc import Iterable\nfrom collections.abc import Sized\n\nimport pytest\n\nfrom phantom import Predicate\nfrom phantom.predicates import collection\nfrom phantom.predicates import generic\nfrom phantom.predicates import numeric\n\nfrom .utils import assert_predicate_name_equals\n\n\nclass TestContains:\n    @pytest.mark.parametrize(\n        \"item, container\",\n        [\n            (1, (1, 2)),\n            (\"b\", \"abc\"),\n        ],\n    )\n    def test_returns_true_for_container_with_item(\n        self, item: object, container: Container\n    ) -> None:\n        assert collection.contains(item)(container) is True\n\n    @pytest.mark.parametrize(\n        \"item, container\",\n        [\n            (1, (2, 3)),\n            (\"d\", \"abc\"),\n        ],\n    )\n    def test_returns_false_for_container_without_item(\n        self, item: object, container: Container\n    ) -> None:\n        assert collection.contains(item)(container) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            collection.contains(\"needle\"), \"contains('needle')\"\n        )\n\n\nclass TestContained:\n    @pytest.mark.parametrize(\n        \"container, item\",\n        [\n            ((1, 2), 1),\n            (\"abc\", \"b\"),\n        ],\n    )\n    def test_returns_true_for_item_in_container(\n        self, container: Container, item: object\n    ) -> None:\n        assert collection.contained(container)(item) is True\n\n    @pytest.mark.parametrize(\n        \"container, item\",\n        [\n            ((2, 3), 1),\n            (\"abc\", \"d\"),\n        ],\n    )\n    def test_returns_false_for_item_not_in_container(\n        self, container: Container, item: object\n    ) -> None:\n        assert collection.contained(container)(item) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            collection.contained((1, 2, 3)), \"contained((1, 2, 3))\"\n        )\n\n\nclass TestCount:\n    @pytest.mark.parametrize(\n        \"predicate, sized\",\n        [\n            (generic.equal(1), [0]),\n            (numeric.le(3), (\"a\", \"b\")),\n            (numeric.ge(3), \"abc\"),\n        ],\n    )\n    def test_returns_true_for_size_matching_predicate(\n        self, predicate: Predicate, sized: Sized\n    ) -> None:\n        assert collection.count(predicate)(sized) is True\n\n    @pytest.mark.parametrize(\n        \"predicate, sized\",\n        [\n            (generic.equal(1), [0, 0]),\n            (numeric.le(3), (\"a\", \"b\", 1, 2)),\n            (numeric.ge(3), \"ab\"),\n        ],\n    )\n    def test_returns_false_for_size_failing_predicate(\n        self, predicate: Predicate, sized: Sized\n    ) -> None:\n        assert collection.count(predicate)(sized) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(collection.count(numeric.ge(3)), \"count(ge(3))\")\n\n\nclass TestExists:\n    @pytest.mark.parametrize(\n        \"predicate, iterable\",\n        [\n            (generic.identical(...), [\"a\", \"b\", ...]),\n            (numeric.greater(0), [-3, 0, 1, 0]),\n            (generic.equal(\"a\"), \"cbad\"),\n        ],\n    )\n    def test_returns_true_for_iterable_containing_satisfying_item(\n        self, predicate: Predicate[object], iterable: Iterable\n    ) -> None:\n        assert collection.exists(predicate)(iterable) is True\n\n    @pytest.mark.parametrize(\n        \"predicate, iterable\",\n        [\n            (generic.identical(...), [\"a\", \"b\", \"c\"]),\n            (numeric.greater(1), [-3, 0, 1, 0]),\n            (generic.equal(\"a\"), \"cbed\"),\n        ],\n    )\n    def test_returns_false_for_iterable_not_containing_satisfying_item(\n        self, predicate: Predicate[object], iterable: Iterable\n    ) -> None:\n        assert collection.exists(predicate)(iterable) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            collection.exists(numeric.less(-3)), \"exists(less(-3))\"\n        )\n\n\nclass TestEvery:\n    @pytest.mark.parametrize(\n        \"predicate, iterable\",\n        [\n            (generic.identical(1), [1, 1, 1]),\n            (generic.identical(1), []),\n            (numeric.greater(0), [1, 2]),\n            (generic.equal(\"a\"), \"aa\"),\n        ],\n    )\n    def test_returns_true_for_complete_iterable(\n        self, predicate: Predicate[object], iterable: Iterable\n    ) -> None:\n        assert collection.every(predicate)(iterable) is True\n\n    @pytest.mark.parametrize(\n        \"predicate, iterable\",\n        [\n            (generic.identical(...), [..., \"b\", \"c\"]),\n            (numeric.greater(1), [-3, 0, 1, 0, 2]),\n            (generic.equal(\"a\"), \"abc\"),\n        ],\n    )\n    def test_returns_false_for_incomplete_iterable(\n        self, predicate: Predicate[object], iterable: Iterable\n    ) -> None:\n        assert collection.every(predicate)(iterable) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            collection.every(numeric.greater(42)), \"every(greater(42))\"\n        )\n"
  },
  {
    "path": "tests/predicates/test_datetime.py",
    "content": "import datetime\n\nimport pytest\n\nfrom phantom.predicates.datetime import is_tz_aware\nfrom phantom.predicates.datetime import is_tz_naive\n\nparametrize_aware = pytest.mark.parametrize(\n    \"dt\", (datetime.datetime.now(tz=datetime.timezone.utc),)\n)\nparametrize_naive = pytest.mark.parametrize(\n    \"dt\", (datetime.datetime.now(), datetime.datetime(1969, 12, 23))\n)\n\n\nclass TestIsTZAware:\n    @parametrize_aware\n    def test_returns_true_for_aware_dt(self, dt: datetime.datetime) -> None:\n        assert is_tz_aware(dt) is True\n\n    @parametrize_naive\n    def test_returns_false_for_naive_dt(self, dt: datetime.datetime) -> None:\n        assert is_tz_aware(dt) is False\n\n\nclass TestIsTZNaive:\n    @parametrize_naive\n    def test_returns_true_for_naive_dt(self, dt: datetime.datetime) -> None:\n        assert is_tz_naive(dt) is True\n\n    @parametrize_aware\n    def test_returns_false_for_aware_dt(self, dt: datetime.datetime) -> None:\n        assert is_tz_naive(dt) is False\n"
  },
  {
    "path": "tests/predicates/test_generic.py",
    "content": "import pytest\n\nfrom phantom.predicates import generic\n\nfrom .utils import assert_predicate_name_equals\n\n\nclass TestEqual:\n    @pytest.mark.parametrize(\"a, b\", [(1, 1.0), (False, 0), (True, 1)])\n    def test_returns_true_for_equal_values(self, a: object, b: object) -> None:\n        assert generic.equal(a)(b) is True\n        assert generic.equal(b)(a) is True\n\n    @pytest.mark.parametrize(\"a, b\", [(2, 1), (1, 1.1), (True, False)])\n    def test_returns_false_for_non_equal_values(self, a: object, b: object) -> None:\n        assert generic.equal(a)(b) is False\n        assert generic.equal(b)(a) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(generic.equal(\"hello\"), \"equal('hello')\")\n\n\nclass TestIdentical:\n    @pytest.mark.parametrize(\n        \"a, b\", [(1, 1), (\"a\", \"a\"), (True, True), (False, False), ((), ())]\n    )\n    def test_returns_true_for_identical_values(self, a: object, b: object) -> None:\n        assert generic.identical(a)(b) is True\n        assert generic.identical(b)(a) is True\n\n    @pytest.mark.parametrize(\"a, b\", [([], []), (1, 1.0), (False, 0), (True, 1)])\n    def test_returns_false_for_different_values(self, a: object, b: object) -> None:\n        assert generic.identical(a)(b) is False\n        assert generic.identical(b)(a) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(generic.identical(\"hello\"), \"identical('hello')\")\n\n\nclass TestOfType:\n    @pytest.mark.parametrize(\"instance,types\", [(1, int), (1, (int, float))])\n    def test_returns_true_for_instance_of_types(\n        self,\n        instance: object,\n        types: type | tuple[type, ...],\n    ) -> None:\n        assert generic.of_type(types)(instance) is True\n\n    @pytest.mark.parametrize(\"instance,types\", [(1, float), (\"\", (int, float))])\n    def test_returns_false_for_instance_of_other_type(\n        self,\n        instance: object,\n        types: type | tuple[type, ...],\n    ) -> None:\n        assert generic.of_type(types)(instance) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(generic.of_type(int), \"of_type(int)\")\n"
  },
  {
    "path": "tests/predicates/test_interval.py",
    "content": "from typing import TypeAlias\n\nimport pytest\n\nfrom phantom.predicates import interval\n\nfrom .utils import assert_predicate_name_equals\n\nBoundaries: TypeAlias = tuple[float, float]\nparametrize_inside = pytest.mark.parametrize(\n    \"value, boundaries\",\n    [\n        (1.0001, (1, 2)),\n        (1.9999, (1, 2)),\n        (1.00001, (1.0, 2)),\n        (1.99999, (1, 2.0)),\n    ],\n)\nparametrize_on_edge = pytest.mark.parametrize(\n    \"value, boundaries\",\n    [\n        (1, (1, 2)),\n        (2, (1, 2)),\n        (1.0, (1.0, 2)),\n        (2.0, (1, 2.0)),\n    ],\n)\nparametrize_outside = pytest.mark.parametrize(\n    \"value, boundaries\",\n    [\n        (0, (1, 2)),\n        (3, (1, 2)),\n        (0.99999, (1.0, 2)),\n        (2.00001, (1, 2.0)),\n        (20, (1, 3)),\n    ],\n)\n\n\nclass TestExclusive:\n    def test_returns_true_for_middle_value(self) -> None:\n        assert interval.exclusive(1, 3)(2) is True\n\n    @parametrize_inside\n    def test_returns_true_for_inside_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.exclusive(*boundaries)(value) is True\n\n    @parametrize_on_edge\n    def test_returns_false_for_edge_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.exclusive(*boundaries)(value) is False\n\n    @parametrize_outside\n    def test_returns_false_for_outside_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.exclusive(*boundaries)(value) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(interval.exclusive(0, 1), \"exclusive(0, 1)\")\n\n\nclass TestInclusive:\n    def test_returns_true_for_middle_value(self) -> None:\n        assert interval.inclusive(1, 3)(2) is True\n\n    @parametrize_inside\n    def test_returns_true_for_inside_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.inclusive(*boundaries)(value) is True\n\n    @parametrize_on_edge\n    def test_returns_true_for_edge_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.inclusive(*boundaries)(value) is True\n\n    @parametrize_outside\n    def test_returns_false_for_outside_value(\n        self, value: float, boundaries: Boundaries\n    ) -> None:\n        assert interval.inclusive(*boundaries)(value) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(interval.inclusive(0, 1), \"inclusive(0, 1)\")\n\n\nclass TestInclusiveExclusive:\n    def test_returns_true_for_middle_value(self) -> None:\n        assert interval.inclusive_exclusive(1, 3)(2) is True\n\n    def test_lower_bound(self) -> None:\n        intv = interval.inclusive_exclusive(5, 10)\n        assert intv(5) is True\n        assert intv(4.9999) is False\n\n    def test_upper_bound(self) -> None:\n        intv = interval.inclusive_exclusive(5, 10)\n        assert intv(10) is False\n        assert intv(9.9999) is True\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            interval.inclusive_exclusive(0, 1), \"inclusive_exclusive(0, 1)\"\n        )\n\n\nclass TestExclusiveInclusive:\n    def test_returns_true_for_middle_value(self) -> None:\n        assert interval.exclusive_inclusive(1, 3)(2) is True\n\n    def test_lower_bound(self) -> None:\n        intv = interval.exclusive_inclusive(5, 10)\n        assert intv(5.0001) is True\n        assert intv(5) is False\n\n    def test_upper_bound(self) -> None:\n        intv = interval.exclusive_inclusive(5, 10)\n        assert intv(10) is True\n        assert intv(10.00001) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            interval.exclusive_inclusive(0, 1), \"exclusive_inclusive(0, 1)\"\n        )\n"
  },
  {
    "path": "tests/predicates/test_numeric.py",
    "content": "import pytest\n\nfrom phantom.predicates import numeric\n\nfrom .utils import assert_predicate_name_equals\n\n\nclass TestLess:\n    def test_returns_true_for_values_below_limit(self) -> None:\n        predicate = numeric.less(10)\n        assert predicate(9) is True\n        assert predicate(9.999) is True\n        assert predicate(-5) is True\n\n    def test_returns_false_for_values_above_limit(self) -> None:\n        predicate = numeric.less(10)\n        assert predicate(10) is False\n        assert predicate(11) is False\n        assert predicate(123) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(numeric.less(100), \"less(100)\")\n\n\nclass TestLE:\n    def test_returns_true_for_values_below_limit(self) -> None:\n        predicate = numeric.le(10)\n        assert predicate(9) is True\n        assert predicate(9.999) is True\n        assert predicate(-5) is True\n        assert predicate(10) is True\n\n    def test_returns_false_for_values_above_limit(self) -> None:\n        predicate = numeric.le(10)\n        assert predicate(10.0001) is False\n        assert predicate(11) is False\n        assert predicate(123) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(numeric.le(-100), \"le(-100)\")\n\n\nclass TestGreater:\n    def test_returns_true_for_values_above_limit(self) -> None:\n        predicate = numeric.greater(120)\n        assert predicate(121) is True\n        assert predicate(120.0001) is True\n        assert predicate(1200) is True\n\n    def test_returns_false_for_values_below_limit(self) -> None:\n        predicate = numeric.greater(120)\n        assert predicate(120) is False\n        assert predicate(119.9999) is False\n        assert predicate(-120) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(numeric.greater(10), \"greater(10)\")\n\n\nclass TestGE:\n    def test_returns_true_for_values_above_limit(self) -> None:\n        predicate = numeric.ge(120)\n        assert predicate(121) is True\n        assert predicate(120.0001) is True\n        assert predicate(1200) is True\n        assert predicate(120) is True\n\n    def test_returns_false_for_values_below_limit(self) -> None:\n        predicate = numeric.ge(120)\n        assert predicate(119.9999) is False\n        assert predicate(-120) is False\n        assert predicate(119) is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(numeric.ge(10.134), \"ge(10.134)\")\n\n\nclass TestPositive:\n    def test_limits(self) -> None:\n        assert numeric.positive(0) is False\n        assert numeric.positive(-1) is False\n        assert numeric.positive(1) is True\n        assert numeric.positive(0.0001) is True\n\n\nclass TestNonPositive:\n    def test_limits(self) -> None:\n        assert numeric.non_positive(0) is True\n        assert numeric.non_positive(-1) is True\n        assert numeric.non_positive(1) is False\n        assert numeric.non_positive(0.0001) is False\n\n\nclass TestNegative:\n    def test_limits(self) -> None:\n        assert numeric.negative(0) is False\n        assert numeric.negative(-1) is True\n        assert numeric.negative(1) is False\n        assert numeric.negative(-0.0001) is True\n\n\nclass TestNonNegative:\n    def test_limits(self) -> None:\n        assert numeric.non_negative(0) is True\n        assert numeric.non_negative(-1) is False\n        assert numeric.non_negative(1) is True\n        assert numeric.non_negative(-0.0001) is False\n\n\nclass TestModulo:\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(\n            numeric.modulo(2, numeric.greater(99)), \"modulo(2, greater(99))\"\n        )\n\n\nparametrize_even = pytest.mark.parametrize(\"value\", [-8296, 0, 2, 32, 9314])\nparametrize_odd = pytest.mark.parametrize(\"value\", [-13829, -1, 1, 31, 10023])\n\n\nclass TestEven:\n    @parametrize_even\n    def test_returns_true_for_even_value(self, value: int) -> None:\n        assert numeric.even(value) is True\n\n    @parametrize_odd\n    def test_returns_false_for_odd_value(self, value: int) -> None:\n        assert numeric.even(value) is False\n\n\nclass TestOdd:\n    @parametrize_odd\n    def test_returns_true_for_odd_value(self, value: int) -> None:\n        assert numeric.odd(value) is True\n\n    @parametrize_even\n    def test_returns_false_for_even_value(self, value: int) -> None:\n        assert numeric.odd(value) is False\n"
  },
  {
    "path": "tests/predicates/test_re.py",
    "content": "import re\n\nfrom phantom.predicates.re import is_full_match\nfrom phantom.predicates.re import is_match\n\nfrom .utils import assert_predicate_name_equals\n\npattern = re.compile(\"abc@\")\nabc_match = is_match(pattern)\nabc_full_match = is_full_match(pattern)\n\n\nclass TestIsMatch:\n    def test_returns_true_for_matching_string(self) -> None:\n        assert abc_match(\"abc@\") is True\n        assert abc_match(\"abc@ extra\") is True\n\n    def test_returns_false_for_non_matching_string(self) -> None:\n        assert abc_match(\"abd@\") is False\n        assert abc_match(\"extra abc@\") is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(abc_match, \"is_match('abc@')\")\n\n\nclass TestIsFullMatch:\n    def test_returns_true_for_matching_string(self) -> None:\n        assert abc_full_match(\"abc@\") is True\n\n    def test_returns_false_for_non_matching_string(self) -> None:\n        assert abc_full_match(\"abc@ extra\") is False\n        assert abc_full_match(\"abd@\") is False\n        assert abc_full_match(\"extra abc@\") is False\n\n    def test_repr_contains_bound_parameter(self):\n        assert_predicate_name_equals(abc_full_match, \"is_full_match('abc@')\")\n"
  },
  {
    "path": "tests/predicates/test_utils.py",
    "content": "from functools import partial\n\nfrom phantom.predicates import boolean\n\nfrom .utils import assert_predicate_name_equals\n\n\ndef foo(a: int, b: int, c: int) -> bool:\n    return a > b > c\n\n\nclass TestFunctionRepr:\n    def test_explodes_partial_arguments(self):\n        predicate = partial(foo, 10, b=5)\n        assert_predicate_name_equals(boolean.negate(predicate), \"negate(foo(10, b=5))\")\n        predicate = partial(foo, 23, c=31)\n        assert_predicate_name_equals(boolean.negate(predicate), \"negate(foo(23, c=31))\")\n"
  },
  {
    "path": "tests/predicates/utils.py",
    "content": "from phantom import Predicate\n\n\ndef assert_predicate_name_equals(predicate: Predicate, expected_name: str) -> None:\n    assert predicate.__name__ == expected_name\n    assert predicate.__qualname__ == expected_name\n    assert repr(predicate).startswith(f\"<function {expected_name} at \")\n"
  },
  {
    "path": "tests/pydantic/__init__.py",
    "content": ""
  },
  {
    "path": "tests/pydantic/test_datetime.py",
    "content": "import datetime\n\nimport pydantic\nimport pytest\nfrom pydantic import ValidationError\n\nfrom phantom.datetime import TZAware\nfrom phantom.datetime import TZNaive\nfrom tests.test_datetime import parametrize_aware_str\nfrom tests.test_datetime import parametrize_naive_str\n\npytestmark = [pytest.mark.external]\n\n\nclass HasTZAware(pydantic.BaseModel):\n    created_at: TZAware\n\n\nclass TestPydanticTZAware:\n    @parametrize_aware_str\n    def test_can_parse_tz_aware(self, value: str, expected: datetime.datetime):\n        obj = HasTZAware.parse_obj({\"created_at\": value})\n        assert type(obj.created_at) is datetime.datetime\n        assert obj.created_at == expected\n\n    def test_tz_aware_rejects_naive_datetime(self):\n        with pytest.raises(ValidationError):\n            HasTZAware.parse_obj({\"created_at\": \"2022-09-24T10:40:20\"})\n\n\nclass HasTZNaive(pydantic.BaseModel):\n    time_of_day: TZNaive\n\n\nclass TestPydanticTZNaive:\n    @parametrize_naive_str\n    def test_can_parse_tz_naive(self, value: str, expected: datetime.datetime):\n        obj = HasTZNaive.parse_obj({\"time_of_day\": value})\n        assert type(obj.time_of_day) is datetime.datetime\n        assert obj.time_of_day == expected\n\n    def test_tz_naive_rejects_aware_datetime(self):\n        with pytest.raises(ValidationError):\n            HasTZNaive.parse_obj({\"time_of_day\": \"2022-09-24T10:40:20+00:00\"})\n"
  },
  {
    "path": "tests/pydantic/test_schemas.py",
    "content": "import pydantic\nimport pytest\n\nfrom phantom.datetime import TZAware\nfrom phantom.datetime import TZNaive\nfrom phantom.ext.phonenumbers import FormattedPhoneNumber\nfrom phantom.ext.phonenumbers import PhoneNumber\nfrom phantom.interval import Exclusive\nfrom phantom.interval import ExclusiveInclusive\nfrom phantom.interval import Inclusive\nfrom phantom.interval import InclusiveExclusive\nfrom phantom.interval import Natural\nfrom phantom.interval import NegativeInt\nfrom phantom.interval import Portion\nfrom phantom.iso3166 import ParsedAlpha2\nfrom phantom.negated import SequenceNotStr\nfrom phantom.predicates.numeric import odd\nfrom phantom.re import FullMatch\nfrom phantom.re import Match\nfrom phantom.sized import Empty\nfrom phantom.sized import NonEmpty\nfrom phantom.sized import NonEmptyStr\nfrom phantom.sized import PhantomSized\n\npytestmark = [pytest.mark.external]\n\n\nclass ExclusiveType(int, Exclusive, low=0, high=100): ...\n\n\nclass InclusiveType(float, Inclusive, low=-1, high=1): ...\n\n\nclass ExclusiveInclusiveType(float, ExclusiveInclusive, low=0, high=100): ...\n\n\nclass InclusiveExclusiveType(float, InclusiveExclusive, low=-100, high=0): ...\n\n\nclass MatchType(Match, pattern=r\"^[A-Z]{2}[0-9]{2}$\"): ...\n\n\nclass FullMatchType(FullMatch, pattern=r\"^[A-Z]{2}[0-9]{2}$\"): ...\n\n\nclass OddSize(PhantomSized[int], len=odd): ...\n\n\nclass DataModel(pydantic.BaseModel):\n    exclusive: ExclusiveType\n    inclusive: InclusiveType\n    exclusive_inclusive: ExclusiveInclusiveType\n    inclusive_exclusive: InclusiveExclusiveType\n    negative_int: NegativeInt\n    natural: Natural\n    portion: Portion\n    tz_aware: TZAware\n    tz_naive: TZNaive\n    match: MatchType\n    full_match: FullMatchType\n    non_empty: NonEmpty[int]\n    empty: Empty\n    non_empty_str: NonEmptyStr\n    odd_size: OddSize\n    country: ParsedAlpha2\n    phone_number: PhoneNumber\n    formatted_phone_number: FormattedPhoneNumber\n    sequence_not_str: SequenceNotStr[int]\n\n\nclass TestShippedTypesImplementsSchema:\n    def test_interval_open_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"exclusive\"] == {\n            \"exclusiveMinimum\": 0,\n            \"exclusiveMaximum\": 100,\n            \"description\": \"A value in the exclusive range (0, 100).\",\n            \"title\": \"ExclusiveType\",\n            \"type\": \"integer\",\n        }\n\n    def test_interval_closed_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"inclusive\"] == {\n            \"description\": \"A value in the inclusive range [-1, 1].\",\n            \"minimum\": -1,\n            \"maximum\": 1,\n            \"title\": \"InclusiveType\",\n            \"type\": \"number\",\n        }\n\n    def test_interval_exclusive_inclusive_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"exclusive_inclusive\"] == {\n            \"description\": \"A value in the half-open range (0, 100].\",\n            \"exclusiveMinimum\": 0,\n            \"maximum\": 100,\n            \"title\": \"ExclusiveInclusiveType\",\n            \"type\": \"number\",\n        }\n\n    def test_interval_inclusive_exclusive_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"inclusive_exclusive\"] == {\n            \"title\": \"InclusiveExclusiveType\",\n            \"description\": \"A value in the half-open range [-100, 0).\",\n            \"minimum\": -100,\n            \"exclusiveMaximum\": 0,\n            \"type\": \"number\",\n        }\n\n    def test_interval_negative_int_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"negative_int\"] == {\n            \"title\": \"NegativeInt\",\n            \"maximum\": 0,\n            \"description\": \"An integer value in the inclusive range (-∞, 0].\",\n            \"type\": \"integer\",\n        }\n\n    def test_interval_natural_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"natural\"] == {\n            \"title\": \"Natural\",\n            \"description\": \"An integer value in the inclusive range [0, ∞).\",\n            \"minimum\": 0,\n            \"type\": \"integer\",\n        }\n\n    def test_interval_portion_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"portion\"] == {\n            \"title\": \"Portion\",\n            \"description\": \"A float value in the inclusive range [0, 1].\",\n            \"minimum\": 0,\n            \"maximum\": 1,\n            \"type\": \"number\",\n        }\n\n    def test_tz_aware_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"tz_aware\"] == {\n            \"title\": \"TZAware\",\n            \"description\": \"A date-time with timezone data.\",\n            \"type\": \"string\",\n            \"format\": \"date-time\",\n        }\n\n    def test_tz_naive_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"tz_naive\"] == {\n            \"title\": \"TZNaive\",\n            \"description\": \"A date-time without timezone data.\",\n            \"type\": \"string\",\n            \"format\": \"date-time-naive\",\n        }\n\n    def test_re_match_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"match\"] == {\n            \"title\": \"MatchType\",\n            \"description\": (\n                \"A string starting with a match of the format regular expression.\"\n            ),\n            \"type\": \"string\",\n            \"format\": \"^[A-Z]{2}[0-9]{2}$\",\n        }\n\n    def test_re_full_match_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"full_match\"] == {\n            \"title\": \"FullMatchType\",\n            \"description\": \"A string that matches the format regular expression.\",\n            \"type\": \"string\",\n            \"format\": \"^[A-Z]{2}[0-9]{2}$\",\n        }\n\n    def test_sized_non_empty_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"non_empty\"] == {\n            \"allOf\": [{\"type\": \"integer\"}],\n            \"title\": \"NonEmpty\",\n            \"type\": \"array\",\n            \"description\": \"A non-empty array.\",\n            \"minItems\": 1,\n        }\n\n    def test_sized_empty_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"empty\"] == {\n            \"title\": \"Empty\",\n            \"type\": \"array\",\n            \"description\": \"An empty array.\",\n            \"maxItems\": 0,\n        }\n\n    def test_sized_non_empty_str_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"non_empty_str\"] == {\n            \"title\": \"NonEmptyStr\",\n            \"type\": \"string\",\n            \"description\": \"A non-empty string.\",\n            \"minLength\": 1,\n        }\n\n    def test_phantom_sized_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"odd_size\"] == {\n            \"title\": \"OddSize\",\n            \"type\": \"array\",\n        }\n\n    def test_country_code_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"country\"] == {\n            \"title\": \"Alpha2\",\n            \"description\": \"ISO3166-1 alpha-2 country code\",\n            \"examples\": [\"NR\", \"KZ\", \"ET\", \"VC\", \"AE\", \"NZ\", \"SX\", \"XK\", \"AX\"],\n            \"type\": \"string\",\n            \"format\": \"iso3166-1 alpha-2\",\n        }\n\n    def test_phone_number_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"phone_number\"] == {\n            \"title\": \"PhoneNumber\",\n            \"description\": \"A valid E.164 phone number.\",\n            \"type\": \"string\",\n            \"format\": \"E.164\",\n        }\n\n    def test_formatted_phone_number_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"formatted_phone_number\"] == {\n            \"title\": \"PhoneNumber\",\n            \"description\": \"A valid E.164 phone number.\",\n            \"type\": \"string\",\n            \"format\": \"E.164\",\n        }\n\n    def test_sequence_not_str_implements_schema(self):\n        assert DataModel.schema()[\"properties\"][\"sequence_not_str\"] == {\n            \"title\": \"SequenceNotStr\",\n            \"type\": \"array\",\n            \"items\": {\"type\": \"integer\"},\n        }\n"
  },
  {
    "path": "tests/test_base.py",
    "content": "import sys\nfrom collections.abc import Callable\nfrom dataclasses import dataclass\nfrom typing import Union\n\nimport pytest\n\nfrom phantom import Phantom\nfrom phantom import PhantomMeta\nfrom phantom._base import AbstractInstanceCheck\nfrom phantom._base import MutableType\nfrom phantom._utils.misc import UnresolvedClassAttribute\nfrom phantom.bounds import Parser\nfrom phantom.bounds import get_bound_parser\nfrom phantom.errors import BoundError\nfrom phantom.predicates import boolean\nfrom phantom.predicates.numeric import positive\n\n\nclass TestParseBound:\n    def test_can_parse_simple_bound(self):\n        value = 1\n        assert get_bound_parser(int)(value) is value\n\n    def test_raises_for_invalid_value(self):\n        parser: Parser[int] = get_bound_parser(int)\n        with pytest.raises(\n            BoundError,\n            match=r\"^Value is not within bound of 'int': '1'$\",\n        ):\n            parser(\"1\")\n\n    def test_raises_for_invalid_intersection(self):\n        parser: Parser[float] = get_bound_parser((int, float))\n        with pytest.raises(\n            BoundError,\n            match=r\"^Value is not within bound of 'Intersection\\[int, float\\]': '2'$\",\n        ):\n            parser(\"2\")\n\n    def test_raises_for_invalid_union(self):\n        parser: Parser[int | float] = get_bound_parser(Union[int, float])  # noqa: UP007\n        with pytest.raises(\n            BoundError,\n            match=r\"^Value is not within bound of 'typing\\.Union\\[int, float\\]': '3'$\",\n        ):\n            parser(\"3\")\n\n    @pytest.mark.skipif(sys.version_info < (3, 10), reason=\"requires 3.10+\")\n    def test_raises_for_invalid_pep_604_union(self):\n        parser: Parser[int | float] = get_bound_parser(int | float)\n        with pytest.raises(\n            BoundError,\n            match=r\"^Value is not within bound of 'typing\\.Union\\[int, float\\]': '3'$\",\n        ):\n            parser(\"3\")\n\n    def test_can_parse_intersection(self):\n        class A: ...\n\n        class B: ...\n\n        class C(A, B): ...\n\n        parser: Parser[C] = get_bound_parser((A, B))\n        value = C()\n        assert parser(value) is value\n\n    def test_can_parse_union(self):\n        class A: ...\n\n        class B: ...\n\n        class C(A, B): ...\n\n        parser: Callable[[object], A | B] = get_bound_parser(A | B)\n        a = A()\n        b = B()\n        c = C()\n        assert parser(a) is a\n        assert parser(b) is b\n        assert parser(c) is c\n\n\nclass TestPhantom:\n    def test_subclass_without_predicate_raises(self):\n        with pytest.raises(\n            UnresolvedClassAttribute, match=\"must define class attribute __predicate__\"\n        ):\n\n            class A(Phantom, bound=int): ...\n\n    def test_subclass_without_bound_raises(self):\n        with pytest.raises(\n            UnresolvedClassAttribute, match=\"must define class attribute __bound__\"\n        ):\n\n            class A(Phantom, predicate=positive): ...\n\n    def test_rejects_partial_bound(self):\n        class A(Phantom, predicate=positive, bound=(int, float)): ...\n\n        assert not isinstance(1.0, A)\n\n    def test_concrecte_subclass_of_abstract_raises_for_missing_class_attribute(self):\n        class A(Phantom, bound=int, abstract=True): ...\n\n        with pytest.raises(\n            UnresolvedClassAttribute, match=\"must define class attribute __predicate__\"\n        ):\n\n            class B(A): ...\n\n    def test_can_subclass_without_predicate_if_abstract(self):\n        class A(Phantom, bound=int, abstract=True): ...\n\n    def test_can_subclass_without_bound_if_abstract(self):\n        class A(Phantom, predicate=positive, abstract=True): ...\n\n    def test_subclass_with_incompatible_bounds_raises(self):\n        class A(Phantom, bound=int | float, abstract=True): ...\n\n        with pytest.raises(BoundError):\n\n            class B(A, bound=str, abstract=True): ...\n\n    def test_can_define_bound_implicitly(self):\n        class A(float, Phantom, abstract=True): ...\n\n        assert A.__bound__ is float\n\n    def test_can_define_bound_explicitly(self):\n        class A(Phantom, bound=float, abstract=True): ...\n\n        assert A.__bound__ is float\n\n    def test_can_inherit_bound(self):\n        class A(Phantom, bound=float, abstract=True): ...\n\n        class B(A, abstract=True): ...\n\n        assert B.__bound__ is float\n\n    @pytest.mark.parametrize(\n        \"bound_type\",\n        [\n            list,\n            set,\n            dict,\n            dataclass(type(\"A\", (), {})),\n        ],\n    )\n    def test_raises_mutable_type_for_mutable_bound_type(self, bound_type: type):\n        with pytest.raises(MutableType):\n\n            class A(\n                bound_type,  # type: ignore[misc]\n                Phantom,\n                abstract=True,\n            ): ...\n\n    def test_can_use_frozen_dataclass_as_bound(self):\n        @dataclass(frozen=True)\n        class A: ...\n\n        class B(A, Phantom, predicate=boolean.true): ...\n\n    def test_abstract_instance_check_raises(self):\n        class A(Phantom, bound=float, abstract=True): ...\n\n        with pytest.raises(AbstractInstanceCheck):\n            isinstance(1, A)\n\n    def test_phantom_meta_is_usable_without_phantom_base(self):\n        class Alt(metaclass=PhantomMeta): ...\n\n        assert isinstance(\"a\", Alt) is False\n\n        class AlwaysTrue(metaclass=PhantomMeta):\n            @classmethod\n            def __instancecheck__(self, instance: object) -> bool:\n                return True\n\n        assert isinstance(\"a\", AlwaysTrue) is True\n"
  },
  {
    "path": "tests/test_boolean.py",
    "content": "import pytest\n\nfrom phantom.boolean import Falsy\nfrom phantom.boolean import Truthy\n\nparametrize_truthy = pytest.mark.parametrize(\"v\", (object(), (\"a\",), 1, True))\nparametrize_falsy = pytest.mark.parametrize(\"v\", ((), 0, False))\n\n\nclass TestTruthy:\n    @parametrize_truthy\n    def test_truthy_value_is_instance(self, v):\n        assert isinstance(v, Truthy)\n\n    @parametrize_falsy\n    def test_falsy_value_is_not_instance(self, v):\n        assert not isinstance(v, Truthy)\n\n    @parametrize_truthy\n    def test_instantiation_returns_instance(self, v):\n        assert v is Truthy.parse(v)\n\n    @parametrize_falsy\n    def test_instantiation_raises_for_falsy_value(self, v):\n        with pytest.raises(TypeError):\n            Truthy.parse(v)\n\n\nclass TestFalsy:\n    @parametrize_falsy\n    def test_falsy_value_is_instance(self, v):\n        assert isinstance(v, Falsy)\n\n    @parametrize_truthy\n    def test_truthy_value_is_not_instance(self, v):\n        assert not isinstance(v, Falsy)\n\n    @parametrize_falsy\n    def test_instantiation_returns_instance(self, v):\n        assert v is Falsy.parse(v)\n\n    @parametrize_truthy\n    def test_instantiation_raises_for_truthy_value(self, v):\n        with pytest.raises(TypeError):\n            Falsy.parse(v)\n"
  },
  {
    "path": "tests/test_datetime.py",
    "content": "import datetime\n\nimport pytest\n\nfrom phantom import BoundError\nfrom phantom.datetime import TZAware\nfrom phantom.datetime import TZNaive\nfrom phantom.errors import MissingDependency\n\nparametrize_aware = pytest.mark.parametrize(\n    \"dt\",\n    (\n        datetime.datetime.now(tz=datetime.timezone.utc),\n        datetime.datetime(1969, 12, 23, tzinfo=datetime.timezone.utc),\n        datetime.datetime.min.replace(tzinfo=datetime.timezone.utc),\n        datetime.datetime.max.replace(tzinfo=datetime.timezone.utc),\n    ),\n)\nparametrize_naive = pytest.mark.parametrize(\n    \"dt\",\n    (\n        datetime.datetime.now(),\n        datetime.datetime(1969, 12, 23),\n        datetime.datetime.min,\n        datetime.datetime.max,\n    ),\n)\nparametrize_invalid_type = pytest.mark.parametrize(\n    \"value\",\n    (\n        object(),\n        datetime.time(),\n        datetime.timedelta(),\n        1,\n        1.1,\n        (),\n        # https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh\n        float(\"inf\"),\n        float(\"-inf\"),\n    ),\n)\nparametrize_invalid_str = pytest.mark.parametrize(\n    \"value\",\n    (\n        # https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh\n        \"infinity\",\n        \"inf\",\n        \"-infinity\",\n        \"-inf\",\n        \"2022-13-24\",\n        \"20222-12-24\",\n        \"2022-12-32\",\n        \"foo\",\n    ),\n)\nmin_utc = datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)\nmax_utc = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)\nparametrize_aware_str = pytest.mark.parametrize(\n    \"value, expected\",\n    [\n        (min_utc.isoformat(), min_utc),\n        (max_utc.isoformat(), max_utc),\n        (\n            \"2022-09-24T10:40:20+00:00\",\n            datetime.datetime(2022, 9, 24, 10, 40, 20, 0, tzinfo=datetime.timezone.utc),\n        ),\n        (\n            \"2022-09-24T10:40:20.779388+00:00\",\n            datetime.datetime(\n                2022, 9, 24, 10, 40, 20, 779388, tzinfo=datetime.timezone.utc\n            ),\n        ),\n    ],\n)\nparametrize_naive_str = pytest.mark.parametrize(\n    \"value, expected\",\n    [\n        (datetime.datetime.min.isoformat(), datetime.datetime.min),\n        (datetime.datetime.max.isoformat(), datetime.datetime.max),\n        (\n            \"2022-09-24T10:40:20\",\n            datetime.datetime(2022, 9, 24, 10, 40, 20, 0),\n        ),\n        (\n            \"2022-09-24T10:40:20.779388\",\n            datetime.datetime(2022, 9, 24, 10, 40, 20, 779388),\n        ),\n    ],\n)\n\n\nclass TestTZAware:\n    @parametrize_aware\n    def test_aware_datetime_is_instance(self, dt: datetime.datetime):\n        assert isinstance(dt, TZAware)\n\n    @parametrize_naive\n    def test_naive_datetime_is_not_instance(self, dt: datetime.datetime):\n        assert not isinstance(dt, TZAware)\n\n    @parametrize_naive\n    def test_instantiation_raises_for_naive_datetime_instance(\n        self, dt: datetime.datetime\n    ):\n        with pytest.raises(TypeError):\n            TZAware.parse(dt)\n\n    @parametrize_aware\n    def test_instantiation_returns_instance(self, dt: datetime.datetime):\n        assert dt is TZAware.parse(dt)\n\n    @parametrize_invalid_type\n    def test_parse_rejects_non_str_object(self, value: object):\n        with pytest.raises(BoundError):\n            TZAware.parse(value)\n\n    @pytest.mark.external\n    @parametrize_invalid_str\n    def test_parse_rejects_invalid_str(self, value: object):\n        with pytest.raises(TypeError):\n            TZAware.parse(value)\n\n    @pytest.mark.external\n    @parametrize_naive_str\n    def test_parse_rejects_naive_str(self, value: str, expected: datetime.datetime):\n        with pytest.raises(TypeError):\n            TZAware.parse(value)\n\n    @pytest.mark.external\n    @parametrize_aware_str\n    def test_can_parse_valid_str(self, value: str, expected: datetime.datetime):\n        assert TZAware.parse(value) == expected\n\n    @pytest.mark.no_external\n    @parametrize_aware_str\n    def test_parse_str_without_dateutil_raises_missing_dependency(\n        self,\n        value: str,\n        expected: datetime.datetime,\n    ):\n        with pytest.raises(MissingDependency):\n            TZAware.parse(value)\n\n\nclass TestTZNaive:\n    @parametrize_naive\n    def test_naive_datetime_is_instance(self, dt: datetime.datetime):\n        assert isinstance(dt, TZNaive)\n\n    @parametrize_aware\n    def test_aware_datetime_is_not_instance(self, dt: datetime.datetime):\n        assert not isinstance(dt, TZNaive)\n\n    @parametrize_aware\n    def test_instantiation_raises_for_aware_datetime_instance(\n        self, dt: datetime.datetime\n    ):\n        with pytest.raises(TypeError):\n            TZNaive.parse(dt)\n\n    @parametrize_naive\n    def test_instantiation_returns_instance(self, dt: datetime.datetime):\n        assert dt is TZNaive.parse(dt)\n\n    @parametrize_invalid_type\n    def test_parse_rejects_non_str_object(self, value: object):\n        with pytest.raises(BoundError):\n            TZNaive.parse(value)\n\n    @pytest.mark.external\n    @parametrize_invalid_str\n    def test_parse_rejects_invalid_str(self, value: object):\n        with pytest.raises(TypeError):\n            TZNaive.parse(value)\n\n    @pytest.mark.external\n    @parametrize_aware_str\n    def test_parse_rejects_aware_str(self, value: str, expected: datetime.datetime):\n        with pytest.raises(TypeError):\n            TZNaive.parse(value)\n\n    @pytest.mark.external\n    @parametrize_naive_str\n    def test_can_parse_valid_str(self, value: str, expected: datetime.datetime):\n        assert TZNaive.parse(value) == expected\n\n    @pytest.mark.no_external\n    @parametrize_naive_str\n    def test_parse_str_without_dateutil_raises_missing_dependency(\n        self,\n        value: str,\n        expected: datetime.datetime,\n    ):\n        with pytest.raises(MissingDependency):\n            TZNaive.parse(value)\n"
  },
  {
    "path": "tests/test_datetime.yaml",
    "content": "- case: calling_function_with_unknown_raises\n  main: |\n    import datetime\n    from phantom.datetime import TZAware\n\n    def take_aware(a: TZAware) -> TZAware:\n        return a\n\n    take_aware(datetime.datetime.now()) # E: Argument 1 to \"take_aware\" has incompatible type \"datetime\"; expected \"TZAware\"  [arg-type]\n- case: calling_function_with_known\n  main: |\n    import datetime\n    from phantom.datetime import TZAware\n\n    def take_aware(a: TZAware) -> TZAware:\n        return a\n\n    b = take_aware(\n      TZAware.parse(datetime.datetime.now(tz=datetime.timezone.utc))\n    )\n    reveal_type(b) # N: Revealed type is \"phantom.datetime.TZAware\"\n- case: narrowing_to_tz_aware_makes_tzinfo_non_optional\n  main: |\n    import datetime\n    from phantom.datetime import TZAware\n\n    dt: datetime.datetime\n    reveal_type(dt.tzinfo) # N: Revealed type is \"datetime.tzinfo | None\"\n    assert isinstance(dt, TZAware)\n    reveal_type(dt.tzinfo) # N: Revealed type is \"datetime.tzinfo\"\n- case: bound_is_not_erased\n  main: |\n    import datetime\n    from phantom.datetime import TZAware, TZNaive\n\n    o: datetime.datetime\n    aware = TZAware.parse(o)\n    naive = TZNaive.parse(o)\n\n    reveal_type(aware.month) # N: Revealed type is \"builtins.int\"\n    reveal_type(naive.month) # N: Revealed type is \"builtins.int\"\n"
  },
  {
    "path": "tests/test_fn.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Callable\nfrom collections.abc import Sequence\nfrom functools import partial\nfrom operator import add\nfrom operator import attrgetter\nfrom operator import itemgetter\nfrom operator import mul\nfrom typing import TypeVar\n\nimport pytest\n\nfrom phantom import Predicate\nfrom phantom.fn import _name\nfrom phantom.fn import compose2\nfrom phantom.fn import excepts\nfrom phantom.predicates.boolean import both\nfrom phantom.predicates.collection import count\nfrom phantom.predicates.collection import every\nfrom phantom.predicates.generic import equal\n\n\nclass Test_name:\n    class Nested:\n        def method(self): ...\n\n    @pytest.mark.parametrize(\n        \"function, expected\",\n        [\n            (lambda: None, \"Test_name.<lambda>\"),\n            (partial(int), \"int\"),\n            (Nested.method, \"Test_name.Nested.method\"),\n            (attrgetter(\"attrib\"), \"operator.attrgetter('attrib')\"),\n            (itemgetter(\"key\"), \"operator.itemgetter('key')\"),\n        ],\n    )\n    def test_can_get_name_of(self, function: Callable, expected: str) -> None:\n        assert _name(function) == expected\n\n\ndef reversed_str(value: str) -> str:\n    return \"\".join(reversed(value))\n\n\nAA = TypeVar(\"AA\")\nAR = TypeVar(\"AR\")\nBA = TypeVar(\"BA\")\n\n\nclass TestCompose2:\n    @pytest.mark.parametrize(\n        \"a, b, argument, expected, name\",\n        [\n            (str.title, reversed_str, \"test\", \"Tset\", \"str.title∘reversed_str\"),\n            (reversed_str, str.title, \"test\", \"tseT\", \"reversed_str∘str.title\"),\n            (partial(add, 7), partial(mul, 3), 5, 22, \"add∘mul\"),\n            (partial(mul, 3), partial(add, 7), 5, 36, \"mul∘add\"),\n        ],\n    )\n    def test_can_compose_two(\n        self,\n        a: Callable[[AA], AR],\n        b: Callable[[BA], AA],\n        argument: BA,\n        expected: AR,\n        name: str,\n    ) -> None:\n        composed = compose2(a, b)\n        assert composed(argument) == expected\n        assert composed.__name__ == name\n\n    def test_can_compose_complex_predicate(self) -> None:\n        as_parts: Callable[[str], list[str]] = partial(str.split, sep=\".\")\n        is_valid_parts: Predicate[Sequence[str]] = both(\n            count(equal(3)),\n            every(str.isidentifier),\n        )\n        is_valid_name = compose2(is_valid_parts, as_parts)\n        assert is_valid_name(\"three.part.name\") is True\n        assert is_valid_name(\"two.parts\") is False\n        assert is_valid_name(\"not identifier.not.valid\") is False\n        assert is_valid_name(\"\") is False\n\n\nclass BaseError(Exception): ...\n\n\nclass Error(BaseError): ...\n\n\nclass ErrorA(Error): ...\n\n\nclass ErrorB(Error): ...\n\n\ndef dummy_function(val: type[Exception]) -> None:\n    if val is not None:\n        raise val\n\n\nclass TestExcepts:\n    @pytest.mark.parametrize(\n        \"function, argument, return_value\",\n        [\n            (excepts(Error)(dummy_function), ErrorA, False),\n            (excepts((ErrorA, ErrorB))(dummy_function), ErrorA, False),\n            (excepts((ErrorA, ErrorB))(dummy_function), None, True),\n            (excepts((ErrorA, ErrorB), negate=True)(dummy_function), None, False),\n            (excepts(Error, negate=True)(dummy_function), ErrorB, True),\n        ],\n    )\n    def test_returns_bool(\n        self,\n        function: Callable,\n        argument: object,\n        return_value: bool,\n    ) -> None:\n        assert function(argument) is return_value\n\n    def test_reraises(self) -> None:\n        with pytest.raises(BaseError):\n            excepts(Error)(dummy_function)(BaseError)\n"
  },
  {
    "path": "tests/test_fn.yaml",
    "content": "- case: test_compose2_can_infer_types\n  disable_cache: true\n  regex: true\n  main: |\n    from typing import Callable\n    from phantom import Predicate\n    from phantom.fn import compose2\n    from predicates import is_valid_name\n\n    reveal_type(is_valid_name)\n    is_valid_name(1)\n    is_valid_name([])\n    reveal_type(is_valid_name(\"\"))\n    def takes_str_predicate(predicate: Predicate[str]) -> None: ...\n    takes_str_predicate(is_valid_name)\n\n    reveal_type(compose2(int, str)(2))\n    def takes_int_str_composed(fn: Callable[[int], int]) -> None: ...\n    takes_int_str_composed(compose2(int, str))\n\n    reveal_type(compose2(str, int)(\"3\"))\n    def takes_str_int_composed(fn: Callable[[str], str]) -> None: ...\n    takes_str_int_composed(compose2(str, int))\n  out: |\n    main:6: note: Revealed type is \"def \\(builtins\\.str\\*?\\) -> builtins\\.bool\\*?\"\n    main:7: error: Argument 1 has incompatible type \"int\"; expected \"str\"  \\[arg-type\\]\n    main:8: error: Argument 1 has incompatible type \"list\\[Never\\]\"; expected \"str\"  \\[arg-type\\]\n    main:9: note: Revealed type is \"builtins.bool\\*?\"\n    main:13: note: Revealed type is \"builtins.int\\*?\"\n    main:17: note: Revealed type is \"builtins.str\\*?\"\n  files:\n    - path: predicates.py\n      content: |\n        from __future__ import annotations\n        from functools import partial\n        from typing import Callable\n        from typing import Sequence\n        from phantom import Predicate\n        from phantom.fn import compose2\n        from phantom.predicates.boolean import both\n        from phantom.predicates.collection import count\n        from phantom.predicates.collection import every\n        from phantom.predicates.generic import equal\n\n        as_parts: Callable[[str], list[str]] = partial(str.split, sep=\".\")\n        is_valid_parts: Predicate[Sequence[str]] = both(\n          count(equal(3)),\n          every(str.isidentifier),\n        )\n        is_valid_name = compose2(is_valid_parts, as_parts)\n"
  },
  {
    "path": "tests/test_intersection.yaml",
    "content": "- case: asserting_twice_results_in_intersection_type\n  main: |\n    from phantom.interval import Portion, Exclusive\n\n    class Positive(float, Exclusive, min=0): ...\n\n    n = .5\n\n    assert isinstance(n, Positive)\n    assert isinstance(n, Portion)\n\n    reveal_type(n)  # N: Revealed type is \"main.<subclass of \"main.Positive\" and \"phantom.interval.Portion\">\"\n"
  },
  {
    "path": "tests/test_interval.py",
    "content": "from __future__ import annotations\n\nfrom decimal import Decimal\nfrom functools import total_ordering\n\nimport pytest\n\nfrom phantom.interval import Inclusive\nfrom phantom.interval import Interval\nfrom phantom.interval import Natural\nfrom phantom.interval import NegativeInt\nfrom phantom.interval import Portion\nfrom phantom.interval import _get_scalar_float_bounds\nfrom phantom.interval import _get_scalar_int_bounds\nfrom phantom.interval import _NonScalarBounds\nfrom phantom.predicates import interval\n\nfrom .types import FloatInc\nfrom .types import FloatIncExc\nfrom .types import IntExc\nfrom .types import IntExcInc\n\n\nclass TestInterval:\n    def test_subclassing_without_check_raises(self):\n        with pytest.raises(TypeError, match=\"A must define an interval check$\"):\n\n            class A(Interval, abstract=False): ...\n\n    def test_parse_coerces_str(self):\n        class Great(int, Inclusive, low=10): ...\n\n        assert Great.parse(\"10\") == 10\n\n    def test_allows_decimal_bound(self):\n        class A(\n            Decimal,\n            Interval,\n            check=interval.exclusive,\n            low=Decimal(\"1.15\"),\n            high=Decimal(\"2.36\"),\n        ): ...\n\n        assert not isinstance(2, A)\n        assert not isinstance(1.98, A)\n        assert isinstance(Decimal(\"1.98\"), A)\n\n    def test_subclass_inherits_bounds(self):\n        class A(int, Inclusive, low=-10, high=10): ...\n\n        class B(A): ...\n\n        assert B.__check__ is A.__check__\n        assert isinstance(-10, B)\n        assert isinstance(10, B)\n        assert not isinstance(-11, B)\n        assert not isinstance(11, B)\n\n        class C(A, low=0): ...\n\n        assert C.__check__ is A.__check__\n        assert isinstance(0, C)\n        assert isinstance(10, C)\n        assert not isinstance(-1, C)\n        assert not isinstance(11, C)\n\n        class D(A, high=0): ...\n\n        assert D.__check__ is A.__check__\n        assert isinstance(-10, D)\n        assert isinstance(0, D)\n        assert not isinstance(-11, D)\n        assert not isinstance(1, D)\n\n\nparametrize_negative_ints = pytest.mark.parametrize(\"i\", (-10, -1, -0, +0))\nparametrize_positive_ints = pytest.mark.parametrize(\"i\", (10, 1, +0, -0))\n\n\nclass TestNegativeInt:\n    @parametrize_negative_ints\n    def test_negative_int_is_instance(self, i):\n        assert isinstance(i, NegativeInt)\n\n    def test_positive_int_is_not_instance(self):\n        assert not isinstance(1, NegativeInt)\n        assert not isinstance(10, NegativeInt)\n\n    def test_instantiation_raises_for_positive_int(self):\n        with pytest.raises(TypeError):\n            NegativeInt.parse(1)\n        with pytest.raises(TypeError):\n            NegativeInt.parse(10)\n\n    @parametrize_negative_ints\n    def test_instantiation_returns_instance(self, i):\n        assert i is NegativeInt.parse(i)\n\n\nclass TestNatural:\n    @parametrize_positive_ints\n    def test_positive_int_is_instance(self, i):\n        assert isinstance(i, Natural)\n\n    def test_negative_int_is_not_instance(self):\n        assert not isinstance(-1, Natural)\n        assert not isinstance(-10, Natural)\n\n    def test_instantiation_raises_for_positive_int(self):\n        with pytest.raises(TypeError):\n            Natural.parse(-1)\n        with pytest.raises(TypeError):\n            Natural.parse(-10)\n        with pytest.raises(TypeError):\n            Natural(-1)\n\n    @parametrize_positive_ints\n    def test_instantiation_returns_instance(self, i):\n        assert i is Natural.parse(i)\n        assert i is Natural(i)\n\n\nparametrize_portion_values = pytest.mark.parametrize(\n    \"i\", (0.0, 1.0, -0.0, +1.0, 0.8652559794322651)\n)\n# TODO: Use math.nextafter on Python 3.9 to test as close to limit as possible\nparametrize_non_portion_values = pytest.mark.parametrize(\"i\", (-1, -0.1, 1.1, 2))\n\n\nclass TestPortion:\n    @parametrize_portion_values\n    def test_value_inside_range_is_instance(self, i):\n        assert isinstance(i, Portion)\n\n    @parametrize_non_portion_values\n    def test_value_outside_range_is_instance(self, i):\n        assert not isinstance(i, Portion)\n\n    @parametrize_portion_values\n    def test_instantiation_returns_instance(self, i):\n        assert i is Portion.parse(i)\n        assert i is Portion(i)\n\n    @parametrize_non_portion_values\n    def test_instantiation_raises_for_non_portion_values(self, i):\n        with pytest.raises(TypeError):\n            Portion.parse(i)\n        with pytest.raises(TypeError):\n            Portion(i)\n\n\nclass TestGetScalarIntBounds:\n    @pytest.mark.parametrize(\n        (\"type_\", \"exclude_min\", \"exclude_max\", \"expected_low\", \"expected_high\"),\n        (\n            (FloatInc, False, False, 0, 100),\n            (IntExc, True, True, 1, 99),\n            (IntExcInc, True, False, 1, 100),\n            (FloatIncExc, False, True, 0, 99),\n            (Natural, False, False, 0, None),\n            (NegativeInt, False, False, None, 0),\n        ),\n    )\n    def test_returns_correct_bounds(\n        self,\n        type_: type[Interval],\n        exclude_min: bool,\n        exclude_max: bool,\n        expected_low: int | None,\n        expected_high: int | None,\n    ):\n        (low, high) = _get_scalar_int_bounds(type_, exclude_min, exclude_max)\n        assert low == expected_low\n        assert high == expected_high\n\n    def test_raises_non_scalar_bounds_for_non_int_lower_bound(self):\n        @total_ordering\n        class Inf:\n            def __eq__(self, other):\n                return False\n\n            def __lt__(self, other):\n                return False\n\n        class Int(int, Inclusive, low=Inf(), high=100): ...\n\n        with pytest.raises(_NonScalarBounds):\n            _get_scalar_int_bounds(Int)\n\n    def test_raises_non_scalar_bounds_for_non_int_upper_bound(self):\n        @total_ordering\n        class Inf:\n            def __eq__(self, other):\n                return False\n\n            def __lt__(self, other):\n                return False\n\n        class Int(int, Inclusive, low=0, high=Inf()): ...\n\n        with pytest.raises(_NonScalarBounds):\n            _get_scalar_int_bounds(Int)\n\n\nclass TestGetScalarFloatBounds:\n    @pytest.mark.parametrize(\n        (\"type_\", \"expected_low\", \"expected_high\"),\n        (\n            (FloatInc, 0, 100),\n            (Natural, 0, None),\n            (NegativeInt, None, 0),\n            (Portion, 0, 1),\n        ),\n    )\n    def test_returns_correct_bounds(\n        self,\n        type_: type[Interval],\n        expected_low: int,\n        expected_high: int,\n    ):\n        (low, high) = _get_scalar_float_bounds(type_)\n        assert low == expected_low\n        assert high == expected_high\n\n    def test_raises_non_scalar_bounds_for_non_int_lower_bound(self):\n        @total_ordering\n        class Inf:\n            def __eq__(self, other):\n                return False\n\n            def __lt__(self, other):\n                return False\n\n        class Int(float, Inclusive, low=Inf(), high=100): ...\n\n        with pytest.raises(_NonScalarBounds):\n            _get_scalar_float_bounds(Int)\n\n    def test_raises_non_scalar_bounds_for_non_int_upper_bound(self):\n        @total_ordering\n        class Inf:\n            def __eq__(self, other):\n                return False\n\n            def __lt__(self, other):\n                return False\n\n        class Int(float, Inclusive, low=0, high=Inf()): ...\n\n        with pytest.raises(_NonScalarBounds):\n            _get_scalar_float_bounds(Int)\n"
  },
  {
    "path": "tests/test_interval.yaml",
    "content": "- case: calling_function_with_unknown_raises\n  main: |\n    from phantom.interval import Natural\n\n    def take_nat(a: Natural) -> Natural:\n        return a\n\n    take_nat(1) # E: Argument 1 to \"take_nat\" has incompatible type \"int\"; expected \"Natural\"  [arg-type]\n\n- case: calling_function_with_known_1\n  main: |\n    from phantom.interval import Natural\n\n    def take_nat(a: Natural) -> Natural:\n        return a\n\n    b = take_nat(Natural.parse(1))\n    reveal_type(b) # N: Revealed type is \"phantom.interval.Natural\"\n\n- case: calling_function_with_known_2\n  main: |\n    from phantom.interval import Natural\n\n    def from_int(a: int) -> Natural:\n        if not isinstance(a, Natural):\n            raise TypeError\n        reveal_type(a) # N: Revealed type is \"phantom.interval.Natural\"\n        return a\n\n    n = from_int(-1)\n    reveal_type(n) # N: Revealed type is \"phantom.interval.Natural\"\n\n- case: test_overload\n  main: |\n    from typing import overload\n    from phantom.interval import Natural, NegativeInt\n\n    @overload\n    def add(a: Natural, b: Natural) -> Natural: ...\n    @overload\n    def add(a: NegativeInt, b: NegativeInt) -> NegativeInt: ...\n    @overload\n    def add(a: int, b: int) -> int: ...\n    def add(a: int, b: int) -> int:\n        return a + b\n\n    a = Natural.parse(1)\n    b = NegativeInt.parse(-1)\n\n    reveal_type(add(a, a)) # N: Revealed type is \"phantom.interval.Natural\"\n    reveal_type(add(b, b)) # N: Revealed type is \"phantom.interval.NegativeInt\"\n    reveal_type(add(a, b)) # N: Revealed type is \"builtins.int\"\n    reveal_type(add(b, a)) # N: Revealed type is \"builtins.int\"\n"
  },
  {
    "path": "tests/test_iso3166.py",
    "content": "import pytest\n\nfrom phantom.iso3166 import InvalidCountryCode\nfrom phantom.iso3166 import ParsedAlpha2\nfrom phantom.iso3166 import normalize_alpha2_country_code\n\n\nclass TestNormalizeAlpha2CountryCode:\n    @pytest.mark.parametrize(\n        \"given, normalized\",\n        ((\"sE\", \"SE\"), (\"dk\", \"DK\"), (\"IE\", \"IE\")),\n    )\n    def test_normalizes_mixed_case_valid_country_code(\n        self, given: str, normalized: str\n    ) -> None:\n        assert normalize_alpha2_country_code(given) == normalized\n\n    @pytest.mark.parametrize(\"invalid\", (\"UK\", \"not a country code\"))\n    def test_raises_for_invalid_country_code(self, invalid: str) -> None:\n        with pytest.raises(InvalidCountryCode):\n            normalize_alpha2_country_code(invalid)\n\n\nclass TestAlpha2:\n    @pytest.mark.parametrize(\"invalid\", (\"SP\", \"DA\", \"AV\", 1))\n    def test_invalid_country_code_is_not_instance(self, invalid: object) -> None:\n        assert not isinstance(invalid, ParsedAlpha2)\n\n    @pytest.mark.parametrize(\"country_code\", (\"PS\", \"AD\", \"VA\"))\n    def test_valid_country_code_is_instance(self, country_code: str) -> None:\n        assert isinstance(country_code, ParsedAlpha2)\n\n    def test_normalizes_valid_country_code(self) -> None:\n        country_code = ParsedAlpha2.parse(\"ps\")\n        assert country_code == \"PS\"\n        assert isinstance(country_code, ParsedAlpha2)\n\n    @pytest.mark.parametrize(\"invalid\", (\"SP\", \"DA\", \"AV\"))\n    def test_raises_for_invalid_country_code(self, invalid: str) -> None:\n        with pytest.raises(InvalidCountryCode):\n            ParsedAlpha2.parse(invalid)\n"
  },
  {
    "path": "tests/test_iso3166.yaml",
    "content": "- case: can_instantiate\n  main: |\n    from phantom.iso3166 import Alpha2, ParsedAlpha2\n\n    def takes_country_code(a: Alpha2) -> None:\n        ...\n\n    takes_country_code(ParsedAlpha2.parse(\"\"))\n- case: can_use_literal\n  main: |\n    from phantom.iso3166 import Alpha2\n\n    def takes_country_code(a: Alpha2) -> None:\n        ...\n\n    takes_country_code(\"FR\")\n- case: can_infer\n  main: |\n    from phantom.iso3166 import ParsedAlpha2, Alpha2\n\n    def takes_country_code(a: Alpha2) -> None:\n        ...\n\n    a = \"\"\n    assert isinstance(a, ParsedAlpha2)\n    takes_country_code(a)\n- case: bound_is_not_subtype\n  main: |\n    from phantom.iso3166 import CountryCode\n\n    def takes_country_code(a: CountryCode) -> None:\n        ...\n\n    takes_country_code(\"fr\")\n  out: |\n    main:6: error: Argument 1 to \"takes_country_code\" has incompatible type \"Literal['fr']\"; expected \"Literal['AF', 'AX', 'AL', 'DZ', 'AS', 'AD', 'AO', 'AI', 'AQ', 'AG', 'AR', 'AM', 'AW', 'AU', 'AT', 'AZ', 'BS', 'BH', 'BD', 'BB', 'BY', 'BE', 'BZ', 'BJ', 'BM', 'BT', 'BO', 'BQ', 'BA', 'BW', 'BV', 'BR', 'IO', 'BN', 'BG', 'BF', 'BI', 'KH', 'CM', 'CA', 'CV', 'KY', 'CF', 'TD', 'CL', 'CN', 'CX', 'CC', 'CO', 'KM', 'CG', 'CD', 'CK', 'CR', 'CI', 'HR', 'CU', 'CW', 'CY', 'CZ', 'DK', 'DJ', 'DM', 'DO', 'EC', 'EG', 'SV', 'GQ', 'ER', 'EE', 'ET', 'FK', 'FO', 'FJ', 'FI', 'FR', 'GF', 'PF', 'TF', 'GA', 'GM', 'GE', 'DE', 'GH', 'GI', 'GR', 'GL', 'GD', 'GP', 'GU', 'GT', 'GG', 'GN', 'GW', 'GY', 'HT', 'HM', 'VA', 'HN', 'HK', 'HU', 'IS', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IM', 'IL', 'IT', 'JM', 'JP', 'JE', 'JO', 'KZ', 'KE', 'KI', 'KP', 'KR', 'XK', 'KW', 'KG', 'LA', 'LV', 'LB', 'LS', 'LR', 'LY', 'LI', 'LT', 'LU', 'MO', 'MK', 'MG', 'MW', 'MY', 'MV', 'ML', 'MT', 'MH', 'MQ', 'MR', 'MU', 'YT', 'MX', 'FM', 'MD', 'MC', 'MN', 'ME', 'MS', 'MA', 'MZ', 'MM', 'NA', 'NR', 'NP', 'NL', 'NC', 'NZ', 'NI', 'NE', 'NG', 'NU', 'NF', 'MP', 'NO', 'OM', 'PK', 'PW', 'PS', 'PA', 'PG', 'PY', 'PE', 'PH', 'PN', 'PL', 'PT', 'PR', 'QA', 'RE', 'RO', 'RU', 'RW', 'BL', 'SH', 'KN', 'LC', 'MF', 'PM', 'VC', 'WS', 'SM', 'ST', 'SA', 'SN', 'RS', 'SC', 'SL', 'SG', 'SX', 'SK', 'SI', 'SB', 'SO', 'ZA', 'GS', 'SS', 'ES', 'LK', 'SD', 'SR', 'SJ', 'SZ', 'SE', 'CH', 'SY', 'TW', 'TJ', 'TZ', 'TH', 'TL', 'TG', 'TK', 'TO', 'TT', 'TN', 'TR', 'TM', 'TC', 'TV', 'UG', 'UA', 'AE', 'GB', 'US', 'UM', 'UY', 'UZ', 'VU', 'VE', 'VN', 'VG', 'VI', 'WF', 'EH', 'YE', 'ZM', 'ZW'] | ParsedAlpha2\"  [arg-type]\n"
  },
  {
    "path": "tests/test_negated.py",
    "content": "from typing import get_args\nfrom typing import get_origin\n\nimport pytest\n\nfrom phantom.negated import SequenceNotStr\n\nparametrize_instances = pytest.mark.parametrize(\n    \"value\",\n    (\n        (\"foo\", \"bar\", \"baz\"),\n        (1, 2, object()),\n        (b\"hello\", b\"there\"),\n        [],\n        [\"foo\"],\n        [b\"bar\"],\n    ),\n)\nparametrize_non_instances = pytest.mark.parametrize(\n    \"value\",\n    (\n        \"\",\n        \"foo\",\n        object(),\n        b\"\",\n        b\"foo\",\n        {},\n        set(),\n        frozenset(),\n    ),\n)\n\n\nclass TestSequenceNotStr:\n    @parametrize_instances\n    def test_is_instance(self, value: object):\n        assert isinstance(value, SequenceNotStr)\n\n    @parametrize_non_instances\n    def test_is_not_instance(self, value: object):\n        assert not isinstance(value, SequenceNotStr)\n\n    @parametrize_instances\n    def test_parse_returns_instance(self, value: object):\n        assert SequenceNotStr.parse(value) is value\n\n    @parametrize_non_instances\n    def test_parse_raises_for_non_instances(self, value: object):\n        with pytest.raises(TypeError):\n            SequenceNotStr.parse(value)\n\n    def test_subscription_returns_type_alias(self):\n        alias = SequenceNotStr[str]\n        assert get_origin(alias) is SequenceNotStr\n        (arg,) = get_args(alias)\n        assert arg is str\n"
  },
  {
    "path": "tests/test_negated.yaml",
    "content": "- case: test_subscripted\n  main: |\n    from phantom.negated import SequenceNotStr\n\n    def greet(names: SequenceNotStr[str]) -> str:\n        return f\"Hello {', '.join(names)}\"\n\n    seq = SequenceNotStr[str].parse((\"Jane\", \"Joe\"))\n    greeting = greet(seq)\n    reveal_type(greeting)  # N: Revealed type is \"builtins.str\"\n\n- case: test_annotated\n  main: |\n    from phantom.negated import SequenceNotStr\n\n    def greet(names: SequenceNotStr[str]) -> str:\n        return f\"Hello {', '.join(names)}\"\n\n    seq: SequenceNotStr[str] = SequenceNotStr.parse((\"Jane\", \"Joe\"))\n    greeting = greet(seq)\n    reveal_type(greeting)  # N: Revealed type is \"builtins.str\"\n\n- case: test_unspecified_inner_type\n  main: |\n    from phantom.negated import SequenceNotStr\n    from typing import Tuple\n\n    def fn(values: SequenceNotStr) -> SequenceNotStr[object]:\n        return values\n\n    seq: Tuple[object, ...] = (1, 2, object())\n    assert isinstance(seq, SequenceNotStr)\n    reveal_type(seq)  # N: Revealed type is \"main.<subclass of \"tuple[builtins.object, ...]\" and \"phantom.negated.SequenceNotStr[Any]\">\"\n    values = fn(seq)\n    reveal_type(values)  # N: Revealed type is \"phantom.negated.SequenceNotStr[builtins.object]\"\n"
  },
  {
    "path": "tests/test_re.py",
    "content": "from __future__ import annotations\n\nimport re\n\nimport pytest\n\nfrom phantom.re import FullMatch\nfrom phantom.re import Match\n\n\nclass MatchPatternInstance(Match, pattern=re.compile(r\"abc\")): ...\n\n\nclass MatchPatternStr(Match, pattern=r\"abc\"): ...\n\n\nparametrize_match = pytest.mark.parametrize(\n    \"match_type\", (MatchPatternInstance, MatchPatternStr)\n)\n\n\nclass TestMatch:\n    @parametrize_match\n    def test_non_matching_string_is_not_instance(self, match_type: type[Match]):\n        assert not isinstance(\"abd\", match_type)\n\n    @parametrize_match\n    def test_matching_string_is_instance(self, match_type: type[Match]):\n        assert isinstance(\"abcd\", match_type)\n\n    @parametrize_match\n    def test_instantiation_raises_for_non_matching_string(\n        self, match_type: type[Match]\n    ):\n        with pytest.raises(TypeError):\n            match_type.parse(\"b\")\n\n    @parametrize_match\n    def test_instantiation_returns_instance(self, match_type: type[Match]):\n        s = \"abc\"\n        assert s is match_type.parse(s)\n\n\nclass FullMatchPatternInstance(FullMatch, pattern=re.compile(r\"abc\")): ...\n\n\nclass FullMatchStr(FullMatch, pattern=r\"abc\"): ...\n\n\nparametrize_full_match = pytest.mark.parametrize(\n    \"full_match_type\", (FullMatchPatternInstance, FullMatchStr)\n)\n\n\nclass TestFullMatch:\n    @parametrize_full_match\n    def test_non_matching_string_is_not_instance(\n        self, full_match_type: type[FullMatch]\n    ):\n        assert not isinstance(\"abcd\", full_match_type)\n\n    @parametrize_full_match\n    def test_matching_string_is_instance(self, full_match_type: type[FullMatch]):\n        assert isinstance(\"abc\", full_match_type)\n\n    @parametrize_full_match\n    def test_instantiation_raises_for_non_matching_string(\n        self, full_match_type: type[FullMatch]\n    ):\n        with pytest.raises(TypeError):\n            full_match_type.parse(\"b\")\n\n    @parametrize_full_match\n    def test_instantiation_returns_instance(self, full_match_type: type[FullMatch]):\n        s = \"abc\"\n        assert s is full_match_type.parse(s)\n"
  },
  {
    "path": "tests/test_re.yaml",
    "content": "- case: str_is_not_subtype_of_match\n  main: |\n    import re\n    from phantom.re import Match\n\n    class A(Match, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    take_a(\"c\") # E: Argument 1 to \"take_a\" has incompatible type \"str\"; expected \"A\"  [arg-type]\n- case: str_is_not_subtype_of_full_match\n  main: |\n    import re\n    from phantom.re import FullMatch\n\n    class A(FullMatch, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    take_a(\"c\") # E: Argument 1 to \"take_a\" has incompatible type \"str\"; expected \"A\"  [arg-type]\n- case: can_instantiate_match\n  main: |\n    import re\n    from phantom.re import Match\n\n    class A(Match, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    a = take_a(A.parse(\"abc\"))\n    reveal_type(a)  # N: Revealed type is \"main.A\"\n- case: can_instantiate_full_match\n  main: |\n    import re\n    from phantom.re import FullMatch\n\n    class A(FullMatch, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    a = take_a(A.parse(\"abc\"))\n    reveal_type(a)  # N: Revealed type is \"main.A\"\n- case: can_infer_match\n  main: |\n    import re\n    from phantom.re import Match\n\n    class A(Match, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    a = \"abc\"\n    assert isinstance(a, A)\n    a = take_a(a)\n- case: can_infer_full_match\n  main: |\n    import re\n    from phantom.re import FullMatch\n\n    class A(FullMatch, pattern=re.compile(r\"^abc$\")):\n        ...\n\n    def take_a(a: A) -> A:\n        return a\n\n    a = \"abc\"\n    assert isinstance(a, A)\n    a = take_a(a)\n"
  },
  {
    "path": "tests/test_sized.py",
    "content": "from dataclasses import dataclass\nfrom typing import Final\nfrom typing import Generic\nfrom typing import TypeVar\nfrom typing import get_args\nfrom typing import get_origin\n\nimport pytest\n\nfrom phantom.predicates.numeric import odd\nfrom phantom.sized import Empty\nfrom phantom.sized import LSPViolation\nfrom phantom.sized import NonEmpty\nfrom phantom.sized import NonEmptyStr\nfrom phantom.sized import PhantomBound\nfrom phantom.sized import PhantomSized\nfrom phantom.sized import UnresolvedBounds\n\n\n@dataclass\nclass MutableDataclass:\n    value: str = \"foo\"\n\n\nparametrize_non_empty: Final = pytest.mark.parametrize(\n    \"container\",\n    ((1,), frozenset({1}), \"foo\"),\n)\nparametrize_empty: Final = pytest.mark.parametrize(\n    \"container\",\n    ((), frozenset(), \"\"),\n)\nparametrize_mutable: Final = pytest.mark.parametrize(\n    \"container\",\n    ([], [1], set(), {1}, {}, {1: 2}, MutableDataclass()),\n)\n\n\nT = TypeVar(\"T\")\n\n\nclass OddSize(PhantomSized[T], Generic[T], len=odd): ...\n\n\nclass TestPhantomSized:\n    odd_length = pytest.mark.parametrize(\n        \"container\",\n        [\n            (1,),\n            (1, 2, 3),\n            (1, 2, 3, 4, 5),\n            frozenset({1}),\n            frozenset({1, 2, 3, 4, 5}),\n        ],\n    )\n    even_length = pytest.mark.parametrize(\n        \"container\",\n        [\n            (),\n            (1, 1),\n            (1, 1, 1, 2, 3, 6),\n            frozenset(),\n            frozenset({1, 2}),\n        ],\n    )\n\n    @odd_length\n    def test_value_fulfilling_predicate_is_instance(self, container: object):\n        assert isinstance(container, OddSize)\n\n    @even_length\n    def test_value_not_fulfilling_predicate_is_not_instance(self, container: object):\n        assert not isinstance(container, OddSize)\n\n    @even_length\n    def test_instantiation_raises_for_invalid_length(self, container: object):\n        with pytest.raises(TypeError):\n            OddSize.parse(container)\n\n    @parametrize_mutable\n    def test_instantiation_raises_for_mutable(self, container: object):\n        with pytest.raises(TypeError):\n            OddSize.parse(container)\n\n    @parametrize_non_empty\n    def test_instantiation_returns_instance(self, container: object):\n        assert container is OddSize.parse(container)\n\n    def test_subscription_returns_type_alias(self):\n        alias = OddSize[tuple]\n        assert get_origin(alias) is OddSize\n        (arg,) = get_args(alias)\n        assert arg is tuple\n\n\nclass Tens(PhantomBound[T], Generic[T], min=10, max=19): ...\n\n\nclass TestPhantomBound:\n    valid = pytest.mark.parametrize(\n        \"container\",\n        [10 * (1,), 19 * (2,)],\n    )\n    invalid = pytest.mark.parametrize(\n        \"container\",\n        [9 * (1,), 20 * (2,)],\n    )\n\n    @valid\n    def test_value_fulfilling_predicate_is_instance(self, container: object):\n        assert isinstance(container, Tens)\n\n    @invalid\n    def test_value_not_fulfilling_predicate_is_not_instance(self, container: object):\n        assert not isinstance(container, Tens)\n\n    @invalid\n    def test_instantiation_raises_for_invalid_length(self, container: object):\n        with pytest.raises(TypeError):\n            Tens.parse(container)\n\n    @parametrize_mutable\n    def test_instantiation_raises_for_mutable(self, container: object):\n        with pytest.raises(TypeError):\n            Tens.parse(container)\n\n    @valid\n    def test_instantiation_returns_instance(self, container: object):\n        assert container is Tens.parse(container)\n\n    def test_subscription_returns_type_alias(self):\n        alias = Tens[tuple]\n        assert get_origin(alias) is Tens\n        (arg,) = get_args(alias)\n        assert arg is tuple\n\n    def test_raises_lsp_violation_when_attempting_to_decrease_min(self):\n        with pytest.raises(LSPViolation):\n\n            class Lower(Tens, min=9): ...\n\n    def test_raises_lsp_violation_when_attempting_to_increase_max(self):\n        with pytest.raises(LSPViolation):\n\n            class Higher(Tens, max=20): ...\n\n    def test_can_narrow_range_in_subclass(self):\n        class Fewer(Tens, min=11, max=18): ...\n\n        assert isinstance(11 * (0,), Fewer)\n        assert isinstance(18 * (0,), Fewer)\n        assert not isinstance(10 * (0,), Fewer)\n        assert not isinstance(19 * (0,), Fewer)\n\n    def test_abstract_subclass_can_omit_bounds(self):\n        class A(PhantomBound, abstract=True): ...\n\n        class B(A, min=10, max=20): ...\n\n        assert B.__min__ == 10\n        assert B.__max__ == 20\n\n    def test_raises_unresolved_bounds_when_concrete_subclass_omits_bounds(self):\n        with pytest.raises(UnresolvedBounds):\n\n            class A(PhantomBound): ...\n\n\nclass TestNonEmpty:\n    @parametrize_non_empty\n    def test_non_empty_container_is_instance(self, container):\n        assert isinstance(container, NonEmpty)\n\n    @parametrize_empty\n    def test_empty_container_is_instance(self, container):\n        assert not isinstance(container, NonEmpty)\n\n    @parametrize_empty\n    def test_instantiation_raises_for_empty_container(self, container):\n        with pytest.raises(TypeError):\n            NonEmpty.parse(container)\n\n    @parametrize_mutable\n    def test_instantiation_raises_for_mutable(self, container):\n        with pytest.raises(TypeError):\n            NonEmpty.parse(container)\n\n    @parametrize_non_empty\n    def test_instantiation_returns_instance(self, container):\n        assert container is NonEmpty.parse(container)\n\n    def test_subscription_returns_type_alias(self):\n        alias = NonEmpty[tuple]\n        assert get_origin(alias) is NonEmpty\n        (arg,) = get_args(alias)\n        assert arg is tuple\n\n\nclass TestEmpty:\n    @parametrize_non_empty\n    def test_non_empty_container_is_instance(self, container):\n        assert not isinstance(container, Empty)\n\n    @parametrize_empty\n    def test_empty_container_is_instance(self, container):\n        assert isinstance(container, Empty)\n\n    @parametrize_non_empty\n    def test_instantiation_raises_for_non_empty_container(self, container):\n        with pytest.raises(TypeError):\n            Empty.parse(container)\n\n    @parametrize_mutable\n    def test_instantiation_raises_for_mutable(self, container):\n        with pytest.raises(TypeError):\n            Empty.parse(container)\n\n    @parametrize_empty\n    def test_instantiation_returns_instance(self, container):\n        assert container is Empty.parse(container)\n\n    def test_subscription_returns_type_alias(self):\n        alias = Empty[frozenset]\n        assert get_origin(alias) is Empty\n        (arg,) = get_args(alias)\n        assert arg is frozenset\n\n\nparametrize_non_empty_strs: Final = pytest.mark.parametrize(\n    \"value\",\n    (\"foo\", \"bar\", \" \"),\n)\n\n\nclass TestNonEmptyStr:\n    @parametrize_non_empty_strs\n    def test_non_empty_str_is_instance(self, value: str):\n        assert isinstance(value, NonEmptyStr)\n\n    def test_empty_str_is_not_instance(self):\n        assert not isinstance(\"\", NonEmptyStr)\n\n    def test_instantiation_raises_for_empty_str(self):\n        with pytest.raises(TypeError):\n            NonEmptyStr.parse(\"\")\n\n    @pytest.mark.parametrize(\n        \"value\",\n        (b\"\", b\"foo\", [], [\"foo\"], (), (\"foo\",)),\n    )\n    def test_instantiation_raises_for_non_str(self, value: object):\n        with pytest.raises(TypeError):\n            NonEmptyStr.parse(value)\n\n    @parametrize_non_empty_strs\n    def test_instantiation_returns_instance(self, value: str):\n        assert value is NonEmptyStr.parse(value)\n"
  },
  {
    "path": "tests/test_sized.yaml",
    "content": "- case: test_subtype\n  main: |\n    from phantom.sized import NonEmpty\n    from typing import Tuple\n\n    class AtLeastOneInt(Tuple[int, ...], NonEmpty[int]): ...\n\n    def fst(things: AtLeastOneInt) -> int:\n        return things[0]\n\n    f = fst(AtLeastOneInt.parse((1, 2)))\n    reveal_type(f)  # N: Revealed type is \"builtins.int\"\n\n- case: test_subscripted\n  main: |\n    from phantom.sized import NonEmpty\n\n    def fst(col: NonEmpty[int]) -> int:\n        return next(iter(col))\n\n    l = NonEmpty[int].parse((0,))\n    i = fst(l)\n    reveal_type(i)  # N: Revealed type is \"builtins.int\"\n\n- case: test_annotated\n  main: |\n    from phantom.sized import NonEmpty\n\n    def fst(col: NonEmpty[int]) -> int:\n        return next(iter(col))\n\n    l: NonEmpty[int] = NonEmpty.parse((0,))\n    i = fst(l)\n    reveal_type(i)  # N: Revealed type is \"builtins.int\"\n\n- case: test_str\n  main: |\n    from phantom.sized import NonEmpty\n\n    def name_length(name: NonEmpty[str]) -> int:\n        return len(name)\n\n    i = name_length(NonEmpty.parse(\"foo\"))\n    reveal_type(i)  # N: Revealed type is \"builtins.int\"\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "from typing import Union\n\nimport pytest\n\nfrom phantom._utils.misc import BoundType\nfrom phantom._utils.misc import is_subtype\n\n\nclass A: ...\n\n\nclass B: ...\n\n\nclass C: ...\n\n\nclass AAndB(A, B): ...\n\n\nclass SubOfA(A): ...\n\n\nclass TestIsSubtype:\n    # The cases are annotated with the numbered cases in the docstring of the function.\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            # 1\n            (int | float, int | float),\n            # 2\n            (int, int | float),\n            # Special case of 3\n            (Union[int], int),  # noqa: UP007\n            # 4\n            (AAndB, (A, B)),\n            (AAndB, (A,)),\n            # 5\n            ((B, SubOfA), A),\n            # 6\n            ((SubOfA, C), A | B),\n            # Special case of 7\n            (Union[int], (int,)),  # noqa: UP007\n            # 8\n            ((AAndB, SubOfA), (A, B)),\n            # 9\n            (SubOfA, A),\n        ],\n    )\n    def test_returns_true_for_valid_subtype(self, a: BoundType, b: BoundType) -> None:\n        assert is_subtype(a, b) is True\n\n    # The cases are annotated with the numbered cases in the docstring of the function.\n    @pytest.mark.parametrize(\n        \"a, b\",\n        [\n            # 1\n            (int | float, Union[int]),  # noqa: UP007\n            (int | float, A | B),\n            # 2\n            (str, int | float),\n            # 3\n            (int | float, float),\n            # 4\n            (SubOfA, (A, B)),\n            (SubOfA, (B,)),\n            # 5\n            ((A, B), SubOfA),\n            # 6\n            ((int, C), A | B),\n            # 7\n            (int | float, (int, float)),\n            # 8\n            ((AAndB, SubOfA), (A, B, C)),\n            # 9\n            (SubOfA, B),\n            (A, B),\n        ],\n    )\n    def test_returns_false_for_valid_subtype(self, a: BoundType, b: BoundType) -> None:\n        assert is_subtype(a, b) is False\n"
  },
  {
    "path": "tests/types.py",
    "content": "from phantom.interval import Exclusive\nfrom phantom.interval import ExclusiveInclusive\nfrom phantom.interval import Inclusive\nfrom phantom.interval import InclusiveExclusive\n\n\nclass FloatInc(float, Inclusive, low=0, high=100): ...\n\n\nclass IntInc(int, Inclusive, low=0, high=100): ...\n\n\nclass FloatExc(float, Exclusive, low=0, high=100): ...\n\n\nclass IntExc(int, Exclusive, low=0, high=100): ...\n\n\nclass FloatIncExc(float, InclusiveExclusive, low=0, high=100): ...\n\n\nclass IntIncExc(int, InclusiveExclusive, low=0, high=100): ...\n\n\nclass FloatExcInc(float, ExclusiveInclusive, low=0, high=100): ...\n\n\nclass IntExcInc(int, ExclusiveInclusive, low=0, high=100): ...\n"
  },
  {
    "path": "typing-requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    'make typing-requirements'\nbeartype==0.22.5 \\\n    --hash=sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341 \\\n    --hash=sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0\n    # via numerary\nhypothesis==6.147.0 \\\n    --hash=sha256:72e6004ea3bd1460bdb4640b6389df23b87ba7a4851893fd84d1375635d3e507 \\\n    --hash=sha256:de588807b6da33550d32f47bcd42b1a86d061df85673aa73e6443680249d185e\n    # via phantom-types (pyproject.toml)\niniconfig==2.3.0 \\\n    --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \\\n    --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12\n    # via pytest\nmypy==1.18.2 \\\n    --hash=sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914 \\\n    --hash=sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b \\\n    --hash=sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b \\\n    --hash=sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc \\\n    --hash=sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544 \\\n    --hash=sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86 \\\n    --hash=sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d \\\n    --hash=sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075 \\\n    --hash=sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e \\\n    --hash=sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac \\\n    --hash=sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b \\\n    --hash=sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34 \\\n    --hash=sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37 \\\n    --hash=sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b \\\n    --hash=sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428 \\\n    --hash=sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893 \\\n    --hash=sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce \\\n    --hash=sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8 \\\n    --hash=sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c \\\n    --hash=sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf \\\n    --hash=sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341 \\\n    --hash=sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e \\\n    --hash=sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba \\\n    --hash=sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed \\\n    --hash=sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f \\\n    --hash=sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d \\\n    --hash=sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8 \\\n    --hash=sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764 \\\n    --hash=sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d \\\n    --hash=sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0 \\\n    --hash=sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c \\\n    --hash=sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133 \\\n    --hash=sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986 \\\n    --hash=sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6 \\\n    --hash=sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074 \\\n    --hash=sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb \\\n    --hash=sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e \\\n    --hash=sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66\n    # via phantom-types (pyproject.toml)\nmypy-extensions==1.1.0 \\\n    --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \\\n    --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558\n    # via mypy\nnumerary==0.4.4 \\\n    --hash=sha256:ad955ddf7f5275f8e52f5520b2d6c654cc3bf1e3ae4bfb45664c9d51b208d0c6\n    # via phantom-types (pyproject.toml)\npackaging==25.0 \\\n    --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \\\n    --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f\n    # via pytest\npathspec==0.12.1 \\\n    --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \\\n    --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712\n    # via mypy\nphonenumbers==9.0.18 \\\n    --hash=sha256:5537c61ba95b11b992c95e804da6e49193cc06b1224f632ade64631518a48ed1 \\\n    --hash=sha256:d3354454ac31c97f8a08121df97a7145b8dca641f734c6f1518a41c2f60c5764\n    # via phantom-types (pyproject.toml)\npluggy==1.6.0 \\\n    --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \\\n    --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746\n    # via pytest\npydantic==1.10.24 \\\n    --hash=sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c \\\n    --hash=sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d \\\n    --hash=sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193 \\\n    --hash=sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6 \\\n    --hash=sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624 \\\n    --hash=sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667 \\\n    --hash=sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2 \\\n    --hash=sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c \\\n    --hash=sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621 \\\n    --hash=sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52 \\\n    --hash=sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816 \\\n    --hash=sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f \\\n    --hash=sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813 \\\n    --hash=sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49 \\\n    --hash=sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666 \\\n    --hash=sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a \\\n    --hash=sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a \\\n    --hash=sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683 \\\n    --hash=sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1 \\\n    --hash=sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c \\\n    --hash=sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94 \\\n    --hash=sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f \\\n    --hash=sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47 \\\n    --hash=sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702 \\\n    --hash=sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd \\\n    --hash=sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70 \\\n    --hash=sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53 \\\n    --hash=sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b \\\n    --hash=sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36 \\\n    --hash=sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c \\\n    --hash=sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993 \\\n    --hash=sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b \\\n    --hash=sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8 \\\n    --hash=sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01 \\\n    --hash=sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69 \\\n    --hash=sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3 \\\n    --hash=sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00 \\\n    --hash=sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37 \\\n    --hash=sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2 \\\n    --hash=sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6 \\\n    --hash=sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d \\\n    --hash=sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def \\\n    --hash=sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574 \\\n    --hash=sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6 \\\n    --hash=sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c \\\n    --hash=sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de \\\n    --hash=sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0 \\\n    --hash=sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579 \\\n    --hash=sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4 \\\n    --hash=sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb\n    # via phantom-types (pyproject.toml)\npygments==2.19.2 \\\n    --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \\\n    --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b\n    # via pytest\npytest==9.0.0 \\\n    --hash=sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e \\\n    --hash=sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96\n    # via phantom-types (pyproject.toml)\npython-dateutil==2.9.0.post0 \\\n    --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \\\n    --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427\n    # via phantom-types (pyproject.toml)\nsix==1.17.0 \\\n    --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \\\n    --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81\n    # via python-dateutil\nsortedcontainers==2.4.0 \\\n    --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \\\n    --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0\n    # via hypothesis\ntypeguard==4.4.4 \\\n    --hash=sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74 \\\n    --hash=sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e\n    # via phantom-types (pyproject.toml)\ntypes-python-dateutil==2.9.0.20251108 \\\n    --hash=sha256:a4a537f0ea7126f8ccc2763eec9aa31ac8609e3c8e530eb2ddc5ee234b3cd764 \\\n    --hash=sha256:d8a6687e197f2fa71779ce36176c666841f811368710ab8d274b876424ebfcaa\n    # via phantom-types (pyproject.toml)\ntyping-extensions==4.15.0 \\\n    --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \\\n    --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548\n    # via\n    #   phantom-types (pyproject.toml)\n    #   mypy\n    #   pydantic\n    #   typeguard\n"
  }
]