diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 9b371fc..0000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -omit = src/openapi_python_generator/language_converters/python/templates/* -[report] -show_missing = true diff --git a/src/openapi_python_generator/language_converters/__init__.py b/.gitattributes similarity index 100% rename from src/openapi_python_generator/language_converters/__init__.py rename to .gitattributes diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..04a4d0a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "saturday" + time: "03:00" + timezone: "Australia/Melbourne" + + - package-ecosystem: "pip" + directory: "/" + open-pull-requests-limit: 2 + schedule: + interval: "weekly" + day: "saturday" + time: "03:00" + timezone: "Australia/Melbourne" diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml deleted file mode 100644 index 521b770..0000000 --- a/.github/workflows/build_docs.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: build_docs -on: - push: - branches: - - master - - main -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install mkdocs-material - - run: mkdocs gh-deploy --force diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..d393f07 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,75 @@ +name: CI + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} - ci-pipeline" + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.12" + + # Private Git deps (optional; remove if not needed) + - name: Configure Git credentials for private deps + if: env.GH_READ_TOKEN != '' + env: + GH_READ_TOKEN: ${{ secrets.GH_READ_TOKEN }} + run: | + git config --global url."https://${GH_READ_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + + - name: Install Python + run: uv python install + + - name: Sync deps + run: uv sync + + - name: Lint + run: | + uv run ruff check + uv run ruff format --check + + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.12" + + - name: Configure Git credentials for private deps + if: env.GH_READ_TOKEN != '' + env: + GH_READ_TOKEN: ${{ secrets.GH_READ_TOKEN }} + run: | + git config --global url."https://${GH_READ_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + + - name: Install Python + run: uv python install + + - name: Sync deps + run: uv sync + + - name: Test + run: uv run pytest -v -m "not integration" diff --git a/.github/workflows/continous_deployment.yml b/.github/workflows/continous_deployment.yml deleted file mode 100644 index d1787a7..0000000 --- a/.github/workflows/continous_deployment.yml +++ /dev/null @@ -1,24 +0,0 @@ -# .github/workflows/test-pypi.yml -name: PYPi -on: - push: - branches: [ main ] - - - workflow_dispatch: -jobs: - test_pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - with: - python-version: '3.9' - architecture: x64 - - run: pip install poetry==1.3.1 - - run: >- - poetry version patch && - version=$(poetry version | awk '{print $2}') && - poetry version $version.dev.$(date +%s) - - run: poetry build - - run: poetry publish --username=__token__ --password=${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..0af6ff9 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,39 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: + +permissions: read-all + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: "${{ github.workflow }} @ ${{ github.head_ref || github.ref }} - release" + cancel-in-progress: true + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: "3.12" + + - name: Install project Python + run: uv python install + + - name: Build + run: uv build --no-sources + + - name: Publish to PyPI + env: + UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }} + run: uv publish --verbose diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 10133f7..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,47 +0,0 @@ -# .github/workflows/release.yml -name: Release -on: - release: - types: [published] - - workflow_dispatch: - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: πŸ“₯ Checkout code - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.release.target_commitish }} - token: ${{ secrets.GITHUB_TOKEN }} - - - name: 🐍 Setup Python - uses: actions/setup-python@v3 - with: - python-version: '3.9' - architecture: x64 - - - name: πŸ“¦ Install Poetry - run: pip install poetry==1.3.1 - - - name: πŸ”§ Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: πŸš€ Bump version, commit, and push - run: | - poetry version ${{ github.ref_name }} - git add pyproject.toml - git commit -m "chore(release): version ${{ github.ref_name }}" - git tag -f ${{ github.ref_name }} HEAD - git push origin ${{ github.event.release.target_commitish }} - git push -f origin refs/tags/${{ github.ref_name }} - - - name: πŸ“¦ Build package - run: poetry build - - - name: πŸš€ Publish to PyPI - run: poetry publish --username=__token__ --password=${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 5120603..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Tests - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - - workflow_dispatch: - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] - fail-fast: false - - steps: - - name: πŸ“₯ Checkout code - uses: actions/checkout@v3 - - - name: 🐍 Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - - name: πŸ“¦ Install Poetry and dependencies - run: | - pip install poetry==1.3.1 - poetry install - - - name: πŸ“Š Run tests with coverage - run: poetry run pytest --cov=src/ --cov-report=xml --cov-fail-under 90 --cov-config=.coveragerc - - - name: πŸ” Type checking with ty - run: poetry run ty check src/ - - - name: 🎨 Code formatting with black - run: poetry run black --check . - - - name: πŸ”§ Linting - run: poetry run ruff check . - - - name: πŸ“ˆ Upload coverage to Codecov - uses: codecov/codecov-action@v2 - if: always() && github.event_name != 'pull_request' - with: - token: ${{ secrets.CODECOV_TOKEN }} - directory: ./coverage/reports/ - env_vars: OS,PYTHON - fail_ci_if_error: true - files: ./coverage.xml - flags: unittests - name: codecov-umbrella-${{ matrix.python-version }} - path_to_write_report: .codecov_report.${{ matrix.python-version }}.txt - verbose: true diff --git a/.gitignore b/.gitignore index bb1d8a2..92b9752 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +*.py[codz] *$py.class # C extensions @@ -39,7 +40,6 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ -.tox/ .nox/ .coverage .coverage.* @@ -48,8 +48,10 @@ nosetests.xml coverage.xml *.cover *.py,cover +*.py.cover .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -72,6 +74,7 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook @@ -82,16 +85,46 @@ profile_default/ ipython_config.py # pyenv -.python-version +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow +Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +uv.lock + +# poetry +# For packages, we do not commit the lock file, +# since we are not controlling the environment, +# refer to https://python-poetry.org/docs/basic-usage/#as-a-library-developer +poetry.lock +poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +pdm.lock +pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff @@ -103,6 +136,7 @@ celerybeat.pid # Environments .env +.envrc .venv env/ venv/ @@ -131,3 +165,54 @@ dmypy.json /testclient/ /tests/test_result/ test_output/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +#.vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +# Generated Files +.DS_Store +openapi.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9ba7de..3a8eb23 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,27 @@ repos: -- repo: local + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.5 hooks: - - id: black - name: black - entry: poetry run black - language: system - types: [python] - - id: flake8 - name: flake8 - entry: poetry run ruff check - language: system - types: [ python ] - - id: typecheck - name: typecheck - entry: poetry run ty check src/ - language: system - types: [ python ] + - id: ruff + args: [--fix] + name: ruff check + fix + - id: ruff-format + name: ruff format + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + args: ["--write-changes"] + + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.16 + hooks: + - id: mdformat + args: ["--wrap", "80"] + + - repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 + hooks: + - id: toml-sort + args: ["--all", "--in-place"] diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..cfdace2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,74 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": "${command:pickArgs}" + }, + { + "name": "pytest", + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["-vv", "--no-cov"], + "justMyCode": false, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "env": { + "PYTHONASYNCIODEBUG": "1" // optional: helps with debugging asyncio + } + }, + { + "name": "pytest: current module", + "type": "python", + "request": "launch", + "module": "pytest", + // run pytest on the directory that contains the file in the active editor + "args": ["-vv", "${fileDirname}", "--no-cov"], + "justMyCode": false, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "env": { + "PYTHONASYNCIODEBUG": "1" // optional: helps with debugging asyncio + } + }, + { + "name": "pytest: current file", + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["-vv", "${relativeFile}", "--no-cov"], + "justMyCode": false, + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "env": { + "PYTHONASYNCIODEBUG": "1" // optional: helps with debugging asyncio + } + }, + { + "name": "pytest: specify case in current file", + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["-vv", "${relativeFile}::${input:testName}", "--no-cov"], + "justMyCode": false, + "console": "integratedTerminal", + "cwd": "${workspaceFolder}", + "env": { + "PYTHONASYNCIODEBUG": "1" // optional: helps with debugging asyncio + } + } + ], + "inputs": [ + { + "id": "testName", + "type": "promptString", + "description": "Enter test name or leave blank to run the whole file", + "default": "" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b509e67 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "install", + "type": "shell", + "command": "make install", + "group": "build", + "detail": "install required dependencies on bare metal" + }, + { + "label": "format", + "type": "shell", + "command": "make format", + "group": "build", + "detail": "Run the formatter on bare metal" + }, + { + "label": "lint", + "type": "shell", + "command": "make lint", + "group": "build", + "detail": "run the linter on bare metal" + }, + { + "label": "test", + "type": "shell", + "command": "make test", + "group": "build", + "detail": "run unit tests on bare metal" + }, + { + "label": "publish", + "type": "shell", + "command": "make publish", + "group": "build", + "detail": "Build & publish the package to Nexus" + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index e0d3ee1..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[muellnermarco@gmail.com](mailto:muellnermarco@gmail.com). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][mozilla coc]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][faq]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[mozilla coc]: https://github.com/mozilla/diversity -[faq]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 62a675c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,115 +0,0 @@ -# Contributor Guide - -Thank you for your interest in improving this project. -This project is open-source under the [MIT license] and -welcomes contributions in the form of bug reports, feature requests, and pull requests. - -Here is a list of important resources for contributors: - -- [Source Code] -- [Documentation] -- [Issue Tracker] -- [Code of Conduct] - -[mit license]: https://opensource.org/licenses/MIT -[source code]: https://github.com/MarcoMuellner/openapi-python-generator -[documentation]: https://openapi-python-generator.readthedocs.io/ -[issue tracker]: https://github.com/MarcoMuellner/openapi-python-generator/issues - -## How to report a bug - -Report bugs on the [Issue Tracker]. - -When filing an issue, make sure to answer these questions: - -- Which operating system and Python version are you using? -- Which version of this project are you using? -- What did you do? -- What did you expect to see? -- What did you see instead? - -The best way to get your bug fixed is to provide a test case, -and/or steps to reproduce the issue. - -## How to request a feature - -Request features on the [Issue Tracker]. - -## How to set up your development environment - -You need Python 3.7+ and the following tools: - -- [Poetry] -- [Nox] -- [nox-poetry] - -Install the package with development requirements: - -```console -$ poetry install -``` - -You can now run an interactive Python session, -or the command-line interface: - -```console -$ poetry run python -$ poetry run openapi-python-generator -``` - -[poetry]: https://python-poetry.org/ -[nox]: https://nox.thea.codes/ -[nox-poetry]: https://nox-poetry.readthedocs.io/ - -## How to test the project - -Run the full test suite: - -```console -$ nox -``` - -List the available Nox sessions: - -```console -$ nox --list-sessions -``` - -You can also run a specific Nox session. -For example, invoke the unit test suite like this: - -```console -$ nox --session=tests -``` - -Unit tests are located in the _tests_ directory, -and are written using the [pytest] testing framework. - -[pytest]: https://pytest.readthedocs.io/ - -## How to submit changes - -Open a [pull request] to submit changes to this project. - -Your pull request needs to meet the following guidelines for acceptance: - -- The Nox test suite must pass without errors and warnings. -- Include unit tests. This project maintains 100% code coverage. -- If your changes add functionality, update the documentation accordingly. - -Feel free to submit early, thoughβ€”we can always iterate on this. - -To run linting and code formatting checks before committing your change, you can install pre-commit as a Git hook by running the following command: - -```console -$ nox --session=pre-commit -- install -``` - -It is recommended to open an issue before starting work on anything. -This will allow a chance to talk it over with the owners and validate your approach. - -[pull request]: https://github.com/MarcoMuellner/openapi-python-generator/pulls - - - -[code of conduct]: CODE_OF_CONDUCT.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed6d5ca --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.DEFAULT_GOAL := help +SHELL := /bin/bash + + +.PHONY: install ## install required dependencies on bare metal +install: + uv sync --refresh + + +.PHONY: format ## Run the formatter on bare metal +format: + uv run ruff format + uv run ruff check --fix + + +.PHONY: lint ## run the linter on bare metal +lint: + uv run ruff check + uv run ruff format --check + + +.PHONY: test ## run unit tests on bare metal +test: + uv run pytest -v -m "not integration" diff --git a/README.md b/README.md index 413113c..1c763e4 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# Openapi Python Generator +# Pydantic OpenAPI Generator -[![PyPI](https://img.shields.io/pypi/v/openapi-python-generator.svg)][pypi_] -[![Status](https://img.shields.io/pypi/status/openapi-python-generator.svg)][status] -[![Python Version](https://img.shields.io/pypi/pyversions/openapi-python-generator)][python version] -[![License](https://img.shields.io/pypi/l/openapi-python-generator)][license] +[![PyPI](https://img.shields.io/pypi/v/pydantic-openapi-generator.svg)][pypi_] +[![Status](https://img.shields.io/pypi/status/pydantic-openapi-generator.svg)][status] +[![Python Version](https://img.shields.io/pypi/pyversions/pydantic-openapi-generator)][python version] +[![License](https://img.shields.io/pypi/l/pydantic-openapi-generator)][license] [![](https://img.shields.io/static/v1?label=documentation&message=enabled&color=)][documentation] -[![Tests](https://github.com/MarcoMuellner/openapi-python-generator/workflows/Tests/badge.svg)][tests] -[![Codecov](https://codecov.io/gh/MarcoMuellner/openapi-python-generator/branch/main/graph/badge.svg)][codecov] +[![Tests](https://github.com/mattcoulter7/pydantic-openapi-generator/workflows/Tests/badge.svg)][tests] +[![Codecov](https://codecov.io/gh/mattcoulter7/pydantic-openapi-generator/branch/main/graph/badge.svg)][codecov] [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)][pre-commit] [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)][black] -[pypi_]: https://pypi.org/project/openapi-python-generator/ -[status]: https://pypi.org/project/openapi-python-generator/ -[python version]: https://pypi.org/project/openapi-python-generator -[documentation]: https://marcomuellner.github.io/openapi-python-generator/ -[tests]: https://github.com/MarcoMuellner/openapi-python-generator/actions?workflow=Tests -[codecov]: https://app.codecov.io/gh/MarcoMuellner/openapi-python-generator +[pypi_]: https://pypi.org/project/pydantic-openapi-generator/ +[status]: https://pypi.org/project/pydantic-openapi-generator/ +[python version]: https://pypi.org/project/pydantic-openapi-generator +[documentation]: https://github.com/mattcoulter7/pydantic-openapi-generator +[tests]: https://github.com/mattcoulter7/pydantic-openapi-generator/actions/workflows/ci.yaml +[codecov]: https://app.codecov.io/gh/mattcoulter7/pydantic-openapi-generator [pre-commit]: https://github.com/pre-commit/pre-commit [black]: https://github.com/psf/black @@ -28,11 +28,27 @@ __Documentation:__ [here][documentation] --- +## Migration from auth-broker + +As of `pydantic-openapi-generator` version `2.2.8`, this package has moved out +of the `auth-broker` organisation, been renamed, and had its import namespace +updated. + +| Item | Previous | Current | +| --- | --- | --- | +| GitHub repository | [`auth-broker/openapi-python-generator`](https://github.com/auth-broker/openapi-python-generator) | [`mattcoulter7/pydantic-openapi-generator`](https://github.com/mattcoulter7/pydantic-openapi-generator) | +| PyPI package | [`ab-openapi-python-generator`](https://pypi.org/project/ab-openapi-python-generator/) | [`pydantic-openapi-generator`](https://pypi.org/project/pydantic-openapi-generator/) | +| CLI command | `ab-openapi-python-generator` | `pydantic-openapi-generator` | +| Import namespace | `ab_openapi_python_generator` | `pydantic_openapi_generator` | + +The old PyPI package is retained as an archived historical package. New work +should use `pydantic-openapi-generator` and `pydantic_openapi_generator`. + ## Features - __Ease of use__. Provide input, output and the library, and the generator will do the rest. -- __Type safety and type hinting.__ __OpenAPI python generator__ makes heavy use of pydantic models to provide type-safe data structures. -- __Support for multiple rest frameworks.__ __OpenAPI python generator__ currently supports the following: +- __Type safety and type hinting.__ __Pydantic OpenAPI Generator__ makes heavy use of pydantic models to provide type-safe data structures. +- __Support for multiple rest frameworks.__ __Pydantic OpenAPI Generator__ currently supports the following: - [httpx](https://pypi.org/project/httpx/) - [requests](https://pypi.org/project/requests/) - [aiohttp](https://pypi.org/project/aiohttp/) @@ -43,14 +59,14 @@ __Documentation:__ [here][documentation] ## Requirements -- Python 3.7+ +- Python 3.12+ ## Installation -You can install _Openapi Python Generator_ via [pip] from [PyPI]: +You can install _Pydantic OpenAPI Generator_ via [pip] from [PyPI]: ```console -$ pip install openapi-python-generator +$ pip install pydantic-openapi-generator ``` ## Usage @@ -89,11 +105,11 @@ This project was generated from [@cjolowicz]'s [Hypermodern Python Cookiecutter] [@cjolowicz]: https://github.com/cjolowicz [pypi]: https://pypi.org/ [hypermodern python cookiecutter]: https://github.com/cjolowicz/cookiecutter-hypermodern-python -[file an issue]: https://github.com/MarcoMuellner/openapi-python-generator/issues +[file an issue]: https://github.com/mattcoulter7/pydantic-openapi-generator/issues [pip]: https://pip.pypa.io/ -[license]: https://github.com/MarcoMuellner/openapi-python-generator/blob/main/LICENSE -[contributor guide]: https://github.com/MarcoMuellner/openapi-python-generator/blob/main/CONTRIBUTING.md -[Quick start page]: https://marcomuellner.github.io/openapi-python-generator/quick_start/ +[license]: https://github.com/mattcoulter7/pydantic-openapi-generator/blob/main/LICENSE +[contributor guide]: https://github.com/mattcoulter7/pydantic-openapi-generator/blob/main/CONTRIBUTING.md +[Quick start page]: https://github.com/mattcoulter7/pydantic-openapi-generator#usage diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index bb167c8..0000000 --- a/codecov.yml +++ /dev/null @@ -1,9 +0,0 @@ -comment: false -coverage: - status: - project: - default: - target: "85" - patch: - default: - target: "85" diff --git a/docs/index.md b/docs/index.md index 6651c38..243505d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,14 +2,14 @@ _OpenAPI python generator, a modern way to generate clients for OpenAPI 3.0.0+ APIs_ -![](https://raw.githubusercontent.com/MarcoMuellner/openapi-python-generator/main/logo.png) +![](https://raw.githubusercontent.com/mattcoulter7/pydantic-openapi-generator/main/logo.png) -![Tests](https://github.com/MarcoMuellner/openapi-python-generator/workflows/Tests/badge.svg) ![Codecov](https://codecov.io/gh/MarcoMuellner/openapi-python-generator/branch/main/graph/badge.svg) ![PyPI](https://img.shields.io/pypi/v/openapi-python-generator.svg) ![Python Version](https://img.shields.io/pypi/pyversions/openapi-python-generator) +![Tests](https://github.com/mattcoulter7/pydantic-openapi-generator/workflows/Tests/badge.svg) ![Codecov](https://codecov.io/gh/mattcoulter7/pydantic-openapi-generator/branch/main/graph/badge.svg) ![PyPI](https://img.shields.io/pypi/v/pydantic-openapi-generator.svg) ![Python Version](https://img.shields.io/pypi/pyversions/pydantic-openapi-generator) --- **Documentation**: -**Source**: [https://github.com/MarcoMuellner/openapi-python-generator](https://github.com/MarcoMuellner/openapi-python-generator) +**Source**: [https://github.com/mattcoulter7/pydantic-openapi-generator](https://github.com/mattcoulter7/pydantic-openapi-generator) --- diff --git a/docs/quick_start.md b/docs/quick_start.md index 0277d83..cdbf01e 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -1,11 +1,11 @@ # Quick start -Make sure you have the latest version of `openapi-python-generator` installed. +Make sure you have the latest version of `pydantic-openapi-generator` installed.
- pip install openapi-python-generator --upgrade + pip install pydantic-openapi-generator --upgrade - Successfully installed openapi-python-generator + Successfully installed pydantic-openapi-generator
--- @@ -14,8 +14,8 @@ To call the generator, simply pass the OpenAPI spec (as a link or to a file), an you can also pass the library you would like to use.
- openapi-python-generator https://raw.githubusercontent.com/MarcoMuellner/openapi-python-generator/main/tests/test_data/test_api.json testclient - Generating data from https://raw.githubusercontent.com/MarcoMuellner/openapi-python-generator/main/tests/test_data/test_api.json + pydantic-openapi-generator https://raw.githubusercontent.com/mattcoulter7/pydantic-openapi-generator/main/tests/test_data/test_api.json testclient + Generating data from https://raw.githubusercontent.com/mattcoulter7/pydantic-openapi-generator/main/tests/test_data/test_api.json
This will generate a folder called testclient, with the following structure (using the file above): diff --git a/docs/references/index.md b/docs/references/index.md index a928966..aed30c1 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -3,7 +3,7 @@ ## CLI Interface ```console -$ openapi-python-generator [library] +$ pydantic-openapi-generator [library] ``` Arguments: ```console diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index 5c96845..c392e46 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -7,9 +7,9 @@ you need to do is to actually install the generator. You can do so via pip or any other package manager.
- pip install openapi-python-generator --upgrade + pip install pydantic-openapi-generator --upgrade - Successfully installed openapi-python-generator + Successfully installed pydantic-openapi-generator
For this tutorial, we'll use the `test_api.json` file contained within the test @@ -22,8 +22,8 @@ suite of the generator. It has the following structure: { "openapi": "3.0.2", "info": { - "title": "openapi-python-generator test api", - "description": "API Schema for openapi-python-generator test api", + "title": "pydantic-openapi-generator test api", + "description": "API Schema for pydantic-openapi-generator test api", "version": "1.0.0", "x-logo": { "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" @@ -605,8 +605,8 @@ suite of the generator. It has the following structure: Lets run the generator on this file:
- openapi-python-generator https://raw.githubusercontent.com/MarcoMuellner/openapi-python-generator/main/tests/test_data/test_api.json testclient - Generating data from https://raw.githubusercontent.com/MarcoMuellner/openapi-python-generator/main/tests/test_data/test_api.json + pydantic-openapi-generator https://raw.githubusercontent.com/mattcoulter7/pydantic-openapi-generator/main/tests/test_data/test_api.json testclient + Generating data from https://raw.githubusercontent.com/mattcoulter7/pydantic-openapi-generator/main/tests/test_data/test_api.json
This will result in the folder structure as denoted in the @@ -633,11 +633,9 @@ file: class EnumComponent(str, Enum): - enumvalue1 = "EnumValue1" enumvalue2 = "EnumValue2" enumvalue3 = "EnumValue3" - ``` This is pretty straight forward, but what about the pydantic models? Lets @@ -746,7 +744,6 @@ therefore reflected in the model. ... created_at: Optional[str] = None ... - ``` Hence, we can also directly use the json output from the service requests @@ -787,17 +784,19 @@ only sync (for __requests__) or only async (for __aiohttp__) services. === "async_general_service.py" ``` py ... + + async def async_root__get() -> RootResponse: - base_path = APIConfig().base_path + base_url = APIConfig().base_url path = f"/" headers = { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": f"Bearer { APIConfig().get_access_token() }", + "Authorization": f"Bearer {APIConfig().get_access_token()}", } query_params = {} - with httpx.AsyncClient(base_url=base_path) as client: + with httpx.AsyncClient(base_url=base_url) as client: response = await client.request( method="get", url=path, @@ -808,23 +807,27 @@ only sync (for __requests__) or only async (for __aiohttp__) services. if response.status_code != 200: raise Exception(f" failed with status code: {response.status_code}") return RootResponse(**response.json()) + + ... ``` === "general_service.py" ``` py ... + + def root__get() -> RootResponse: - base_path = APIConfig().base_path + base_url = APIConfig().base_url path = f"/" headers = { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": f"Bearer { APIConfig().get_access_token() }", + "Authorization": f"Bearer {APIConfig().get_access_token()}", } query_params = {} - with httpx.Client(base_url=base_path) as client: + with httpx.Client(base_url=base_url) as client: response = client.request( method="get", url=path, @@ -835,6 +838,8 @@ only sync (for __requests__) or only async (for __aiohttp__) services. if response.status_code != 200: raise Exception(f" failed with status code: {response.status_code}") return RootResponse(**response.json()) + + ... ``` @@ -860,10 +865,10 @@ Paths are automatically created from the specification. No need to worry about t ```py ... headers = { - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": f"Bearer { APIConfig.get_access_token() }", - } + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"Bearer {APIConfig.get_access_token()}", +} query_params = {} ... ``` diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 0c97159..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,62 +0,0 @@ -# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json -site_name: Openapi python generator -site_url: https://example.com/ -repo_url: https://github.com/MarcoMuellner/openapi-python-generator -theme: - name: material - palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: indigo - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: indigo - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - - content.code.annotate - icon: - repo: fontawesome/brands/github-alt -markdown_extensions: - - pymdownx.superfences - - admonition - - attr_list - - md_in_html - - pymdownx.tabbed: - alternate_style: true -nav: - - OpenAPI Python Generator: index.md - - openapi-definition.md - - quick_start.md - - Tutorial - User Guide: - - tutorial/index.md - - tutorial/authentication.md - - tutorial/advanced.md - - References : - - references/index.md - - Acknowledgements: - - acknowledgements/index.md -extra_css: - - css/termynal.css - - css/custom.css -extra_javascript: - - js/termynal.js - - js/custom.js -extra: - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/MarcoMuellner - - icon: fontawesome/brands/twitter - link: https://twitter.com/MuellnerM7490 - analytics: - provider: google - tracking_id: G-ZETQJVSGQ6 diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index ba8d057..0000000 --- a/noxfile.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Nox sessions.""" - -import os -import shlex -import shutil -import sys -from pathlib import Path -from textwrap import dedent - -import nox - -try: - from nox_poetry import Session, session -except ImportError: - message = f"""\ - Nox failed to import the 'nox-poetry' package. - - Please install it using the following command: - - {sys.executable} -m pip install nox-poetry""" - raise SystemExit(dedent(message)) from None - -package = "openapi_python_generator" -python_versions = ["3.9"] -nox.needs_version = ">= 2021.6.6" -nox.options.sessions = ( - "pre-commit", - "mypy", - "tests", - "typeguard", - "xdoctest", -) - - -def activate_virtualenv_in_precommit_hooks(session: Session) -> None: - """Activate virtualenv in hooks installed by pre-commit. - - This function patches git hooks installed by pre-commit to activate the - session's virtual environment. This allows pre-commit to locate hooks in - that environment when invoked from git. - - Args: - session: The Session object. - """ - assert session.bin is not None # noqa: S101 - - # Only patch hooks containing a reference to this session's bindir. Support - # quoting rules for Python and bash, but strip the outermost quotes so we - # can detect paths within the bindir, like /python. - bindirs = [ - bindir[1:-1] if bindir[0] in "'\"" else bindir - for bindir in (repr(session.bin), shlex.quote(session.bin)) - ] - - virtualenv = session.env.get("VIRTUAL_ENV") - if virtualenv is None: - return - - headers = { - # pre-commit < 2.16.0 - "python": f"""\ - import os - os.environ["VIRTUAL_ENV"] = {virtualenv!r} - os.environ["PATH"] = os.pathsep.join(( - {session.bin!r}, - os.environ.get("PATH", ""), - )) - """, - # pre-commit >= 2.16.0 - "bash": f"""\ - VIRTUAL_ENV={shlex.quote(virtualenv)} - PATH={shlex.quote(session.bin)}"{os.pathsep}$PATH" - """, - # pre-commit >= 2.17.0 on Windows forces sh shebang - "/bin/sh": f"""\ - VIRTUAL_ENV={shlex.quote(virtualenv)} - PATH={shlex.quote(session.bin)}"{os.pathsep}$PATH" - """, - } - - hookdir = Path(".git") / "hooks" - if not hookdir.is_dir(): - return - - for hook in hookdir.iterdir(): - if hook.name.endswith(".sample") or not hook.is_file(): - continue - - if not hook.read_bytes().startswith(b"#!"): - continue - - text = hook.read_text() - - if not any( - Path("A") == Path("a") and bindir.lower() in text.lower() or bindir in text - for bindir in bindirs - ): - continue - - lines = text.splitlines() - - for executable, header in headers.items(): - if executable in lines[0].lower(): - lines.insert(1, dedent(header)) - hook.write_text("\n".join(lines)) - break - - -@session(name="pre-commit", python=python_versions[0]) -def precommit(session: Session) -> None: - """Lint using pre-commit.""" - args = session.posargs or [ - "run", - "--all-files", - "--hook-stage=manual", - "--show-diff-on-failure", - ] - session.install( - "black", - "darglint", - "flake8", - "flake8-bandit", - "flake8-bugbear", - "flake8-docstrings", - "flake8-rst-docstrings", - "isort", - "pep8-naming", - "pre-commit", - "pre-commit-hooks", - "pyupgrade", - ) - session.run("pre-commit", *args) - if args and args[0] == "install": - activate_virtualenv_in_precommit_hooks(session) - - -@session(python=python_versions) -def mypy(session: Session) -> None: - """Type-check using mypy.""" - args = session.posargs or ["src", "tests"] - session.install(".") - session.install("mypy", "pytest", "respx", "fastapi", "uvicorn") - session.run("mypy", *args) - if not session.posargs: - session.run("mypy", f"--python-executable={sys.executable}", "noxfile.py") - - -@session(python=python_versions) -def tests(session: Session) -> None: - """Run the test suite.""" - session.install(".") - session.install("coverage[toml]", "pytest", "pygments", "respx", "fastapi") - try: - session.run("coverage", "run", "--parallel", "-m", "pytest", *session.posargs) - finally: - if session.interactive: - session.notify("coverage", posargs=[]) - - -@session(python=python_versions[0]) -def coverage(session: Session) -> None: - """Produce the coverage report.""" - args = session.posargs or ["report", "-i"] - - session.install("coverage[toml]") - - if not session.posargs and any(Path().glob(".coverage.*")): - session.run("coverage", "combine") - - session.run("coverage", *args) - - -@session(python=python_versions[0]) -def typeguard(session: Session) -> None: - """Runtime type checking using Typeguard.""" - session.install(".") - session.install("pytest", "typeguard", "pygments", "respx", "fastapi", "uvicorn") - session.run("pytest", f"--typeguard-packages={package}", *session.posargs) - - -@session(python=python_versions) -def xdoctest(session: Session) -> None: - """Run examples with xdoctest.""" - if session.posargs: - args = [package, *session.posargs] - else: - args = [f"--modname={package}", "--command=all"] - if "FORCE_COLOR" in os.environ: - args.append("--colored=1") - - session.install(".") - session.install("xdoctest[colors]") - session.run("python", "-m", "xdoctest", *args) - - -@session(python=python_versions[0]) -def docs(session: Session) -> None: - """Build and serve the documentation with live reloading on file changes.""" - args = session.posargs or ["serve"] - session.install(".") - session.install("mkdocs", "mkdocs-material") - - build_dir = Path("docs", "_build") - if build_dir.exists(): - shutil.rmtree(build_dir) - - session.run("mkdocs", *args) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 7c5a294..0000000 --- a/poetry.lock +++ /dev/null @@ -1,2878 +0,0 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. - -[[package]] -name = "aiohappyeyeballs" -version = "2.4.3" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, -] - -[[package]] -name = "aiohttp" -version = "3.10.11" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, - {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, - {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, - {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, - {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, - {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, - {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, - {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, - {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, - {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, - {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, - {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, - {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, - {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, - {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, - {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, - {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, - {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, - {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, - {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, - {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, - {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, - {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, - {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, - {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, - {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, - {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, - {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, - {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, - {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, - {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, - {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, - {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, - {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, - {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, - {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, - {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, - {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, - {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, - {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, - {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, - {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, - {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, - {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, - {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, - {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, - {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, - {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, - {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, - {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, - {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, - {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, - {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, - {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, - {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, - {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, - {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, - {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, - {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, - {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, - {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, - {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, - {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, - {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, - {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, - {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, - {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, - {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, - {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, - {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, - {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, - {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, - {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, - {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, - {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, - {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, - {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, - {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, - {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, - {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, - {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, - {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, - {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, - {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, - {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, - {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, - {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, - {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, - {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, - {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, - {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.12.0,<2.0" - -[package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "anyio" -version = "4.5.2" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, - {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "async-timeout" -version = "5.0.1" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, - {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, -] - -[[package]] -name = "attrs" -version = "24.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, -] - -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] - -[[package]] -name = "authlib" -version = "1.3.2" -description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"}, - {file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"}, -] - -[package.dependencies] -cryptography = "*" - -[[package]] -name = "babel" -version = "2.16.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, -] - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -groups = ["dev"] -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["dev"] -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} - -[[package]] -name = "coverage" -version = "6.5.0" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] - -[[package]] -name = "cryptography" -version = "43.0.3" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "darglint" -version = "1.8.1" -description = "A utility for ensuring Google-style docstrings stay up to date with the source code." -optional = false -python-versions = ">=3.6,<4.0" -groups = ["dev"] -files = [ - {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, - {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, -] - -[[package]] -name = "distlib" -version = "0.3.9" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, -] - -[[package]] -name = "docutils" -version = "0.20.1" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, -] - -[[package]] -name = "dparse" -version = "0.6.4" -description = "A parser for Python dependency files" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "dparse-0.6.4-py3-none-any.whl", hash = "sha256:fbab4d50d54d0e739fbb4dedfc3d92771003a5b9aa8545ca7a7045e3b174af57"}, - {file = "dparse-0.6.4.tar.gz", hash = "sha256:90b29c39e3edc36c6284c82c4132648eaf28a01863eb3c231c2512196132201a"}, -] - -[package.dependencies] -packaging = "*" -tomli = {version = "*", markers = "python_version < \"3.11\""} - -[package.extras] -all = ["pipenv", "poetry", "pyyaml"] -conda = ["pyyaml"] -pipenv = ["pipenv"] -poetry = ["poetry"] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "fastapi" -version = "0.115.5" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"}, - {file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.42.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] - -[[package]] -name = "filelock" -version = "3.12.4" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1) ; python_version < \"3.11\""] - -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6.1" -groups = ["dev"] -files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - -[[package]] -name = "frozenlist" -version = "1.5.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, -] - -[[package]] -name = "furo" -version = "2024.8.6" -description = "A clean customisable Sphinx documentation theme." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, - {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, -] - -[package.dependencies] -beautifulsoup4 = "*" -pygments = ">=2.7" -sphinx = ">=6.0,<9.0" -sphinx-basic-ng = ">=1.0.0.beta2" - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.8" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, - {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "identify" -version = "2.6.1" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "importlib-metadata" -version = "8.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "marshmallow" -version = "3.22.0" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"}, - {file = "marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.4.2" -description = "Collection of plugins for markdown-it-py" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, - {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, -] - -[package.dependencies] -markdown-it-py = ">=1.0.0,<4.0.0" - -[package.extras] -code-style = ["pre-commit"] -rtd = ["myst-parser", "sphinx-book-theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "multidict" -version = "6.1.0" -description = "multidict implementation" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "myst-parser" -version = "3.0.1" -description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, - {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, -] - -[package.dependencies] -docutils = ">=0.18,<0.22" -jinja2 = "*" -markdown-it-py = ">=3.0,<4.0" -mdit-py-plugins = ">=0.4,<1.0" -pyyaml = "*" -sphinx = ">=6,<8" - -[package.extras] -code-style = ["pre-commit (>=3.0,<4.0)"] -linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "openapi-pydantic" -version = "0.5.1" -description = "Pydantic OpenAPI schema implementation" -optional = false -python-versions = "<4.0,>=3.8" -groups = ["main"] -files = [ - {file = "openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146"}, - {file = "openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d"}, -] - -[package.dependencies] -pydantic = ">=1.8" - -[[package]] -name = "orjson" -version = "3.10.12" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, - {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, - {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, - {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, - {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, - {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, - {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, - {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, - {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, - {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, - {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, - {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, - {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, - {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, - {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, - {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, - {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, - {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, - {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, - {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, - {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, - {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, - {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, - {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, - {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, - {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, - {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, - {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, - {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, - {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, - {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pep8-naming" -version = "0.14.1" -description = "Check PEP-8 naming conventions, plugin for flake8" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36"}, - {file = "pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5"}, -] - -[package.dependencies] -flake8 = ">=5.0.0" - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.5.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pre-commit-hooks" -version = "5.0.0" -description = "Some out-of-the-box hooks for pre-commit." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pre_commit_hooks-5.0.0-py2.py3-none-any.whl", hash = "sha256:8d71cfb582c5c314a5498d94e0104b6567a8b93fb35903ea845c491f4e290a7a"}, - {file = "pre_commit_hooks-5.0.0.tar.gz", hash = "sha256:10626959a9eaf602fbfc22bc61b6e75801436f82326bfcee82bb1f2fc4bc646e"}, -] - -[package.dependencies] -"ruamel.yaml" = ">=0.15" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "propcache" -version = "0.2.0" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] - -[[package]] -name = "psutil" -version = "6.0.0" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["dev"] -files = [ - {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, - {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, - {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, - {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, - {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, - {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, - {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, - {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, - {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, - {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, - {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, - {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, - {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, -] - -[package.extras] -test = ["enum34 ; python_version <= \"3.4\"", "ipaddress ; python_version < \"3.0\"", "mock ; python_version < \"3.0\"", "pywin32 ; sys_platform == \"win32\"", "wmi ; sys_platform == \"win32\""] - -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "2.10.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.1" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-core" -version = "2.27.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, - {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, - {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.3.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pyupgrade" -version = "3.8.0" -description = "A tool to automatically upgrade syntax for newer versions." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pyupgrade-3.8.0-py2.py3-none-any.whl", hash = "sha256:08d0e6129f5e9da7e7a581bdbea689e0d49c3c93eeaf156a07ae2fd794f52660"}, - {file = "pyupgrade-3.8.0.tar.gz", hash = "sha256:1facb0b8407cca468dfcc1d13717e3a85aa37b9e6e7338664ad5bfe5ef50c867"}, -] - -[package.dependencies] -tokenize-rt = ">=3.2.0" - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "responses" -version = "0.25.8" -description = "A utility library for mocking out the `requests` Python library." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c"}, - {file = "responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4"}, -] - -[package.dependencies] -pyyaml = "*" -requests = ">=2.30.0,<3.0" -urllib3 = ">=1.25.10,<3.0" - -[package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] - -[[package]] -name = "respx" -version = "0.22.0" -description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, - {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, -] - -[package.dependencies] -httpx = ">=0.25.0" - -[[package]] -name = "rich" -version = "13.9.4" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["dev"] -files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "ruamel-yaml" -version = "0.18.6" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, - {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, -] - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} - -[package.extras] -docs = ["mercurial (>5.7)", "ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.8" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" -files = [ - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, - {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, - {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, -] - -[[package]] -name = "ruff" -version = "0.12.12" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc"}, - {file = "ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727"}, - {file = "ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45"}, - {file = "ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489"}, - {file = "ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee"}, - {file = "ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1"}, - {file = "ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d"}, - {file = "ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093"}, - {file = "ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6"}, -] - -[[package]] -name = "safety" -version = "3.2.9" -description = "Checks installed dependencies for known vulnerabilities and licenses." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "safety-3.2.9-py3-none-any.whl", hash = "sha256:5e199c057550dc6146c081084274279dfb98c17735193b028db09a55ea508f1a"}, - {file = "safety-3.2.9.tar.gz", hash = "sha256:494bea752366161ac9e0742033d2a82e4dc51d7c788be42e0ecf5f3ef36b8071"}, -] - -[package.dependencies] -Authlib = ">=1.2.0" -Click = ">=8.0.2" -dparse = ">=0.6.4b0" -filelock = ">=3.12.2,<3.13.0" -jinja2 = ">=3.1.0" -marshmallow = ">=3.15.0" -packaging = ">=21.0" -psutil = ">=6.0.0,<6.1.0" -pydantic = ">=1.10.12" -requests = "*" -rich = "*" -"ruamel.yaml" = ">=0.17.21" -safety-schemas = ">=0.0.4" -setuptools = ">=65.5.1" -typer = "*" -typing-extensions = ">=4.7.1" -urllib3 = ">=1.26.5" - -[package.extras] -github = ["pygithub (>=1.43.3)"] -gitlab = ["python-gitlab (>=1.3.0)"] -spdx = ["spdx-tools (>=0.8.2)"] - -[[package]] -name = "safety-schemas" -version = "0.0.5" -description = "Schemas for Safety tools" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "safety_schemas-0.0.5-py3-none-any.whl", hash = "sha256:6ac9eb71e60f0d4e944597c01dd48d6d8cd3d467c94da4aba3702a05a3a6ab4f"}, - {file = "safety_schemas-0.0.5.tar.gz", hash = "sha256:0de5fc9a53d4423644a8ce9a17a2e474714aa27e57f3506146e95a41710ff104"}, -] - -[package.dependencies] -dparse = ">=0.6.4b0" -packaging = ">=21.0" -pydantic = "*" -ruamel-yaml = ">=0.17.21" -typing-extensions = ">=4.7.1" - -[[package]] -name = "setuptools" -version = "75.3.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, - {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""] -core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.12.*)", "pytest-mypy"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "soupsieve" -version = "2.6" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, -] - -[[package]] -name = "sphinx" -version = "7.1.2" -description = "Python documentation generator" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, - {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinx-basic-ng" -version = "1.0.0b2" -description = "A modern skeleton for Sphinx themes." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, - {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, -] - -[package.dependencies] -sphinx = ">=4.0" - -[package.extras] -docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "starlette" -version = "0.41.3" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, - {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "tokenize-rt" -version = "6.0.0" -description = "A wrapper around the stdlib `tokenize` which roundtrips." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "tokenize_rt-6.0.0-py2.py3-none-any.whl", hash = "sha256:d4ff7ded2873512938b4f8cbb98c9b07118f01d30ac585a30d7a88353ca36d22"}, - {file = "tokenize_rt-6.0.0.tar.gz", hash = "sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367"}, -] - -[[package]] -name = "tomli" -version = "2.1.0" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, -] -markers = {main = "python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""} - -[[package]] -name = "ty" -version = "0.0.1a20" -description = "An extremely fast Python type checker, written in Rust." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "ty-0.0.1a20-py3-none-linux_armv6l.whl", hash = "sha256:f73a7aca1f0d38af4d6999b375eb00553f3bfcba102ae976756cc142e14f3450"}, - {file = "ty-0.0.1a20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cad12c857ea4b97bf61e02f6796e13061ccca5e41f054cbd657862d80aa43bae"}, - {file = "ty-0.0.1a20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f153b65c7fcb6b8b59547ddb6353761b3e8d8bb6f0edd15e3e3ac14405949f7a"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c4336987a6a781d4392a9fd7b3a39edb7e4f3dd4f860e03f46c932b52aefa2"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ff75cd4c744d09914e8c9db8d99e02f82c9379ad56b0a3fc4c5c9c923cfa84e"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e26437772be7f7808868701f2bf9e14e706a6ec4c7d02dbd377ff94d7ba60c11"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:83a7ee12465841619b5eb3ca962ffc7d576bb1c1ac812638681aee241acbfbbe"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:726d0738be4459ac7ffae312ba96c5f486d6cbc082723f322555d7cba9397871"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b481f26513f38543df514189fb16744690bcba8d23afee95a01927d93b46e36"}, - {file = "ty-0.0.1a20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7abbe3c02218c12228b1d7c5f98c57240029cc3bcb15b6997b707c19be3908c1"}, - {file = "ty-0.0.1a20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fff51c75ee3f7cc6d7722f2f15789ef8ffe6fd2af70e7269ac785763c906688e"}, - {file = "ty-0.0.1a20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b4124ab75e0e6f09fe7bc9df4a77ee43c5e0ef7e61b0c149d7c089d971437cbd"}, - {file = "ty-0.0.1a20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8a138fa4f74e6ed34e9fd14652d132409700c7ff57682c2fed656109ebfba42f"}, - {file = "ty-0.0.1a20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8eff8871d6b88d150e2a67beba2c57048f20c090c219f38ed02eebaada04c124"}, - {file = "ty-0.0.1a20-py3-none-win32.whl", hash = "sha256:3c2ace3a22fab4bd79f84c74e3dab26e798bfba7006bea4008d6321c1bd6efc6"}, - {file = "ty-0.0.1a20-py3-none-win_amd64.whl", hash = "sha256:f41e77ff118da3385915e13c3f366b3a2f823461de54abd2e0ca72b170ba0f19"}, - {file = "ty-0.0.1a20-py3-none-win_arm64.whl", hash = "sha256:d8ac1c5a14cda5fad1a8b53959d9a5d979fe16ce1cc2785ea8676fed143ac85f"}, - {file = "ty-0.0.1a20.tar.gz", hash = "sha256:933b65a152f277aa0e23ba9027e5df2c2cc09e18293e87f2a918658634db5f15"}, -] - -[[package]] -name = "typeguard" -version = "4.4.0" -description = "Run-time type checker for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "typeguard-4.4.0-py3-none-any.whl", hash = "sha256:8ca34c14043f53b2caae7040549ba431770869bcd6287cfa8239db7ecb882b4a"}, - {file = "typeguard-4.4.0.tar.gz", hash = "sha256:463bd8697a65a4aa576a63767c369b1ecfba8a5ba735edfe3223127b6ecfa28c"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} -typing-extensions = ">=4.10.0" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] -test = ["coverage[toml] (>=7)", "mypy (>=1.2.0) ; platform_python_implementation != \"PyPy\"", "pytest (>=7)"] - -[[package]] -name = "typer" -version = "0.13.1" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "typer-0.13.1-py3-none-any.whl", hash = "sha256:5b59580fd925e89463a29d363e0a43245ec02765bde9fb77d39e5d0f29dd7157"}, - {file = "typer-0.13.1.tar.gz", hash = "sha256:9d444cb96cc268ce6f8b94e13b4335084cef4c079998a9f4851a90229a3bd25c"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.18.3" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, - {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.0)"] - -[[package]] -name = "virtualenv" -version = "20.28.0" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, - {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "xdoctest" -version = "1.2.0" -description = "A rewrite of the builtin doctest module" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "xdoctest-1.2.0-py3-none-any.whl", hash = "sha256:0f1ecf5939a687bd1fc8deefbff1743c65419cce26dff908f8b84c93fbe486bc"}, - {file = "xdoctest-1.2.0.tar.gz", hash = "sha256:d8cfca6d8991e488d33f756e600d35b9fdf5efd5c3a249d644efcbbbd2ed5863"}, -] - -[package.dependencies] -colorama = {version = ">=0.4.1", optional = true, markers = "platform_system == \"Windows\" and extra == \"colors\""} -Pygments = {version = ">=2.4.1", optional = true, markers = "python_version >= \"3.5.0\" and extra == \"colors\""} - -[package.extras] -all = ["IPython (>=7.23.1)", "Pygments (>=2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (>=2.4.1) ; python_version >= \"3.5.0\"", "attrs (>=19.2.0)", "colorama (>=0.4.1) ; platform_system == \"Windows\"", "debugpy (>=1.0.0) ; python_version == \"3.8\"", "debugpy (>=1.3.0) ; python_version == \"3.9\"", "debugpy (>=1.6.0) ; python_version >= \"3.10\"", "ipykernel (>=6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (>=6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (>=0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (>=0.16)", "jinja2 (>=3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (>=6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\"", "pyflakes (>=2.2.0)", "pytest (>=4.6.0) ; python_version < \"3.10.0\" and python_version >= \"3.7.0\"", "pytest (>=6.2.5) ; python_version >= \"3.10.0\"", "pytest-cov (>=3.0.0) ; python_version >= \"3.6.0\"", "tomli (>=0.2.0) ; python_version < \"3.11.0\" and python_version >= \"3.6\""] -all-strict = ["IPython (==7.23.1)", "Pygments (==2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (==2.4.1) ; python_version >= \"3.5.0\"", "attrs (==19.2.0)", "colorama (==0.4.1) ; platform_system == \"Windows\"", "debugpy (==1.0.0) ; python_version == \"3.8\"", "debugpy (==1.3.0) ; python_version == \"3.9\"", "debugpy (==1.6.0) ; python_version >= \"3.10\"", "ipykernel (==6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (==6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (==0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (==0.16)", "jinja2 (==3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (==6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\"", "pyflakes (==2.2.0)", "pytest (==4.6.0) ; python_version < \"3.10.0\" and python_version >= \"3.7.0\"", "pytest (==6.2.5) ; python_version >= \"3.10.0\"", "pytest-cov (==3.0.0) ; python_version >= \"3.6.0\"", "tomli (==0.2.0) ; python_version < \"3.11.0\" and python_version >= \"3.6\""] -colors = ["Pygments (>=2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (>=2.4.1) ; python_version >= \"3.5.0\"", "colorama (>=0.4.1) ; platform_system == \"Windows\""] -colors-strict = ["Pygments (==2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (==2.4.1) ; python_version >= \"3.5.0\"", "colorama (==0.4.1) ; platform_system == \"Windows\""] -docs = ["Pygments (>=2.9.0)", "myst-parser (>=0.18.0)", "sphinx (>=5.0.1)", "sphinx-autoapi (>=1.8.4)", "sphinx-autobuild (>=2021.3.14)", "sphinx-reredirects (>=0.0.1)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-napoleon (>=0.7)"] -docs-strict = ["Pygments (==2.9.0)", "myst-parser (==0.18.0)", "sphinx (==5.0.1)", "sphinx-autoapi (==1.8.4)", "sphinx-autobuild (==2021.3.14)", "sphinx-reredirects (==0.0.1)", "sphinx-rtd-theme (==1.0.0)", "sphinxcontrib-napoleon (==0.7)"] -jupyter = ["IPython (>=7.23.1)", "attrs (>=19.2.0)", "debugpy (>=1.0.0) ; python_version == \"3.8\"", "debugpy (>=1.3.0) ; python_version == \"3.9\"", "debugpy (>=1.6.0) ; python_version >= \"3.10\"", "ipykernel (>=6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (>=6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (>=0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (>=0.16)", "jinja2 (>=3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (>=6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\""] -jupyter-strict = ["IPython (==7.23.1)", "attrs (==19.2.0)", "debugpy (==1.0.0) ; python_version == \"3.8\"", "debugpy (==1.3.0) ; python_version == \"3.9\"", "debugpy (==1.6.0) ; python_version >= \"3.10\"", "ipykernel (==6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (==6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (==0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (==0.16)", "jinja2 (==3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (==6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\""] -optional = ["IPython (>=7.23.1)", "Pygments (>=2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (>=2.4.1) ; python_version >= \"3.5.0\"", "attrs (>=19.2.0)", "colorama (>=0.4.1) ; platform_system == \"Windows\"", "debugpy (>=1.0.0) ; python_version == \"3.8\"", "debugpy (>=1.3.0) ; python_version == \"3.9\"", "debugpy (>=1.6.0) ; python_version >= \"3.10\"", "ipykernel (>=6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (>=6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (>=0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (>=0.16)", "jinja2 (>=3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (>=6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\"", "pyflakes (>=2.2.0)", "tomli (>=0.2.0) ; python_version < \"3.11.0\" and python_version >= \"3.6\""] -optional-strict = ["IPython (==7.23.1)", "Pygments (==2.0.0) ; python_version < \"3.5.0\" and python_version >= \"2.7.0\"", "Pygments (==2.4.1) ; python_version >= \"3.5.0\"", "attrs (==19.2.0)", "colorama (==0.4.1) ; platform_system == \"Windows\"", "debugpy (==1.0.0) ; python_version == \"3.8\"", "debugpy (==1.3.0) ; python_version == \"3.9\"", "debugpy (==1.6.0) ; python_version >= \"3.10\"", "ipykernel (==6.0.0) ; python_version < \"3.12\" and python_version >= \"3.7\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipykernel (==6.11.0) ; python_version < \"4.0\" and python_version >= \"3.12\" and (platform_system != \"Windows\" or platform_python_implementation != \"PyPy\")", "ipython-genutils (==0.2.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jedi (==0.16)", "jinja2 (==3.0.0) ; python_version >= \"3.6\" and platform_python_implementation != \"PyPy\"", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0) ; python_version < \"3.7.0\" and python_version >= \"3.6.0\" and platform_python_implementation != \"PyPy\"", "nbconvert (==6.1.0) ; python_version >= \"3.7.0\" and platform_python_implementation != \"PyPy\"", "pyflakes (==2.2.0)", "tomli (==0.2.0) ; python_version < \"3.11.0\" and python_version >= \"3.6\""] -tests = ["pytest (>=4.6.0) ; python_version < \"3.10.0\" and python_version >= \"3.7.0\"", "pytest (>=6.2.5) ; python_version >= \"3.10.0\"", "pytest-cov (>=3.0.0) ; python_version >= \"3.6.0\""] -tests-binary = ["cmake (>=3.21.2) ; python_version < \"3.11\"", "cmake (>=3.25.0) ; python_version < \"4.0\" and python_version >= \"3.11\"", "ninja (>=1.10.2) ; python_version < \"3.11\"", "ninja (>=1.11.1) ; python_version < \"4.0\" and python_version >= \"3.11\"", "pybind11 (>=2.10.3) ; python_version < \"4.0\" and python_version >= \"3.11\"", "pybind11 (>=2.7.1) ; python_version < \"3.11\"", "scikit-build (>=0.11.1) ; python_version < \"3.11\"", "scikit-build (>=0.16.1) ; python_version < \"4.0\" and python_version >= \"3.11\""] -tests-binary-strict = ["cmake (==3.21.2) ; python_version < \"3.11\"", "cmake (==3.25.0) ; python_version < \"4.0\" and python_version >= \"3.11\"", "ninja (==1.10.2) ; python_version < \"3.11\"", "ninja (==1.11.1) ; python_version < \"4.0\" and python_version >= \"3.11\"", "pybind11 (==2.10.3) ; python_version < \"4.0\" and python_version >= \"3.11\"", "pybind11 (==2.7.1) ; python_version < \"3.11\"", "scikit-build (==0.11.1) ; python_version < \"3.11\"", "scikit-build (==0.16.1) ; python_version < \"4.0\" and python_version >= \"3.11\""] -tests-strict = ["pytest (==4.6.0) ; python_version < \"3.10.0\" and python_version >= \"3.7.0\"", "pytest (==6.2.5) ; python_version >= \"3.10.0\"", "pytest-cov (==3.0.0) ; python_version >= \"3.6.0\""] - -[[package]] -name = "yarl" -version = "1.15.2" -description = "Yet another URL library" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, - {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, - {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, - {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, - {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, - {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, - {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, - {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, - {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, - {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, - {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, - {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, - {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, - {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, - {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, - {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, - {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, - {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, - {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, - {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, - {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, - {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, - {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, - {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, - {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, - {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, - {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, - {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, - {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, - {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, - {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, - {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, - {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, - {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, - {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, - {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, - {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, - {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, - {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, - {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, - {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, - {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, - {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, - {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, - {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, - {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, - {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, - {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, - {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, - {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, - {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, - {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, - {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, - {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, - {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, - {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, - {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, - {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, - {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, - {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, - {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, - {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, - {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, - {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, - {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, - {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, - {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, - {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" - -[[package]] -name = "zipp" -version = "3.20.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.9" -content-hash = "aa1fb69614a0c82d7d5df2ef06298aa9b41532911f5d7e713888818581b351f4" diff --git a/pyproject.toml b/pyproject.toml index 19bb714..ccb3b05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,59 +1,81 @@ -[tool.poetry] -name = "openapi-python-generator" -version = "v2.1.2" +[project] +name = "pydantic-openapi-generator" +version = "2.6.0" description = "Openapi Python Generator" -authors = ["Marco MΓΌllner "] -license = "MIT" +authors = [ + { name = "Marco MΓΌllner", email = "muellnermarco@gmail.com" }, + { name = "Matt Coulter", email = "mattcoul7@gmail.com" }, +] +requires-python = ">=3.12,<4" readme = "README.md" -homepage = "https://github.com/MarcoMuellner/openapi-python-generator" -repository = "https://github.com/MarcoMuellner/openapi-python-generator" -documentation = "https://openapi-python-generator.readthedocs.io" -classifiers = [ - "Development Status :: 3 - Alpha", +license = "MIT" +keywords = [ + "OpenAPI", + "Generator", + "Python", + "async", ] -keywords = ["OpenAPI", "Generator", "Python", "async"] +classifiers = ["Development Status :: 3 - Alpha"] +dependencies = [ + "httpx[all]>=0.28.0,<0.29", + "pydantic>=2.10.2,<3", + "orjson>=3.9.15,<4", + "Jinja2>=3.1.2,<4", + "click>=8.1.3,<9", + "black>=21.10b0", + "isort>=5.10.1", + "openapi-pydantic>=0.5.1,<0.6", + "pyyaml>=6.0.2,<7", + "importlib-metadata>=8.7.0,<9", + "pydantic-di>=0.3.1", +] + +[project.urls] +Homepage = "https://github.com/mattcoulter7/pydantic-openapi-generator" +Repository = "https://github.com/mattcoulter7/pydantic-openapi-generator" +Documentation = "https://github.com/mattcoulter7/pydantic-openapi-generator" +Changelog = "https://github.com/mattcoulter7/pydantic-openapi-generator/releases" -[tool.poetry.urls] -Changelog = "https://github.com/MarcoMuellner/openapi-python-generator/releases" +[project.scripts] +pydantic-openapi-generator = "pydantic_openapi_generator.__main__:main" -[tool.poetry.dependencies] -python = "^3.9" -httpx = {extras = ["all"], version = "^0.28.0"} -pydantic = "^2.10.2" -orjson = "^3.9.15" -Jinja2 = "^3.1.2" -click = "^8.1.3" -black = ">=21.10b0" -isort = ">=5.10.1" -openapi-pydantic = "^0.5.1" -pyyaml = "^6.0.2" -importlib-metadata = "^8.7.0" +[dependency-groups] +dev = [ + "debugpy>=1.8.14,<2", + "isort>=6.0.1,<7", + "Pygments>=2.10.0", + "coverage[toml]>=7.10.0,<8", + "darglint>=1.8.1", + "ruff>=0.12.12,<0.17", + "furo>=2021.11.12", + "ty>=0.0.1a20,<0.0.2", + "pep8-naming>=0.10.1", + "pre-commit>=2.16.0", + "pre-commit-hooks>=4.1.0", + "pytest>=6.2.5", + "pyupgrade>=2.29.1", + "safety>=1.10.3", + "typeguard>=2.13.3", + "xdoctest[colors]>=0.15.10", + "myst-parser>=0.16.1", + "pytest-cov>=7.1.0,<8", + "pytest-asyncio>=1.4.0,<2", + "fastapi>=0.115.5,<0.116", + "uvicorn>=0.18.1,<0.19", + "respx>=0.22.0,<0.23", + "aiohttp>=3.8.3,<4", + "responses>=0.25.8,<0.26" +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" -[tool.poetry.group.dev.dependencies] -Pygments = ">=2.10.0" -coverage = {extras = ["toml"], version = "^6.4.1"} -darglint = ">=1.8.1" -ruff = ">=0.12.12" -furo = ">=2021.11.12" -ty = "^0.0.1a20" -pep8-naming = ">=0.10.1" -pre-commit = ">=2.16.0" -pre-commit-hooks = ">=4.1.0" -pytest = ">=6.2.5" -pyupgrade = ">=2.29.1" -safety = ">=1.10.3" -typeguard = ">=2.13.3" -xdoctest = {extras = ["colors"], version = ">=0.15.10"} -myst-parser = {version = ">=0.16.1"} -pytest-cov = "^3.0.0" -fastapi = "^0.115.5" -uvicorn = "^0.18.1" -respx = "^0.22.0" -aiohttp = "^3.8.3" -responses = "^0.25.8" +[tool.hatch.build.targets.sdist] +include = ["src/pydantic_openapi_generator"] -[tool.poetry.scripts] -openapi-python-generator = "openapi_python_generator.__main__:main" +[tool.hatch.build.targets.wheel] +packages = ["src/pydantic_openapi_generator"] [tool.coverage.paths] @@ -62,7 +84,7 @@ tests = ["tests", "*/tests"] [tool.coverage.run] branch = true -source = ["openapi_python_generator", "tests"] +source = ["pydantic_openapi_generator", "tests"] [tool.coverage.report] show_missing = true @@ -81,10 +103,6 @@ show_column_numbers = true show_error_codes = true show_error_context = true -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - [tool.ruff] exclude = ["tests/*"] line-length = 120 diff --git a/src/openapi_python_generator/generate_data.py b/src/openapi_python_generator/generate_data.py deleted file mode 100644 index 9630e45..0000000 --- a/src/openapi_python_generator/generate_data.py +++ /dev/null @@ -1,235 +0,0 @@ -from pathlib import Path -from typing import List, Optional, Union - -import black -import click -import httpx -import isort -import orjson -import yaml # type: ignore -from black.report import NothingChanged # type: ignore -from httpx import ConnectError, ConnectTimeout -from pydantic import ValidationError - -from .common import FormatOptions, Formatter, HTTPLibrary, PydanticVersion -from .language_converters.python.jinja_config import SERVICE_TEMPLATE, create_jinja_env -from .models import ConversionResult -from .parsers import ( - generate_code_3_0, - generate_code_3_1, - parse_openapi_3_0, - parse_openapi_3_1, -) -from .version_detector import detect_openapi_version - - -def write_code(path: Path, content: str, formatter: Formatter) -> None: - """ - Write the content to the file at the given path. - :param path: The path to the file. - :param content: The content to write. - :param formatter: The formatter applied to the code written. - """ - if formatter == Formatter.BLACK: - formatted_contend = format_using_black(content) - elif formatter == Formatter.NONE: - formatted_contend = content - else: - raise NotImplementedError( - f"Missing implementation for formatter {formatter!r}." - ) - with open(path, "w") as f: - f.write(formatted_contend) - - -def format_using_black(content: str) -> str: - try: - formatted_contend = black.format_file_contents( - content, - fast=FormatOptions.skip_validation, - mode=black.FileMode(line_length=FormatOptions.line_length), - ) - except NothingChanged: - return content - return isort.code(formatted_contend, line_length=FormatOptions.line_length) - - -def get_open_api(source: Union[str, Path]): - """ - Tries to fetch the openapi specification file from the web or load from a local file. - Supports both JSON and YAML formats. Returns the according OpenAPI object. - Automatically supports OpenAPI 3.0 and 3.1 specifications with intelligent version detection. - - Args: - source: URL or file path to the OpenAPI specification - - Returns: - tuple: (OpenAPI object, version) where version is "3.0" or "3.1" - - Raises: - FileNotFoundError: If the specified file cannot be found - ConnectError: If the URL cannot be accessed - ValidationError: If the specification is invalid - JSONDecodeError/YAMLError: If the file cannot be parsed - """ - try: - # Handle remote files - if not isinstance(source, Path) and ( - source.startswith("http://") or source.startswith("https://") - ): - content = httpx.get(source).text - # Try JSON first, then YAML for remote files - try: - data = orjson.loads(content) - except orjson.JSONDecodeError: - data = yaml.safe_load(content) - else: - # Handle local files - with open(source, "r") as f: - file_content = f.read() - - # Try JSON first - try: - data = orjson.loads(file_content) - except orjson.JSONDecodeError: - # If JSON fails, try YAML - try: - data = yaml.safe_load(file_content) - except yaml.YAMLError as e: - click.echo( - f"File {source} is neither a valid JSON nor YAML file: {str(e)}" - ) - raise - - # Detect version and parse with appropriate parser - version = detect_openapi_version(data) - - if version == "3.0": - openapi_obj = parse_openapi_3_0(data) # type: ignore[assignment] - elif version == "3.1": - openapi_obj = parse_openapi_3_1(data) # type: ignore[assignment] - else: - # Unsupported version detected (version detection already limited to 3.0 / 3.1) - raise ValueError( - f"Unsupported OpenAPI version: {version}. Only 3.0.x and 3.1.x are supported." - ) - - return openapi_obj, version - - except FileNotFoundError: - click.echo( - f"File {source} not found. Please make sure to pass the path to the OpenAPI specification." - ) - raise - except (ConnectError, ConnectTimeout): - click.echo(f"Could not connect to {source}.") - raise ConnectError(f"Could not connect to {source}.") from None - except ValidationError: - click.echo(f"File {source} is not a valid OpenAPI 3.0+ specification.") - raise - - -def write_data( - data: ConversionResult, output: Union[str, Path], formatter: Formatter -) -> None: - """ - This function will firstly create the folder structure of output, if it doesn't exist. Then it will create the - models from data.models into the models sub module of the output folder. After this, the services will be created - into the services sub module of the output folder. - :param data: The data to write. - :param output: The path to the output folder. - :param formatter: The formatter applied to the code written. - """ - - # Create the folder structure of the output folder. - Path(output).mkdir(parents=True, exist_ok=True) - - # Create the models module. - models_path = Path(output) / "models" - models_path.mkdir(parents=True, exist_ok=True) - - # Create the services module. - services_path = Path(output) / "services" - services_path.mkdir(parents=True, exist_ok=True) - - files: List[str] = [] - - # Write the models. - for model in data.models: - files.append(model.file_name) - write_code(models_path / f"{model.file_name}.py", model.content, formatter) - - # Create models.__init__.py file containing imports to all models. - write_code( - models_path / "__init__.py", - "\n".join([f"from .{file} import *" for file in files]), - formatter, - ) - - files = [] - - # Write the services. - jinja_env = create_jinja_env() - for service in data.services: - if len(service.operations) == 0: - continue - files.append(service.file_name) - write_code( - services_path / f"{service.file_name}.py", - jinja_env.get_template(SERVICE_TEMPLATE).render(**service.model_dump()), - formatter, - ) - - # Create services.__init__.py file containing imports to all services. - write_code(services_path / "__init__.py", "", formatter) - - # Write the api_config.py file. - write_code(Path(output) / "api_config.py", data.api_config.content, formatter) - - # Write the __init__.py file. - write_code( - Path(output) / "__init__.py", - "from .models import *\nfrom .services import *\nfrom .api_config import *", - formatter, - ) - - -def generate_data( - source: Union[str, Path], - output: Union[str, Path], - library: HTTPLibrary = HTTPLibrary.httpx, - env_token_name: Optional[str] = None, - use_orjson: bool = False, - custom_template_path: Optional[str] = None, - pydantic_version: PydanticVersion = PydanticVersion.V2, - formatter: Formatter = Formatter.BLACK, -) -> None: - """ - Generate Python code from an OpenAPI 3.0+ specification. - """ - openapi_obj, version = get_open_api(source) - click.echo(f"Generating data from {source} (OpenAPI {version})") - - # Use version-specific generator - if version == "3.0": - result = generate_code_3_0( - openapi_obj, # type: ignore - library, - env_token_name, - use_orjson, - custom_template_path, - pydantic_version, - ) - elif version == "3.1": - result = generate_code_3_1( - openapi_obj, # type: ignore - library, - env_token_name, - use_orjson, - custom_template_path, - pydantic_version, - ) - else: - raise ValueError(f"Unsupported OpenAPI version: {version}") - - write_data(result, output, formatter) diff --git a/src/openapi_python_generator/language_converters/python/api_config_generator.py b/src/openapi_python_generator/language_converters/python/api_config_generator.py deleted file mode 100644 index 634b3cd..0000000 --- a/src/openapi_python_generator/language_converters/python/api_config_generator.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Optional - -from openapi_pydantic.v3 import OpenAPI - -from openapi_python_generator.common import PydanticVersion -from openapi_python_generator.language_converters.python.jinja_config import ( - API_CONFIG_TEMPLATE, - API_CONFIG_TEMPLATE_PYDANTIC_V2, - create_jinja_env, -) -from openapi_python_generator.models import APIConfig - - -def generate_api_config( - data: OpenAPI, - env_token_name: Optional[str] = None, - pydantic_version: PydanticVersion = PydanticVersion.V2, -) -> APIConfig: - """ - Generate the API model. - """ - - template_name = ( - API_CONFIG_TEMPLATE_PYDANTIC_V2 - if pydantic_version == PydanticVersion.V2 - else API_CONFIG_TEMPLATE - ) - jinja_env = create_jinja_env() - return APIConfig( - file_name="api_config", - content=jinja_env.get_template(template_name).render( - env_token_name=env_token_name, **data.model_dump() - ), - base_url=data.servers[0].url if len(data.servers) > 0 else "NO SERVER", - ) diff --git a/src/openapi_python_generator/language_converters/python/generator.py b/src/openapi_python_generator/language_converters/python/generator.py deleted file mode 100644 index 0760ff3..0000000 --- a/src/openapi_python_generator/language_converters/python/generator.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import Optional, Union - -from openapi_pydantic.v3.v3_0 import OpenAPI as OpenAPI30 -from openapi_pydantic.v3.v3_1 import OpenAPI as OpenAPI31 - -from openapi_python_generator.common import PydanticVersion -from openapi_python_generator.language_converters.python import common -from openapi_python_generator.language_converters.python.api_config_generator import ( - generate_api_config, -) -from openapi_python_generator.language_converters.python.model_generator import ( - generate_models, -) -from openapi_python_generator.language_converters.python.service_generator import ( - generate_services, -) -from openapi_python_generator.models import ConversionResult, LibraryConfig - -# Type alias for both OpenAPI versions -OpenAPISpec = Union[OpenAPI30, OpenAPI31] - - -def generator( - data: OpenAPISpec, - library_config: LibraryConfig, - env_token_name: Optional[str] = None, - use_orjson: bool = False, - custom_template_path: Optional[str] = None, - pydantic_version: PydanticVersion = PydanticVersion.V2, -) -> ConversionResult: - """ - Generate Python code from an OpenAPI 3.0+ specification. - """ - - common.set_use_orjson(use_orjson) - common.set_custom_template_path(custom_template_path) - - if data.components is not None: - models = generate_models(data.components, pydantic_version) - else: - models = [] - - if data.paths is not None: - services = generate_services(data.paths, library_config) - else: - services = [] - - api_config = generate_api_config(data, env_token_name, pydantic_version) - - return ConversionResult( - models=models, - services=services, - api_config=api_config, - ) diff --git a/src/openapi_python_generator/language_converters/python/model_generator.py b/src/openapi_python_generator/language_converters/python/model_generator.py deleted file mode 100644 index d494b1d..0000000 --- a/src/openapi_python_generator/language_converters/python/model_generator.py +++ /dev/null @@ -1,476 +0,0 @@ -import itertools -from typing import List, Optional, Union - -import click -from openapi_pydantic.v3.v3_0 import ( - Components as Components30, -) -from openapi_pydantic.v3.v3_0 import ( - Reference as Reference30, -) -from openapi_pydantic.v3.v3_0 import ( - Schema as Schema30, -) -from openapi_pydantic.v3.v3_1 import ( - Components as Components31, -) -from openapi_pydantic.v3.v3_1 import ( - Reference as Reference31, -) -from openapi_pydantic.v3.v3_1 import ( - Schema as Schema31, -) - -from openapi_python_generator.common import PydanticVersion -from openapi_python_generator.language_converters.python import common -from openapi_python_generator.language_converters.python.jinja_config import ( - ENUM_TEMPLATE, - MODELS_TEMPLATE, - MODELS_TEMPLATE_PYDANTIC_V2, - create_jinja_env, -) -from openapi_python_generator.models import Model, Property, TypeConversion - -# Type aliases for compatibility -Schema = Union[Schema30, Schema31] -Reference = Union[Reference30, Reference31] -Components = Union[Components30, Components31] - - -def type_converter( # noqa: C901 - schema: Union[Schema, Reference], - required: bool = False, - model_name: Optional[str] = None, -) -> TypeConversion: - """ - Converts an OpenAPI type to a Python type. - :param schema: Schema or Reference containing the type to be converted - :param model_name: Name of the original model on which the type is defined - :param required: Flag indicating if the type is required by the class - :return: The converted type - """ - # Handle Reference objects by converting them to type references - if isinstance(schema, Reference30) or isinstance(schema, Reference31): - import_type = common.normalize_symbol(schema.ref.split("/")[-1]) - if required: - converted_type = import_type - else: - converted_type = f"Optional[{import_type}]" - - return TypeConversion( - original_type=schema.ref, - converted_type=converted_type, - import_types=( - [f"from .{import_type} import {import_type}"] - if import_type != model_name - else None - ), - ) - - if required: - pre_type = "" - post_type = "" - else: - pre_type = "Optional[" - post_type = "]" - - original_type = ( - schema.type.value - if hasattr(schema.type, "value") and schema.type is not None - else str(schema.type) if schema.type is not None else "object" - ) - import_types: Optional[List[str]] = None - - if schema.allOf is not None: - conversions = [] - for sub_schema in schema.allOf: - if isinstance(sub_schema, Schema30) or isinstance(sub_schema, Schema31): - conversions.append(type_converter(sub_schema, True)) - else: - import_type = common.normalize_symbol(sub_schema.ref.split("/")[-1]) - if import_type == model_name and model_name is not None: - conversions.append( - TypeConversion( - original_type=sub_schema.ref, - converted_type='"' + model_name + '"', - import_types=None, - ) - ) - else: - import_types = [f"from .{import_type} import {import_type}"] - conversions.append( - TypeConversion( - original_type=sub_schema.ref, - converted_type=import_type, - import_types=import_types, - ) - ) - - original_type = ( - "tuple<" + ",".join([i.original_type for i in conversions]) + ">" - ) - if len(conversions) == 1: - converted_type = conversions[0].converted_type - else: - converted_type = ( - "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]" - ) - - converted_type = pre_type + converted_type + post_type - # Collect first import from referenced sub-schemas only (skip empty lists) - import_types = [ - i.import_types[0] - for i in conversions - if i.import_types is not None and len(i.import_types) > 0 - ] or None - - elif schema.oneOf is not None or schema.anyOf is not None: - used = schema.oneOf if schema.oneOf is not None else schema.anyOf - used = used if used is not None else [] - conversions = [] - for sub_schema in used: - if isinstance(sub_schema, Schema30) or isinstance(sub_schema, Schema31): - conversions.append(type_converter(sub_schema, True)) - else: - import_type = common.normalize_symbol(sub_schema.ref.split("/")[-1]) - import_types = [f"from .{import_type} import {import_type}"] - conversions.append( - TypeConversion( - original_type=sub_schema.ref, - converted_type=import_type, - import_types=import_types, - ) - ) - original_type = ( - "union<" + ",".join([i.original_type for i in conversions]) + ">" - ) - - if len(conversions) == 1: - converted_type = conversions[0].converted_type - else: - converted_type = ( - "Union[" + ",".join([i.converted_type for i in conversions]) + "]" - ) - - converted_type = pre_type + converted_type + post_type - import_types = list( - itertools.chain( - *[i.import_types for i in conversions if i.import_types is not None] - ) - ) - # We only want to auto convert to datetime if orjson is used throghout the code, otherwise we can not - # serialize it to JSON. - elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and ( - schema.schema_format is None or not common.get_use_orjson() - ): - converted_type = pre_type + "str" + post_type - elif ( - (schema.type == "string" or str(schema.type) == "DataType.STRING") - and schema.schema_format is not None - and schema.schema_format.startswith("uuid") - and common.get_use_orjson() - ): - if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric(): - uuid_type = schema.schema_format.upper() - converted_type = pre_type + uuid_type + post_type - import_types = ["from pydantic import " + uuid_type] - else: - converted_type = pre_type + "UUID" + post_type - import_types = ["from uuid import UUID"] - elif ( - schema.type == "string" or str(schema.type) == "DataType.STRING" - ) and schema.schema_format == "date-time": - converted_type = pre_type + "datetime" + post_type - import_types = ["from datetime import datetime"] - elif schema.type == "integer" or str(schema.type) == "DataType.INTEGER": - converted_type = pre_type + "int" + post_type - elif schema.type == "number" or str(schema.type) == "DataType.NUMBER": - converted_type = pre_type + "float" + post_type - elif schema.type == "boolean" or str(schema.type) == "DataType.BOOLEAN": - converted_type = pre_type + "bool" + post_type - elif schema.type == "array" or str(schema.type) == "DataType.ARRAY": - retVal = pre_type + "List[" - if isinstance(schema.items, Reference30) or isinstance( - schema.items, Reference31 - ): - converted_reference = _generate_property_from_reference( - model_name or "", "", schema.items, schema, required - ) - import_types = converted_reference.type.import_types - original_type = "array<" + converted_reference.type.original_type + ">" - retVal += converted_reference.type.converted_type - elif isinstance(schema.items, Schema30) or isinstance(schema.items, Schema31): - type_str = schema.items.type - if hasattr(type_str, "value"): - type_value = str(type_str.value) if type_str is not None else "unknown" - else: - type_value = str(type_str) if type_str is not None else "unknown" - original_type = "array<" + type_value + ">" - retVal += type_converter(schema.items, True).converted_type - else: - original_type = "array" - retVal += "Any" - - converted_type = retVal + "]" + post_type - elif schema.type == "object" or str(schema.type) == "DataType.OBJECT": - converted_type = pre_type + "Dict[str, Any]" + post_type - elif schema.type == "null" or str(schema.type) == "DataType.NULL": - converted_type = pre_type + "None" + post_type - elif schema.type is None: - converted_type = pre_type + "Any" + post_type - else: - # Handle DataType enum types as strings - if hasattr(schema.type, "value"): - # Single DataType enum - if schema.type.value == "string": - # Check for UUID format first - if ( - schema.schema_format is not None - and schema.schema_format.startswith("uuid") - and common.get_use_orjson() - ): - if ( - len(schema.schema_format) > 4 - and schema.schema_format[4].isnumeric() - ): - uuid_type = schema.schema_format.upper() - converted_type = pre_type + uuid_type + post_type - import_types = ["from pydantic import " + uuid_type] - else: - converted_type = pre_type + "UUID" + post_type - import_types = ["from uuid import UUID"] - # Check for date-time format - elif schema.schema_format == "date-time": - converted_type = pre_type + "datetime" + post_type - import_types = ["from datetime import datetime"] - else: - converted_type = pre_type + "str" + post_type - elif schema.type.value == "integer": - converted_type = pre_type + "int" + post_type - elif schema.type.value == "number": - converted_type = pre_type + "float" + post_type - elif schema.type.value == "boolean": - converted_type = pre_type + "bool" + post_type - elif schema.type.value == "array": - converted_type = pre_type + "List[Any]" + post_type - elif schema.type.value == "object": - converted_type = pre_type + "Dict[str, Any]" + post_type - elif schema.type.value == "null": - converted_type = pre_type + "None" + post_type - else: - converted_type = pre_type + "str" + post_type # Default fallback - elif isinstance(schema.type, list) and len(schema.type) > 0: - # List of DataType enums - use first one - first_type = schema.type[0] - if hasattr(first_type, "value"): - if first_type.value == "string": - # Check for UUID format first - if ( - schema.schema_format is not None - and schema.schema_format.startswith("uuid") - and common.get_use_orjson() - ): - if ( - len(schema.schema_format) > 4 - and schema.schema_format[4].isnumeric() - ): - uuid_type = schema.schema_format.upper() - converted_type = pre_type + uuid_type + post_type - import_types = ["from pydantic import " + uuid_type] - else: - converted_type = pre_type + "UUID" + post_type - import_types = ["from uuid import UUID"] - # Check for date-time format - elif schema.schema_format == "date-time": - converted_type = pre_type + "datetime" + post_type - import_types = ["from datetime import datetime"] - else: - converted_type = pre_type + "str" + post_type - elif first_type.value == "integer": - converted_type = pre_type + "int" + post_type - elif first_type.value == "number": - converted_type = pre_type + "float" + post_type - elif first_type.value == "boolean": - converted_type = pre_type + "bool" + post_type - elif first_type.value == "array": - converted_type = pre_type + "List[Any]" + post_type - elif first_type.value == "object": - converted_type = pre_type + "Dict[str, Any]" + post_type - elif first_type.value == "null": - converted_type = pre_type + "None" + post_type - else: - converted_type = pre_type + "str" + post_type # Default fallback - else: - converted_type = pre_type + "str" + post_type # Default fallback - else: - converted_type = pre_type + "str" + post_type # Default fallback - - return TypeConversion( - original_type=original_type, - converted_type=converted_type, - import_types=import_types, - ) - - -def _generate_property_from_schema( - model_name: str, name: str, schema: Schema, parent_schema: Optional[Schema] = None -) -> Property: - """ - Generates a property from a schema. It takes the type of the schema and converts it to a python type, and then - creates the according property. - :param model_name: Name of the model this property belongs to - :param name: Name of the schema - :param schema: schema to be converted - :param parent_schema: Component this belongs to - :return: Property - """ - required = ( - parent_schema is not None - and parent_schema.required is not None - and name in parent_schema.required - ) - - import_type = None - if required: - import_type = [] if name == model_name else [name] - - return Property( - name=name, - type=type_converter(schema, required, model_name), - required=required, - default=None if required else "None", - import_type=import_type, - ) - - -def _generate_property_from_reference( - model_name: str, - name: str, - reference: Reference, - parent_schema: Optional[Schema] = None, - force_required: bool = False, -) -> Property: - """ - Generates a property from a reference. It takes the name of the reference as the type, and then - returns a property type - :param name: Name of the schema - :param reference: reference to be converted - :param parent_schema: Component this belongs to - :param force_required: Force the property to be required - :return: Property and model to be imported by the file - """ - required = ( - parent_schema is not None - and parent_schema.required is not None - and name in parent_schema.required - ) or force_required - import_model = common.normalize_symbol(reference.ref.split("/")[-1]) - - if import_model == model_name: - type_conv = TypeConversion( - original_type=reference.ref, - converted_type=( - import_model if required else 'Optional["' + import_model + '"]' - ), - import_types=None, - ) - else: - type_conv = TypeConversion( - original_type=reference.ref, - converted_type=( - import_model if required else "Optional[" + import_model + "]" - ), - import_types=[f"from .{import_model} import {import_model}"], - ) - return Property( - name=name, - type=type_conv, - required=required, - default=None if required else "None", - import_type=[import_model], - ) - - -def generate_models( - components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2 -) -> List[Model]: - """ - Receives components from an OpenAPI 3.0+ specification and generates the models from it. - It does so, by iterating over the components.schemas dictionary. For each schema, it checks if - it is a normal schema (i.e. simple type like string, integer, etc.), a reference to another schema, or - an array of types/references. It then computes pydantic models from it using jinja2 - :param components: The components from an OpenAPI 3.0+ specification. - :param pydantic_version: The version of pydantic to use. - :return: A list of models. - """ - models: List[Model] = [] - - if components.schemas is None: - return models - - jinja_env = create_jinja_env() - for schema_name, schema_or_reference in components.schemas.items(): - name = common.normalize_symbol(schema_name) - if schema_or_reference.enum is not None: - value_dict = schema_or_reference.model_dump() - value_dict["enum"] = [ - (common.normalize_symbol(str(i)).upper(), i) for i in value_dict["enum"] - ] - m = Model( - file_name=name, - content=jinja_env.get_template(ENUM_TEMPLATE).render( - name=name, **value_dict - ), - openapi_object=schema_or_reference, - properties=[], - ) - try: - compile(m.content, "", "exec") - models.append(m) - except SyntaxError as e: # pragma: no cover - click.echo(f"Error in model {name}: {e}") - - continue # pragma: no cover - - properties = [] - property_iterator = ( - schema_or_reference.properties.items() - if schema_or_reference.properties is not None - else {} - ) - for prop_name, property in property_iterator: - if isinstance(property, Reference30) or isinstance(property, Reference31): - conv_property = _generate_property_from_reference( - name, prop_name, property, schema_or_reference - ) - else: - conv_property = _generate_property_from_schema( - name, prop_name, property, schema_or_reference - ) - properties.append(conv_property) - - template_name = ( - MODELS_TEMPLATE_PYDANTIC_V2 - if pydantic_version == PydanticVersion.V2 - else MODELS_TEMPLATE - ) - - generated_content = jinja_env.get_template(template_name).render( - schema_name=name, schema=schema_or_reference, properties=properties - ) - - try: - compile(generated_content, "", "exec") - except SyntaxError as e: # pragma: no cover - click.echo(f"Error in model {name}: {e}") # pragma: no cover - - models.append( - Model( - file_name=name, - content=generated_content, - openapi_object=schema_or_reference, - properties=properties, - ) - ) - - return models diff --git a/src/openapi_python_generator/language_converters/python/service_generator.py b/src/openapi_python_generator/language_converters/python/service_generator.py deleted file mode 100644 index 613039e..0000000 --- a/src/openapi_python_generator/language_converters/python/service_generator.py +++ /dev/null @@ -1,504 +0,0 @@ -import re -from typing import Any, Dict, List, Literal, Optional, Tuple, Union - -import click -from openapi_pydantic.v3 import ( - Operation, - PathItem, - Reference, - Response, - Schema, -) -from openapi_pydantic.v3.v3_0 import ( - MediaType as MediaType30, -) - -# Import version-specific types for isinstance checks -from openapi_pydantic.v3.v3_0 import ( - Reference as Reference30, -) -from openapi_pydantic.v3.v3_0 import ( - Response as Response30, -) -from openapi_pydantic.v3.v3_0 import ( - Schema as Schema30, -) -from openapi_pydantic.v3.v3_0.parameter import Parameter as Parameter30 -from openapi_pydantic.v3.v3_1 import ( - MediaType as MediaType31, -) -from openapi_pydantic.v3.v3_1 import ( - Reference as Reference31, -) -from openapi_pydantic.v3.v3_1 import ( - Response as Response31, -) -from openapi_pydantic.v3.v3_1 import ( - Schema as Schema31, -) -from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31 - -from openapi_python_generator.language_converters.python import common -from openapi_python_generator.language_converters.python.common import normalize_symbol -from openapi_python_generator.language_converters.python.jinja_config import ( - create_jinja_env, -) -from openapi_python_generator.language_converters.python.model_generator import ( - type_converter, -) -from openapi_python_generator.models import ( - LibraryConfig, - OpReturnType, - Service, - ServiceOperation, - TypeConversion, -) - - -# Helper functions for isinstance checks across OpenAPI versions -def is_response_type(obj) -> bool: - """Check if object is a Response from any OpenAPI version""" - return isinstance(obj, (Response30, Response31)) - - -def create_media_type_for_reference( - reference_obj: Union[Response30, Reference30, Response31, Reference31], -): - """Create a MediaType wrapper for a reference object, using the correct version""" - # Check which version the reference object belongs to - if isinstance(reference_obj, Reference30): - return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics - elif isinstance(reference_obj, Reference31): - return MediaType31(schema=reference_obj) # type: ignore - pydantic issue with generics - else: - # Fallback to v3.0 for generic Reference - return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics - - -def is_media_type(obj) -> bool: - """Check if object is a MediaType from any OpenAPI version""" - return isinstance(obj, (MediaType30, MediaType31)) - - -def is_reference_type(obj: Any) -> bool: - """Check if object is a Reference type across different versions.""" - return isinstance(obj, (Reference, Reference30, Reference31)) - - -def is_schema_type(obj: Any) -> bool: - """Check if object is a Schema from any OpenAPI version""" - return isinstance(obj, (Schema30, Schema31)) - - -HTTP_OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"] - - -def generate_body_param(operation: Operation) -> Union[str, None]: - if operation.requestBody is None: - return None - else: - if isinstance(operation.requestBody, Reference30) or isinstance( - operation.requestBody, Reference31 - ): - return "data.dict()" - - if operation.requestBody.content is None: - return None # pragma: no cover - - if operation.requestBody.content.get("application/json") is None: - return None # pragma: no cover - - media_type = operation.requestBody.content.get("application/json") - - if media_type is None: - return None # pragma: no cover - - if isinstance( - media_type.media_type_schema, (Reference, Reference30, Reference31) - ): - return "data.dict()" - elif hasattr(media_type.media_type_schema, "ref"): - # Handle Reference objects from different OpenAPI versions - return "data.dict()" - elif isinstance(media_type.media_type_schema, (Schema, Schema30, Schema31)): - schema = media_type.media_type_schema - if schema.type == "array": - return "[i.dict() for i in data]" - elif schema.type == "object": - return "data" - else: - raise Exception( - f"Unsupported schema type for request body: {schema.type}" - ) # pragma: no cover - else: - raise Exception( - f"Unsupported schema type for request body: {type(media_type.media_type_schema)}" - ) # pragma: no cover - - -def generate_params(operation: Operation) -> str: - def _generate_params_from_content(content: Any): - # Accept reference from either 3.0 or 3.1 - if isinstance(content, (Reference, Reference30, Reference31)): - return f"data : {content.ref.split('/')[-1]}" # type: ignore - elif isinstance(content, (Schema, Schema30, Schema31)): - return f"data : {type_converter(content, True).converted_type}" # type: ignore - else: # pragma: no cover - raise Exception(f"Unsupported request body schema type: {type(content)}") - - if operation.parameters is None and operation.requestBody is None: - return "" - - params = "" - default_params = "" - if operation.parameters is not None: - for param in operation.parameters: - if not isinstance(param, (Parameter30, Parameter31)): - continue # pragma: no cover - converted_result = "" - required = False - param_name_cleaned = common.normalize_symbol(param.name) - - if isinstance(param.param_schema, Schema30) or isinstance( - param.param_schema, Schema31 - ): - converted_result = ( - f"{param_name_cleaned} : {type_converter(param.param_schema, param.required).converted_type}" - + ("" if param.required else " = None") - ) - required = param.required - elif isinstance(param.param_schema, Reference30) or isinstance( - param.param_schema, Reference31 - ): - converted_result = ( - f"{param_name_cleaned} : {param.param_schema.ref.split('/')[-1] }" - + ( - "" - if isinstance(param, Reference30) - or isinstance(param, Reference31) - or param.required - else " = None" - ) - ) - required = isinstance(param, Reference) or param.required - - if required: - params += f"{converted_result}, " - else: - default_params += f"{converted_result}, " - - operation_request_body_types = [ - "application/json", - "text/plain", - "multipart/form-data", - "application/octet-stream", - ] - - if operation.requestBody is not None and not is_reference_type( - operation.requestBody - ): - # Safe access only if it's a concrete RequestBody object - rb_content = getattr(operation.requestBody, "content", None) - if isinstance(rb_content, dict) and any( - rb_content.get(i) is not None for i in operation_request_body_types - ): - get_keyword = [ - i for i in operation_request_body_types if rb_content.get(i) - ][0] - content = rb_content.get(get_keyword) - if content is not None and hasattr(content, "media_type_schema"): - mts = getattr(content, "media_type_schema", None) - if isinstance( - mts, - (Reference, Reference30, Reference31, Schema, Schema30, Schema31), - ): - params += f"{_generate_params_from_content(mts)}, " - else: # pragma: no cover - raise Exception( - f"Unsupported media type schema for {str(operation)}: {type(mts)}" - ) - # else: silently ignore unsupported body shapes (could extend later) - # Replace - with _ in params - params = params.replace("-", "_") - default_params = default_params.replace("-", "_") - - return params + default_params - - -def generate_operation_id( - operation: Operation, http_op: str, path_name: Optional[str] = None -) -> str: - if operation.operationId is not None: - return common.normalize_symbol(operation.operationId) - elif path_name is not None: - return common.normalize_symbol(f"{http_op}_{path_name}") - else: - raise Exception( - f"OperationId is not defined for {http_op} of path_name {path_name} --> {operation.summary}" - ) # pragma: no cover - - -def _generate_params( - operation: Operation, param_in: Literal["query", "header"] = "query" -): - if operation.parameters is None: - return [] - - params = [] - for param in operation.parameters: - if isinstance(param, (Parameter30, Parameter31)) and param.param_in == param_in: - param_name_cleaned = common.normalize_symbol(param.name) - params.append(f"{param.name!r} : {param_name_cleaned}") - - return params - - -def generate_query_params(operation: Operation) -> List[str]: - return _generate_params(operation, "query") - - -def generate_header_params(operation: Operation) -> List[str]: - return _generate_params(operation, "header") - - -def generate_return_type(operation: Operation) -> OpReturnType: - if operation.responses is None: - return OpReturnType(type=None, status_code=200, complex_type=False) - - good_responses: List[Tuple[int, Union[Response, Reference]]] = [ - (int(status_code), response) - for status_code, response in operation.responses.items() - if status_code.startswith("2") - ] - if len(good_responses) == 0: - return OpReturnType(type=None, status_code=200, complex_type=False) - - chosen_response = good_responses[0][1] - media_type_schema = None - - if is_response_type(chosen_response): - # It's a Response type, access content safely - if hasattr(chosen_response, "content") and chosen_response.content is not None: # type: ignore - media_type_schema = chosen_response.content.get("application/json") # type: ignore - elif is_reference_type(chosen_response): - media_type_schema = create_media_type_for_reference(chosen_response) - - if media_type_schema is None: - return OpReturnType( - type=None, status_code=good_responses[0][0], complex_type=False - ) - - if is_media_type(media_type_schema): - inner_schema = getattr(media_type_schema, "media_type_schema", None) - if is_reference_type(inner_schema): - type_conv = TypeConversion( - original_type=inner_schema.ref, # type: ignore - converted_type=inner_schema.ref.split("/")[-1], # type: ignore - import_types=[inner_schema.ref.split("/")[-1]], # type: ignore - ) - return OpReturnType( - type=type_conv, - status_code=good_responses[0][0], - complex_type=True, - ) - elif is_schema_type(inner_schema): - converted_result = type_converter(inner_schema, True) # type: ignore - if "array" in converted_result.original_type and isinstance( - converted_result.import_types, list - ): - matched = re.findall(r"List\[(.+)\]", converted_result.converted_type) - if len(matched) > 0: - list_type = matched[0] - else: # pragma: no cover - raise Exception( - f"Unable to parse list type from {converted_result.converted_type}" - ) - else: - list_type = None - return OpReturnType( - type=converted_result, - status_code=good_responses[0][0], - complex_type=bool( - converted_result.import_types - and len(converted_result.import_types) > 0 - ), - list_type=list_type, - ) - else: # pragma: no cover - raise Exception("Unknown media type schema type") - elif media_type_schema is None: - return OpReturnType( - type=None, - status_code=good_responses[0][0], - complex_type=False, - ) - else: - raise Exception("Unknown media type schema type") # pragma: no cover - - -def generate_services( - paths: Dict[str, PathItem], library_config: LibraryConfig -) -> List[Service]: - """ - Generates services from a paths object. - :param paths: paths object to be converted - :return: List of services - """ - jinja_env = create_jinja_env() - - def generate_service_operation( - op: Operation, path_name: str, async_type: bool - ) -> ServiceOperation: - # Merge path-level parameters (always required by spec) into the - # operation-level parameters so they get turned into function args. - try: - path_level_params = [] - if hasattr(path, "parameters") and path.parameters is not None: # type: ignore - path_level_params = [p for p in path.parameters if p is not None] # type: ignore - if path_level_params: - existing_names = set() - if op.parameters is not None: - for p in op.parameters: # type: ignore - if isinstance(p, (Parameter30, Parameter31)): - existing_names.add(p.name) - for p in path_level_params: - if ( - isinstance(p, (Parameter30, Parameter31)) - and p.name not in existing_names - ): - if op.parameters is None: - op.parameters = [] # type: ignore - op.parameters.append(p) # type: ignore - except Exception: # pragma: no cover - print( - f"Error merging path-level parameters for {path_name}" - ) # pragma: no cover - pass - - params = generate_params(op) - # Fallback: ensure all {placeholders} in path are present as function params - try: - placeholder_names = [ - m.group(1) for m in re.finditer(r"\{([^}/]+)\}", path_name) - ] - existing_param_names = { - p.split(":")[0].strip() for p in params.split(",") if ":" in p - } - for ph in placeholder_names: - norm_ph = common.normalize_symbol(ph) - if norm_ph not in existing_param_names and norm_ph: - params = f"{norm_ph}: Any, " + params - except Exception: # pragma: no cover - print( - f"Error ensuring path placeholders in params for {path_name}" - ) # pragma: no cover - pass - operation_id = generate_operation_id(op, http_operation, path_name) - query_params = generate_query_params(op) - header_params = generate_header_params(op) - return_type = generate_return_type(op) - body_param = generate_body_param(op) - - so = ServiceOperation( - params=params, - operation_id=operation_id, - query_params=query_params, - header_params=header_params, - return_type=return_type, - operation=op, - pathItem=path, - content="", - async_client=async_type, - body_param=body_param, - path_name=path_name, - method=http_operation, - use_orjson=common.get_use_orjson(), - ) - - so.content = jinja_env.get_template(library_config.template_name).render( - **so.model_dump() - ) - - if op.tags is not None and len(op.tags) > 0: - so.tag = normalize_symbol(op.tags[0]) - - try: - compile(so.content, "", "exec") - except SyntaxError as e: # pragma: no cover - click.echo(f"Error in service {so.operation_id}: {e}") # pragma: no cover - - return so - - services = [] - service_ops = [] - for path_name, path in paths.items(): - clean_path_name = clean_up_path_name(path_name) - for http_operation in HTTP_OPERATIONS: - op = path.__getattribute__(http_operation) - if op is None: - continue - - if library_config.include_sync: - sync_so = generate_service_operation(op, clean_path_name, False) - service_ops.append(sync_so) - - if library_config.include_async: - async_so = generate_service_operation(op, clean_path_name, True) - service_ops.append(async_so) - - # Ensure every operation has a tag; fallback to "default" for untagged operations - for so in service_ops: - if not so.tag: - so.tag = "default" - - tags = list({so.tag for so in service_ops}) - - for tag in tags: - services.append( - Service( - file_name=f"{tag}_service", - operations=[ - so for so in service_ops if so.tag == tag and not so.async_client - ], - content="\n".join( - [ - so.content - for so in service_ops - if so.tag == tag and not so.async_client - ] - ), - async_client=False, - library_import=library_config.library_name, - use_orjson=common.get_use_orjson(), - ) - ) - - for tag in tags: - services.append( - Service( - file_name=f"async_{tag}_service", - operations=[ - so for so in service_ops if so.tag == tag and so.async_client - ], - content="\n".join( - [ - so.content - for so in service_ops - if so.tag == tag and so.async_client - ] - ), - async_client=True, - library_import=library_config.library_name, - use_orjson=common.get_use_orjson(), - ) - ) - - return services - - -def clean_up_path_name(path_name: str) -> str: - # Clean up path name: only replace dashes inside curly brackets for f-string compatibility, keep other dashes - def _replace_bracket_dashes(match): - return "{" + match.group(1).replace("-", "_") + "}" - - return re.sub(r"\{([^}/]+)\}", _replace_bracket_dashes, path_name) diff --git a/src/openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 b/src/openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 deleted file mode 100644 index 86f994e..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/aiohttp.jinja2 +++ /dev/null @@ -1,49 +0,0 @@ -async def {{ operation_id }}(api_config_override : Optional[APIConfig] = None{% if params.strip() %}, *, {{ params.rstrip(', ') }}{% endif %}) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}: - api_config = api_config_override if api_config_override else APIConfig() - - base_path = api_config.base_path - path = f'{{ path_name }}' - headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': f'Bearer { api_config.get_access_token() }', - {{ header_params | join(',\n') | safe }} - } - - query_params : Dict[str,Any] = { - {% if query_params|length > 0 %} - {{ query_params | join(',\n') | safe }} - {% endif %} - } - - query_params = {key:value for (key,value) in query_params.items() if value is not None} - - async with aiohttp.ClientSession(headers=headers) as session: - async with session.request( - '{{ method }}', - base_path + path, - params=query_params, - {% if body_param %} - {% if use_orjson %} - data=orjson.dumps({{ body_param }}) - {% else %} - json = {{ body_param }} - {% endif %} - {% endif %} - ) as initial_response: - if initial_response.status != {{ return_type.status_code }}: - raise HTTPException(initial_response.status, f'{{ operation_id }} failed with status code: {initial_response.status}') - # Only parse JSON when a body is expected (avoid errors on 204 No Content) - body = None if {{ return_type.status_code }} == 204 else await initial_response.json() - -{% if return_type.type is none or return_type.type.converted_type is none %} - return None -{% elif return_type.complex_type %} - {%- if return_type.list_type is none %} - return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}() - {%- else %} - return [{{ return_type.list_type }}(**item) for item in body] - {%- endif %} -{% else %} - return body -{% endif %} diff --git a/src/openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 b/src/openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 deleted file mode 100644 index b5bfde7..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/apiconfig.jinja2 +++ /dev/null @@ -1,38 +0,0 @@ -{% if env_token_name is not none %}import os{% endif %} - -from pydantic import BaseModel, Field -from typing import Optional, Union - -class APIConfig(BaseModel): - base_path: str = {% if servers|length > 0 %} '{{ servers[0].url }}' {% else %} 'NO SERVER' {% endif %} - - verify: Union[bool, str] = True -{% if env_token_name is none %} - access_token : Optional[str] = None -{% endif %} - - def get_access_token(self) -> Optional[str]: -{% if env_token_name is not none %} - try: - return os.environ['{{ env_token_name }}'] - except KeyError: - return None -{% else %} - return self.access_token -{% endif %} - - def set_access_token(self, value : str): -{% if env_token_name is not none %} - raise Exception("This client was generated with an environment variable for the access token. Please set the environment variable '{{ env_token_name }}' to the access token.") -{% else %} - self.access_token = value -{% endif %} - -class HTTPException(Exception): - def __init__(self, status_code: int, message: str): - self.status_code = status_code - self.message = message - super().__init__(f"{status_code} {message}") - - def __str__(self): - return f"{self.status_code} {self.message}" diff --git a/src/openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 b/src/openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 deleted file mode 100644 index b07c498..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/apiconfig_pydantic_2.jinja2 +++ /dev/null @@ -1,42 +0,0 @@ -{% if env_token_name is not none %}import os{% endif %} - -from pydantic import BaseModel, Field -from typing import Optional, Union - -class APIConfig(BaseModel): - model_config = { - "validate_assignment": True - } - - base_path: str = {% if servers|length > 0 %} '{{ servers[0].url }}' {% else %} 'NO SERVER' {% endif %} - - verify: Union[bool, str] = True -{% if env_token_name is none %} - access_token : Optional[str] = None -{% endif %} - - def get_access_token(self) -> Optional[str]: -{% if env_token_name is not none %} - try: - return os.environ['{{ env_token_name }}'] - except KeyError: - return None -{% else %} - return self.access_token -{% endif %} - - def set_access_token(self, value : str): -{% if env_token_name is not none %} - raise Exception("This client was generated with an environment variable for the access token. Please set the environment variable '{{ env_token_name }}' to the access token.") -{% else %} - self.access_token = value -{% endif %} - -class HTTPException(Exception): - def __init__(self, status_code: int, message: str): - self.status_code = status_code - self.message = message - super().__init__(f"{status_code} {message}") - - def __str__(self): - return f"{self.status_code} {self.message}" \ No newline at end of file diff --git a/src/openapi_python_generator/language_converters/python/templates/httpx.jinja2 b/src/openapi_python_generator/language_converters/python/templates/httpx.jinja2 deleted file mode 100644 index 018006b..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/httpx.jinja2 +++ /dev/null @@ -1,56 +0,0 @@ -{% if async_client %}async {% endif %}def {{ operation_id }}(api_config_override : Optional[APIConfig] = None{% if params.strip() %}, *, {{ params.rstrip(', ') }}{% endif %}) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}: - api_config = api_config_override if api_config_override else APIConfig() - - base_path = api_config.base_path - path = f'{{ path_name }}' - headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': f'Bearer { api_config.get_access_token() }', - {{ header_params | join(',\n') | safe }} - } - query_params : Dict[str,Any] = { - {% if query_params|length > 0 %} - {{ query_params | join(',\n') | safe }} - {% endif %} - } - - query_params = {key:value for (key,value) in query_params.items() if value is not None} - - {% if async_client %} -async with httpx.AsyncClient(base_url=base_path, verify=api_config.verify) as client: - response = await client.request( - {% else %} -with httpx.Client(base_url=base_path, verify=api_config.verify) as client: - response = client.request( - {% endif %} - '{{ method }}', - httpx.URL(path), - headers=headers, - params=query_params, - {% if body_param %} - {% if use_orjson %} - content=orjson.dumps({{ body_param }}) - {% else %} - json = {{ body_param }} - {% endif %} - {% endif %} - ) - - if response.status_code != {{ return_type.status_code }}: - raise HTTPException(response.status_code, f'{{ operation_id }} failed with status code: {response.status_code}') - else: - {# Conditional body parsing: avoid calling .json() for 204 #} - body = None if {{ return_type.status_code }} == 204 else response.json() - -{% if return_type.type is none or return_type.type.converted_type is none %} - return None -{% elif return_type.complex_type %} - {%- if return_type.list_type is none %} - return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}() - {%- else %} - return [{{ return_type.list_type }}(**item) for item in body] - {%- endif %} -{% else %} - return body -{% endif %} diff --git a/src/openapi_python_generator/language_converters/python/templates/requests.jinja2 b/src/openapi_python_generator/language_converters/python/templates/requests.jinja2 deleted file mode 100644 index bf2567b..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/requests.jinja2 +++ /dev/null @@ -1,50 +0,0 @@ -def {{ operation_id }}(api_config_override : Optional[APIConfig] = None{% if params.strip() %}, *, {{ params.rstrip(', ') }}{% endif %}) -> {% if return_type.type is none or return_type.type.converted_type is none %}None{% else %}{{ return_type.type.converted_type}}{% endif %}: - api_config = api_config_override if api_config_override else APIConfig() - - base_path = api_config.base_path - path = f'{{ path_name }}' - headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': f'Bearer { api_config.get_access_token() }', - {{ header_params | join(',\n') | safe }} - } - query_params : Dict[str,Any] = { - {% if query_params|length > 0 %} - {{ query_params | join(',\n') | safe }} - {% endif %} - } - - query_params = {key:value for (key,value) in query_params.items() if value is not None} - - response = requests.request( - '{{ method }}', - f'{base_path}{path}', - headers=headers, - params=query_params, - verify=api_config.verify, - {% if body_param %} - {% if use_orjson %} - content=orjson.dumps({{ body_param }}) - {% else %} - json = {{ body_param }} - {% endif %} - {% endif %} - ) - if response.status_code != {{ return_type.status_code }}: - raise HTTPException(response.status_code, f'{{ operation_id }} failed with status code: {response.status_code}') - else: - {# Conditional body parsing: avoid calling .json() for 204 #} - body = None if {{ return_type.status_code }} == 204 else response.json() - -{% if return_type.type is none or return_type.type.converted_type is none %} - return None -{% elif return_type.complex_type %} - {%- if return_type.list_type is none %} - return {{ return_type.type.converted_type }}(**body) if body is not None else {{ return_type.type.converted_type }}() - {%- else %} - return [{{ return_type.list_type }}(**item) for item in body] - {%- endif %} -{% else %} - return body -{% endif %} diff --git a/src/openapi_python_generator/language_converters/python/templates/service.jinja2 b/src/openapi_python_generator/language_converters/python/templates/service.jinja2 deleted file mode 100644 index ee5864c..0000000 --- a/src/openapi_python_generator/language_converters/python/templates/service.jinja2 +++ /dev/null @@ -1,12 +0,0 @@ -from typing import * -import {{ library_import }} - -{% if use_orjson %} -import orjson -from uuid import UUID -{% endif %} - -from ..models import * -from ..api_config import APIConfig, HTTPException - -{{ content | safe}} diff --git a/src/openapi_python_generator/models.py b/src/openapi_python_generator/models.py deleted file mode 100644 index c599a4b..0000000 --- a/src/openapi_python_generator/models.py +++ /dev/null @@ -1,100 +0,0 @@ -from typing import List, Optional, Union - -from openapi_pydantic.v3.v3_0 import ( - Operation as Operation30, -) -from openapi_pydantic.v3.v3_0 import ( - PathItem as PathItem30, -) -from openapi_pydantic.v3.v3_0 import ( - Schema as Schema30, -) -from openapi_pydantic.v3.v3_1 import ( - Operation as Operation31, -) -from openapi_pydantic.v3.v3_1 import ( - PathItem as PathItem31, -) -from openapi_pydantic.v3.v3_1 import ( - Schema as Schema31, -) -from pydantic import BaseModel - -# Type unions for compatibility with both OpenAPI 3.0 and 3.1 -Operation = Union[Operation30, Operation31] -PathItem = Union[PathItem30, PathItem31] -Schema = Union[Schema30, Schema31] - - -class LibraryConfig(BaseModel): - name: str - library_name: str - template_name: str - include_async: bool - include_sync: bool - - -class TypeConversion(BaseModel): - original_type: str - converted_type: str - import_types: Optional[List[str]] = None - - -class OpReturnType(BaseModel): - type: Optional[TypeConversion] = None - status_code: int - complex_type: bool = False - list_type: Optional[str] = None - - -class ServiceOperation(BaseModel): - params: str - operation_id: str - query_params: List[str] - header_params: List[str] - return_type: OpReturnType - operation: Operation - pathItem: PathItem - content: str - async_client: Optional[bool] = False - tag: Optional[str] = None - path_name: str - body_param: Optional[str] = None - method: str - use_orjson: bool = False - - -class Property(BaseModel): - name: str - type: TypeConversion - required: bool - default: Optional[str] - import_type: Optional[List[str]] = None - - -class Model(BaseModel): - file_name: str - content: str - openapi_object: Schema - properties: List[Property] = [] - - -class Service(BaseModel): - file_name: str - operations: List[ServiceOperation] - content: str - async_client: Optional[bool] = False - library_import: str - use_orjson: bool = False - - -class APIConfig(BaseModel): - file_name: str - base_url: str - content: str - - -class ConversionResult(BaseModel): - models: List[Model] - services: List[Service] - api_config: APIConfig diff --git a/src/openapi_python_generator/__init__.py b/src/pydantic_openapi_generator/__init__.py similarity index 100% rename from src/openapi_python_generator/__init__.py rename to src/pydantic_openapi_generator/__init__.py diff --git a/src/openapi_python_generator/__main__.py b/src/pydantic_openapi_generator/__main__.py similarity index 75% rename from src/openapi_python_generator/__main__.py rename to src/pydantic_openapi_generator/__main__.py index 20616a2..9877164 100644 --- a/src/openapi_python_generator/__main__.py +++ b/src/pydantic_openapi_generator/__main__.py @@ -2,9 +2,9 @@ import click -from openapi_python_generator import __version__ -from openapi_python_generator.common import Formatter, HTTPLibrary, PydanticVersion -from openapi_python_generator.generate_data import generate_data +from pydantic_openapi_generator import __version__ +from pydantic_openapi_generator.common import Formatter, HTTPLibrary, PydanticVersion +from pydantic_openapi_generator.generate_data import generate_data @click.command() @@ -52,6 +52,19 @@ show_default=True, help="Option to choose which auto formatter is applied.", ) +@click.option( + "--config-path", + type=click.Path(exists=True, dir_okay=False), + default=None, + help="Optional YAML configuration for generated client parameters.", +) +@click.option( + "--overlay", + "overlay_paths", + type=click.Path(exists=True, dir_okay=False), + multiple=True, + help="Optional JSON/YAML OpenAPI overlay file. Can be provided multiple times and is applied in order.", +) @click.version_option(version=__version__) def main( source: str, @@ -62,6 +75,8 @@ def main( custom_template_path: Optional[str] = None, pydantic_version: PydanticVersion = PydanticVersion.V2, formatter: Formatter = Formatter.BLACK, + config_path: Optional[str] = None, + overlay_paths: tuple[str, ...] = (), ) -> None: """ Generate Python code from an OpenAPI 3.0+ specification. @@ -78,6 +93,8 @@ def main( custom_template_path, pydantic_version, formatter, + config_path, + overlay_paths, ) diff --git a/src/openapi_python_generator/common.py b/src/pydantic_openapi_generator/common.py similarity index 95% rename from src/openapi_python_generator/common.py rename to src/pydantic_openapi_generator/common.py index b6e6a49..4005410 100644 --- a/src/openapi_python_generator/common.py +++ b/src/pydantic_openapi_generator/common.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Dict, Optional -from openapi_python_generator.models import LibraryConfig +from pydantic_openapi_generator.models import LibraryConfig class HTTPLibrary(str, Enum): diff --git a/src/pydantic_openapi_generator/config/__init__.py b/src/pydantic_openapi_generator/config/__init__.py new file mode 100644 index 0000000..b9b436b --- /dev/null +++ b/src/pydantic_openapi_generator/config/__init__.py @@ -0,0 +1,5 @@ +from pydantic_openapi_generator.config.parameter_source import ParameterSource + +__all__ = [ + "ParameterSource", +] diff --git a/src/pydantic_openapi_generator/config/base_parameter.py b/src/pydantic_openapi_generator/config/base_parameter.py new file mode 100644 index 0000000..4e0b24d --- /dev/null +++ b/src/pydantic_openapi_generator/config/base_parameter.py @@ -0,0 +1,73 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from pydantic import BaseModel, ConfigDict + +from pydantic_openapi_generator.config.utils import Parameter, parameter_base_type_hint +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.models import GeneratedParameter + + +class BaseArgumentConfiguration(BaseModel, ABC): + model_config = ConfigDict(extra="forbid") + + name: str + code_name: Optional[str] = None + + def resolve_parameter(self, param: Parameter) -> GeneratedParameter: + code_name = self.resolved_code_name(param) + type_hint, default = self.method_type_and_default(param) + + return GeneratedParameter( + wire_name=param.name, + code_name=code_name, + location=param.param_in, # type: ignore[arg-type] + type_hint=type_hint, + base_type_hint=parameter_base_type_hint(param), + required=param.required, + default=default, + source=self.type, # type: ignore[attr-defined] + is_secret=self.is_secret_parameter(), + field_type_hint=self.field_type_hint(param), + field_default=self.field_default(param), + getter_name=self.getter_name(code_name), + local_name=self.local_name(code_name), + value_expression=self.value_expression(code_name), + ) + + def resolved_code_name(self, param: Parameter) -> str: + if self.code_name: + return common.normalize_symbol(self.code_name) + return self.default_code_name(param.name) + + @abstractmethod + def default_code_name(self, param_name: str) -> str: + raise NotImplementedError + + @abstractmethod + def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]: + raise NotImplementedError + + @abstractmethod + def field_type_hint(self, param: Parameter) -> Optional[str]: + raise NotImplementedError + + @abstractmethod + def field_default(self, param: Parameter) -> Optional[str]: + raise NotImplementedError + + @abstractmethod + def getter_name(self, code_name: str) -> Optional[str]: + raise NotImplementedError + + @abstractmethod + def local_name(self, code_name: str) -> Optional[str]: + raise NotImplementedError + + @abstractmethod + def value_expression(self, code_name: str) -> str: + raise NotImplementedError + + @abstractmethod + def is_secret_parameter(self) -> bool: + raise NotImplementedError diff --git a/src/pydantic_openapi_generator/config/class_var_parameter.py b/src/pydantic_openapi_generator/config/class_var_parameter.py new file mode 100644 index 0000000..3385774 --- /dev/null +++ b/src/pydantic_openapi_generator/config/class_var_parameter.py @@ -0,0 +1,60 @@ +from typing import Literal, Optional, override + +from pydantic_openapi_generator.config.base_parameter import ( + BaseArgumentConfiguration, +) +from pydantic_openapi_generator.config.parameter_source import ParameterSource +from pydantic_openapi_generator.config.utils import ( + Parameter, + lower_code_name, + optional_override_type_and_default, + optional_type, + parameter_base_type_hint, + parameter_default, +) + + +class ClassVarArgumentConfiguration(BaseArgumentConfiguration): + type: Literal[ParameterSource.CLASS_VAR] = ParameterSource.CLASS_VAR + is_secret: bool = False + + @override + def default_code_name(self, param_name: str) -> str: + return lower_code_name(param_name) + + @override + def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]: + return optional_override_type_and_default(param) + + @override + def field_type_hint(self, param: Parameter) -> str: + default = parameter_default(param) + base_type = "SecretStr" if self.is_secret else parameter_base_type_hint(param) + + if param.required or default is not None: + return base_type + + return optional_type(base_type) + + @override + def field_default(self, param: Parameter) -> Optional[str]: + default = parameter_default(param) + if param.required: + return None + return default if default is not None else "None" + + @override + def getter_name(self, code_name: str) -> Optional[str]: + return None + + @override + def local_name(self, code_name: str) -> str: + return f"_{code_name}" + + @override + def value_expression(self, code_name: str) -> str: + return self.local_name(code_name) + + @override + def is_secret_parameter(self) -> bool: + return self.is_secret diff --git a/src/pydantic_openapi_generator/config/function_parameter.py b/src/pydantic_openapi_generator/config/function_parameter.py new file mode 100644 index 0000000..ea621ae --- /dev/null +++ b/src/pydantic_openapi_generator/config/function_parameter.py @@ -0,0 +1,47 @@ +from typing import Literal, Optional, override + +from pydantic_openapi_generator.config.base_parameter import ( + BaseArgumentConfiguration, +) +from pydantic_openapi_generator.config.parameter_source import ParameterSource +from pydantic_openapi_generator.config.utils import ( + Parameter, + lower_code_name, + optional_override_type_and_default, +) + + +class FunctionArgumentConfiguration(BaseArgumentConfiguration): + type: Literal[ParameterSource.FUNCTION] = ParameterSource.FUNCTION + + @override + def default_code_name(self, param_name: str) -> str: + return lower_code_name(param_name) + + @override + def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]: + return optional_override_type_and_default(param) + + @override + def field_type_hint(self, param: Parameter) -> Optional[str]: + return None + + @override + def field_default(self, param: Parameter) -> Optional[str]: + return None + + @override + def getter_name(self, code_name: str) -> str: + return f"get_{code_name}" + + @override + def local_name(self, code_name: str) -> str: + return f"_{code_name}" + + @override + def value_expression(self, code_name: str) -> str: + return self.local_name(code_name) + + @override + def is_secret_parameter(self) -> bool: + return False diff --git a/src/pydantic_openapi_generator/config/generator_config.py b/src/pydantic_openapi_generator/config/generator_config.py new file mode 100644 index 0000000..72b8524 --- /dev/null +++ b/src/pydantic_openapi_generator/config/generator_config.py @@ -0,0 +1,33 @@ +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from pydantic_openapi_generator.config.base_parameter import BaseArgumentConfiguration +from pydantic_openapi_generator.config.kwarg_parameter import KwargArgumentConfiguration +from pydantic_openapi_generator.config.parameter_union import ArgumentConfiguration + + +class PydanticOpenAPIGeneratorConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + parameters: list[ArgumentConfiguration] = Field(default_factory=list) + + def parameter_configuration_for(self, parameter_name: str) -> BaseArgumentConfiguration: + configured = self.parameters_by_name().get(parameter_name) + return configured if configured is not None else KwargArgumentConfiguration(name=parameter_name) + + def parameters_by_name(self) -> dict[str, ArgumentConfiguration]: + return {parameter.name: parameter for parameter in self.parameters} + + @model_validator(mode="after") + def validate_unique_parameter_names(self) -> "PydanticOpenAPIGeneratorConfig": + names: set[str] = set() + duplicates: set[str] = set() + for parameter in self.parameters: + if parameter.name in names: + duplicates.add(parameter.name) + names.add(parameter.name) + + if duplicates: + duplicate_list = ", ".join(sorted(duplicates)) + raise ValueError(f"Duplicate parameter configuration for: {duplicate_list}") + + return self diff --git a/src/pydantic_openapi_generator/config/kwarg_parameter.py b/src/pydantic_openapi_generator/config/kwarg_parameter.py new file mode 100644 index 0000000..5d0e0f0 --- /dev/null +++ b/src/pydantic_openapi_generator/config/kwarg_parameter.py @@ -0,0 +1,56 @@ +from typing import Literal, Optional, override + +from pydantic_openapi_generator.config.base_parameter import BaseArgumentConfiguration +from pydantic_openapi_generator.config.parameter_source import ParameterSource +from pydantic_openapi_generator.config.utils import ( + Parameter, + optional_type, + parameter_base_type_hint, + parameter_default, +) +from pydantic_openapi_generator.language_converters.python import common + + +class KwargArgumentConfiguration(BaseArgumentConfiguration): + type: Literal[ParameterSource.KWARG] = ParameterSource.KWARG + + @override + def default_code_name(self, param_name: str) -> str: + return common.normalize_symbol(param_name) + + @override + def method_type_and_default(self, param: Parameter) -> tuple[str, Optional[str]]: + base_type = parameter_base_type_hint(param) + default = parameter_default(param) + + if param.required: + return base_type, None + + if default is not None: + return base_type, default + + return optional_type(base_type), "None" + + @override + def field_type_hint(self, param: Parameter) -> Optional[str]: + return None + + @override + def field_default(self, param: Parameter) -> Optional[str]: + return None + + @override + def getter_name(self, code_name: str) -> Optional[str]: + return None + + @override + def local_name(self, code_name: str) -> Optional[str]: + return None + + @override + def value_expression(self, code_name: str) -> str: + return code_name + + @override + def is_secret_parameter(self) -> bool: + return False diff --git a/src/pydantic_openapi_generator/config/loader.py b/src/pydantic_openapi_generator/config/loader.py new file mode 100644 index 0000000..4b63b0a --- /dev/null +++ b/src/pydantic_openapi_generator/config/loader.py @@ -0,0 +1,17 @@ +from pathlib import Path +from typing import Optional + +from pydantic_di.loaders import ObjectLoaderYaml + +from pydantic_openapi_generator.config.generator_config import ( + PydanticOpenAPIGeneratorConfig, +) + + +def load_config(config_path: Optional[str | Path]) -> PydanticOpenAPIGeneratorConfig: + if config_path is None: + return PydanticOpenAPIGeneratorConfig() + + return ObjectLoaderYaml[PydanticOpenAPIGeneratorConfig]( + path=Path(config_path), + ).load() diff --git a/src/pydantic_openapi_generator/config/parameter_source.py b/src/pydantic_openapi_generator/config/parameter_source.py new file mode 100644 index 0000000..5f4b161 --- /dev/null +++ b/src/pydantic_openapi_generator/config/parameter_source.py @@ -0,0 +1,7 @@ +from enum import StrEnum + + +class ParameterSource(StrEnum): + KWARG = "Kwarg" + CLASS_VAR = "ClassVar" + FUNCTION = "Function" diff --git a/src/pydantic_openapi_generator/config/parameter_union.py b/src/pydantic_openapi_generator/config/parameter_union.py new file mode 100644 index 0000000..d383596 --- /dev/null +++ b/src/pydantic_openapi_generator/config/parameter_union.py @@ -0,0 +1,16 @@ +from typing import Annotated + +from pydantic import Field + +from pydantic_openapi_generator.config.class_var_parameter import ( + ClassVarArgumentConfiguration, +) +from pydantic_openapi_generator.config.function_parameter import ( + FunctionArgumentConfiguration, +) +from pydantic_openapi_generator.config.kwarg_parameter import KwargArgumentConfiguration + +ArgumentConfiguration = Annotated[ + KwargArgumentConfiguration | ClassVarArgumentConfiguration | FunctionArgumentConfiguration, + Field(discriminator="type"), +] diff --git a/src/pydantic_openapi_generator/config/utils.py b/src/pydantic_openapi_generator/config/utils.py new file mode 100644 index 0000000..324beef --- /dev/null +++ b/src/pydantic_openapi_generator/config/utils.py @@ -0,0 +1,56 @@ +import re +from typing import Any, Optional, Union + +from openapi_pydantic.v3.v3_0 import Reference as Reference30 +from openapi_pydantic.v3.v3_0 import Schema as Schema30 +from openapi_pydantic.v3.v3_0.parameter import Parameter as Parameter30 +from openapi_pydantic.v3.v3_1 import Reference as Reference31 +from openapi_pydantic.v3.v3_1 import Schema as Schema31 +from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31 + +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.model_generator import ( + type_converter, +) + +Parameter = Union[Parameter30, Parameter31] + + +def lower_code_name(value: str) -> str: + normalized = common.normalize_symbol(value) + parts = re.findall(r"[A-Z]+(?=[A-Z][a-z0-9]|$)|[A-Z]?[a-z0-9]+", normalized) + return "_".join(part.lower() for part in parts) or normalized.lower() + + +def literal_default(schema: Any) -> Optional[str]: + default = getattr(schema, "default", None) + return f"{default!r}" if default is not None else None + + +def optional_type(type_hint: str) -> str: + if type_hint.startswith("Optional[") and type_hint.endswith("]"): + return type_hint + return f"Optional[{type_hint}]" + + +def parameter_type_hint(param: Parameter, required: bool) -> str: + if isinstance(param.param_schema, (Schema30, Schema31)): + return type_converter(param.param_schema, required).converted_type + if isinstance(param.param_schema, (Reference30, Reference31)): + model_name = common.normalize_symbol(param.param_schema.ref.split("/")[-1]) + return model_name if required else f"Optional[{model_name}]" + return "Any" + + +def parameter_base_type_hint(param: Parameter) -> str: + return parameter_type_hint(param, True) + + +def parameter_default(param: Parameter) -> Optional[str]: + if isinstance(param.param_schema, (Schema30, Schema31)): + return literal_default(param.param_schema) + return None + + +def optional_override_type_and_default(param: Parameter) -> tuple[str, Optional[str]]: + return optional_type(parameter_base_type_hint(param)), "None" diff --git a/src/pydantic_openapi_generator/generate_data.py b/src/pydantic_openapi_generator/generate_data.py new file mode 100644 index 0000000..f49a5f0 --- /dev/null +++ b/src/pydantic_openapi_generator/generate_data.py @@ -0,0 +1,263 @@ +from pathlib import Path +from typing import Any, List, Optional, Sequence, Union + +import black +import click +import httpx +import isort +import orjson +import yaml # type: ignore +from black.report import NothingChanged # type: ignore +from httpx import ConnectError, ConnectTimeout +from pydantic import ValidationError + +from .common import FormatOptions, Formatter, HTTPLibrary, PydanticVersion +from .config.generator_config import PydanticOpenAPIGeneratorConfig +from .config.loader import load_config +from .models import ConversionResult +from .parsers import ( + generate_code_3_0, + generate_code_3_1, + parse_openapi_3_0, + parse_openapi_3_1, +) +from .version_detector import detect_openapi_version + + +def write_code(path: Path, content: str, formatter: Formatter) -> None: + """ + Write the content to the file at the given path. + :param path: The path to the file. + :param content: The content to write. + :param formatter: The formatter applied to the code written. + """ + if formatter == Formatter.BLACK: + formatted_contend = format_using_black(content) + elif formatter == Formatter.NONE: + formatted_contend = content + else: + raise NotImplementedError(f"Missing implementation for formatter {formatter!r}.") + with open(path, "w") as f: + f.write(formatted_contend) + + +def format_using_black(content: str) -> str: + try: + formatted_contend = black.format_file_contents( + content, + fast=FormatOptions.skip_validation, + mode=black.FileMode(line_length=FormatOptions.line_length), + ) + except NothingChanged: + return content + return isort.code(formatted_contend, line_length=FormatOptions.line_length) + + +def load_openapi_data(source: Union[str, Path]) -> dict[str, Any]: + """ + Load an OpenAPI JSON/YAML document from a URL or local file path. + """ + if not isinstance(source, Path) and (source.startswith("http://") or source.startswith("https://")): + content = httpx.get(source).text + else: + with open(source, "r") as f: + content = f.read() + + try: + data = orjson.loads(content) + except orjson.JSONDecodeError: + try: + data = yaml.safe_load(content) + except yaml.YAMLError as e: + click.echo(f"File {source} is neither a valid JSON nor YAML file: {str(e)}") + raise + + if not isinstance(data, dict): + raise ValueError(f"OpenAPI data loaded from {source} must be a JSON/YAML object.") + + return data + + +def deep_merge(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: + """ + Recursively merge two dictionaries. Overlay lists and scalar values replace base values. + """ + result = base.copy() + for key, overlay_value in overlay.items(): + base_value = result.get(key) + if isinstance(base_value, dict) and isinstance(overlay_value, dict): + result[key] = deep_merge(base_value, overlay_value) + else: + result[key] = overlay_value + return result + + +def get_open_api(source: Union[str, Path], overlay_paths: Optional[Sequence[Union[str, Path]]] = None): + """ + Tries to fetch the openapi specification file from the web or load from a local file. + Supports both JSON and YAML formats. Returns the according OpenAPI object. + Automatically supports OpenAPI 3.0 and 3.1 specifications with intelligent version detection. + + Args: + source: URL or file path to the OpenAPI specification + overlay_paths: Optional JSON/YAML overlay files to deep-merge into source in order + + Returns: + tuple: (OpenAPI object, version) where version is "3.0" or "3.1" + + Raises: + FileNotFoundError: If the specified file cannot be found + ConnectError: If the URL cannot be accessed + ValidationError: If the specification is invalid + JSONDecodeError/YAMLError: If the file cannot be parsed + """ + try: + data = load_openapi_data(source) + for overlay_path in overlay_paths or (): + data = deep_merge(data, load_openapi_data(overlay_path)) + + # Detect version and parse with appropriate parser + version = detect_openapi_version(data) + + if version == "3.0": + openapi_obj = parse_openapi_3_0(data) # type: ignore[assignment] + elif version == "3.1": + openapi_obj = parse_openapi_3_1(data) # type: ignore[assignment] + else: + # Unsupported version detected (version detection already limited to 3.0 / 3.1) + raise ValueError(f"Unsupported OpenAPI version: {version}. Only 3.0.x and 3.1.x are supported.") + + return openapi_obj, version + + except FileNotFoundError: + click.echo(f"File {source} not found. Please make sure to pass the path to the OpenAPI specification.") + raise + except (ConnectError, ConnectTimeout): + click.echo(f"Could not connect to {source}.") + raise ConnectError(f"Could not connect to {source}.") from None + except ValidationError: + click.echo(f"File {source} is not a valid OpenAPI 3.0+ specification.") + raise + + +def write_data(data: ConversionResult, output: Union[str, Path], formatter: Formatter) -> None: + """ + Write generated code to disk. + + Creates: + - models/ (and models/__init__.py) + - clients/ (and clients/__init__.py) + - exceptions (package root/__init__.py) + - __init__.py (package root) + """ + out = Path(output) + out.mkdir(parents=True, exist_ok=True) + + # ---------------------------- + # models/ + # ---------------------------- + models_path = out / "models" + models_path.mkdir(parents=True, exist_ok=True) + + model_files: List[str] = [] + for model in data.models: + model_files.append(model.file_name) + write_code(models_path / f"{model.file_name}.py", model.content, formatter) + + write_code( + models_path / "__init__.py", + "\n".join([f"from .{f} import *" for f in model_files]) + ("\n" if model_files else ""), + formatter, + ) + + # ---------------------------- + # clients/ + # ---------------------------- + clients_path = out / "clients" + clients_path.mkdir(parents=True, exist_ok=True) + + client_files: List[str] = [] + for client in data.clients: + client_files.append(client.file_name) + write_code(clients_path / f"{client.file_name}.py", client.content, formatter) + + write_code( + clients_path / "__init__.py", + "\n".join([f"from .{f} import *" for f in client_files]) + ("\n" if client_files else ""), + formatter, + ) + + # ---------------------------- + # exceptions/ + # ---------------------------- + exceptions_path = out / "exceptions" + exceptions_path.mkdir(parents=True, exist_ok=True) + + exception_files: List[str] = [] + for ex in data.exceptions: + exception_files.append(ex.file_name) + write_code(exceptions_path / f"{ex.file_name}.py", ex.content, formatter) + + write_code( + exceptions_path / "__init__.py", + "\n".join([f"from .{f} import *" for f in exception_files]) + ("\n" if exception_files else ""), + formatter, + ) + + # ---------------------------- + # package __init__.py (root) + # ---------------------------- + init_lines: List[str] = [ + "from .models import *", + "from .clients import *", + "from .exceptions import *", + ] + + write_code(out / "__init__.py", "\n".join(init_lines) + "\n", formatter) + + +def generate_data( + source: Union[str, Path], + output: Union[str, Path], + library: HTTPLibrary = HTTPLibrary.httpx, + env_token_name: Optional[str] = None, + use_orjson: bool = False, + custom_template_path: Optional[str] = None, + pydantic_version: PydanticVersion = PydanticVersion.V2, + formatter: Formatter = Formatter.BLACK, + config_path: Optional[Union[str, Path]] = None, + overlay_paths: Optional[Sequence[Union[str, Path]]] = None, + config: Optional[PydanticOpenAPIGeneratorConfig] = None, +) -> None: + """ + Generate Python code from an OpenAPI 3.0+ specification. + """ + openapi_obj, version = get_open_api(source, overlay_paths) + loaded_config = config if config is not None else load_config(config_path) + click.echo(f"Generating data from {source} (OpenAPI {version})") + + # Use version-specific generator + if version == "3.0": + result = generate_code_3_0( + openapi_obj, # type: ignore + library, + env_token_name, + use_orjson, + custom_template_path, + pydantic_version, + loaded_config, + ) + elif version == "3.1": + result = generate_code_3_1( + openapi_obj, # type: ignore + library, + env_token_name, + use_orjson, + custom_template_path, + pydantic_version, + loaded_config, + ) + else: + raise ValueError(f"Unsupported OpenAPI version: {version}") + + write_data(result, output, formatter) diff --git a/src/openapi_python_generator/language_converters/python/__init__.py b/src/pydantic_openapi_generator/language_converters/__init__.py similarity index 100% rename from src/openapi_python_generator/language_converters/python/__init__.py rename to src/pydantic_openapi_generator/language_converters/__init__.py diff --git a/tests/regression/__init__.py b/src/pydantic_openapi_generator/language_converters/python/__init__.py similarity index 100% rename from tests/regression/__init__.py rename to src/pydantic_openapi_generator/language_converters/python/__init__.py diff --git a/src/pydantic_openapi_generator/language_converters/python/client_generator.py b/src/pydantic_openapi_generator/language_converters/python/client_generator.py new file mode 100644 index 0000000..ba1e1bc --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/client_generator.py @@ -0,0 +1,897 @@ +import logging +import re +from typing import Any, Dict, List, Literal, Optional, Tuple, Union + +from openapi_pydantic.v3 import ( + Operation, + PathItem, + Reference, + Response, + Schema, +) +from openapi_pydantic.v3.v3_0 import ( + MediaType as MediaType30, +) + +# Import version-specific types for isinstance checks +from openapi_pydantic.v3.v3_0 import ( + Reference as Reference30, +) +from openapi_pydantic.v3.v3_0 import ( + Response as Response30, +) +from openapi_pydantic.v3.v3_0 import ( + Schema as Schema30, +) +from openapi_pydantic.v3.v3_0.parameter import Parameter as Parameter30 +from openapi_pydantic.v3.v3_1 import ( + MediaType as MediaType31, +) +from openapi_pydantic.v3.v3_1 import ( + Reference as Reference31, +) +from openapi_pydantic.v3.v3_1 import ( + Response as Response31, +) +from openapi_pydantic.v3.v3_1 import ( + Schema as Schema31, +) +from openapi_pydantic.v3.v3_1.parameter import Parameter as Parameter31 + +from pydantic_openapi_generator.common import PydanticVersion +from pydantic_openapi_generator.config.generator_config import ( + PydanticOpenAPIGeneratorConfig, +) +from pydantic_openapi_generator.config.parameter_source import ParameterSource +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.jinja_config import ( + ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2, + SYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2, + create_jinja_env, +) +from pydantic_openapi_generator.language_converters.python.model_generator import ( + type_converter, +) +from pydantic_openapi_generator.models import ( + GeneratedParameter, + LibraryConfig, + Model, + OpReturnType, + RequestBodyDefinition, + ResponseContentHandler, + ResponseVariant, + ServiceOperation, + TypeConversion, +) + +RESERVED_CLIENT_MEMBER_NAMES = { + "base_url", + "verify", + "access_token", + "get_access_token", + "set_access_token", + "model_config", +} + + +# Helper functions for isinstance checks across OpenAPI versions +def is_response_type(obj) -> bool: + """Check if object is a Response from any OpenAPI version""" + return isinstance(obj, (Response30, Response31)) + + +def create_media_type_for_reference( + reference_obj: Union[Response30, Reference30, Response31, Reference31], +): + """Create a MediaType wrapper for a reference object, using the correct version""" + # Check which version the reference object belongs to + if isinstance(reference_obj, Reference30): + return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics + elif isinstance(reference_obj, Reference31): + return MediaType31(schema=reference_obj) # type: ignore - pydantic issue with generics + else: + # Fallback to v3.0 for generic Reference + return MediaType30(schema=reference_obj) # type: ignore - pydantic issue with generics + + +def is_media_type(obj) -> bool: + """Check if object is a MediaType from any OpenAPI version""" + return isinstance(obj, (MediaType30, MediaType31)) + + +def is_reference_type(obj: Any) -> bool: + """Check if object is a Reference type across different versions.""" + return isinstance(obj, (Reference, Reference30, Reference31)) + + +def is_schema_type(obj: Any) -> bool: + """Check if object is a Schema from any OpenAPI version""" + return isinstance(obj, (Schema30, Schema31)) + + +def _common_suffix(a: str, b: str) -> str: + i = 1 + while i <= min(len(a), len(b)) and a[-i] == b[-i]: + i += 1 + return a[-(i - 1) :] if i > 1 else "" + + +def _common_suffix_many(names: List[str]) -> str: + if not names: + return "" + suf = names[0] + for n in names[1:]: + suf = _common_suffix(suf, n) + if not suf: + break + return suf + + +def operation_is_sse(op: Operation) -> bool: + """Detect if an Operation advertises Server-Sent-Events (text/event-stream) in any 2xx response.""" + if not getattr(op, "responses", None): + return False + + for status_code, resp in op.responses.items(): + try: + if not str(status_code).startswith("2"): + continue + except Exception as e: + logger = logging.getLogger(__name__) + logger.debug("Skipping response status key; conversion failed", exc_info=e) + continue + + # Concrete Response object + if is_response_type(resp): + content = getattr(resp, "content", None) + if isinstance(content, dict) and "text/event-stream" in content: + return True + + # Reference responses could be resolved externally; skip for now + if is_reference_type(resp): + # If you need supporting $ref'ed SSE responses, resolve via components + pass + + return False + + +def _sse_data_schema_from_media_type(media_type: Any) -> Any: + media_type_schema = getattr(media_type, "media_type_schema", None) + if media_type_schema is None: + return None + + if is_reference_type(media_type_schema): + return media_type_schema + + if not is_schema_type(media_type_schema): + return None + + properties = getattr(media_type_schema, "properties", None) + if isinstance(properties, dict) and "data" in properties: + return properties["data"] + + schema_type = getattr(media_type_schema, "type", None) + if schema_type != "object" and str(schema_type) != "DataType.OBJECT": + return media_type_schema + + return None + + +def generate_sse_data_handler(operation: Operation) -> Optional[ResponseContentHandler]: + if not getattr(operation, "responses", None): + return None + + for status_code, response in operation.responses.items(): + try: + if not str(status_code).startswith("2"): + continue + except Exception as e: + logger = logging.getLogger(__name__) + logger.debug("Skipping response status key; conversion failed", exc_info=e) + continue + + if not is_response_type(response): + continue + + content = getattr(response, "content", None) + if not isinstance(content, dict): + continue + + media_type = content.get("text/event-stream") + if not is_media_type(media_type): + continue + + data_schema = _sse_data_schema_from_media_type(media_type) + if data_schema is None: + continue + + variant = _response_variant_from_schema(int(status_code), "application/json", data_schema) + if variant.type is None or variant.body_kind == "empty": + continue + + return ResponseContentHandler( + content_type="text/event-stream", + type=variant.type, + complex_type=variant.complex_type, + list_type=variant.list_type, + body_kind=variant.body_kind, # type: ignore[arg-type] + ) + + return None + + +HTTP_OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"] + + +def _json_body_expression(media_type_schema: Any) -> str: + if isinstance(media_type_schema, (Reference, Reference30, Reference31)) or hasattr(media_type_schema, "ref"): + return "data.model_dump(by_alias=True, exclude_none=True)" + + if isinstance(media_type_schema, (Schema, Schema30, Schema31)): + schema = media_type_schema + + if schema.type == "array": + return "[i.model_dump(by_alias=True, exclude_none=True) if hasattr(i, 'model_dump') else i for i in data]" + + if schema.type == "object": + return "data.model_dump(by_alias=True, exclude_none=True) if hasattr(data, 'model_dump') else data" + + return "data" + + raise Exception(f"Unsupported schema type for request body: {type(media_type_schema)}") # pragma: no cover + + +def generate_request_body(operation: Operation) -> Union[RequestBodyDefinition, None]: + if operation.requestBody is None: + return None + + # If requestBody is a $ref, it will be a Pydantic model instance in the client. + if isinstance(operation.requestBody, (Reference30, Reference31)): + return RequestBodyDefinition( + content_type="application/json", + encoding="json", + expression="data.model_dump(by_alias=True, exclude_none=True)", + ) + + rb_content = getattr(operation.requestBody, "content", None) + if rb_content is None: + return None # pragma: no cover + + ordered_content_types = [ + "application/json", + "multipart/form-data", + "application/x-www-form-urlencoded", + "application/octet-stream", + "text/plain", + ] + content_type = next((ct for ct in ordered_content_types if rb_content.get(ct) is not None), None) + if content_type is None: + return None + + media_type = rb_content.get(content_type) + if media_type is None: + return None # pragma: no cover + + mts = getattr(media_type, "media_type_schema", None) + if content_type == "application/json": + if mts is None: + return None # pragma: no cover + return RequestBodyDefinition( + content_type=content_type, + encoding="json", + expression=_json_body_expression(mts), + ) + + if content_type == "multipart/form-data": + return RequestBodyDefinition(content_type=None, encoding="multipart", expression="data") + + if content_type == "application/x-www-form-urlencoded": + return RequestBodyDefinition(content_type=content_type, encoding="form", expression="data") + + if content_type == "application/octet-stream": + return RequestBodyDefinition(content_type=content_type, encoding="binary", expression="data") + + if content_type == "text/plain": + return RequestBodyDefinition(content_type=content_type, encoding="text", expression="data") + + return None # pragma: no cover + + +def generate_body_param(operation: Operation) -> Union[str, None]: + request_body = generate_request_body(operation) + return None if request_body is None else request_body.expression + + +def _resolve_parameter( + param: Union[Parameter30, Parameter31], + config: PydanticOpenAPIGeneratorConfig, +) -> GeneratedParameter: + return config.parameter_configuration_for(param.name).resolve_parameter(param) + + +def _method_signature(parameters: List[GeneratedParameter], body_param: Optional[str]) -> str: + required_params: List[str] = [] + default_params: List[str] = [] + + for parameter in parameters: + rendered = f"{parameter.code_name}: {parameter.type_hint}" + if parameter.default is None: + required_params.append(rendered) + else: + default_params.append(f"{rendered} = {parameter.default}") + + if body_param is not None: + required_params.append(body_param) + + all_params = required_params + default_params + return ", ".join(all_params) + (", " if all_params else "") + + +def _body_signature_param(operation: Operation) -> Optional[str]: + if operation.requestBody is None or is_reference_type(operation.requestBody): + if isinstance(operation.requestBody, (Reference, Reference30, Reference31)): + return f"data : {operation.requestBody.ref.split('/')[-1]}" # type: ignore + return None + + rb_content = getattr(operation.requestBody, "content", None) + operation_request_body_types = [ + "application/json", + "text/plain", + "multipart/form-data", + "application/x-www-form-urlencoded", + "application/octet-stream", + ] + if not isinstance(rb_content, dict): + return None + content_type = next((i for i in operation_request_body_types if rb_content.get(i)), None) + if content_type is None: + return None + content = rb_content.get(content_type) + if content is None or not hasattr(content, "media_type_schema"): + return None + mts = getattr(content, "media_type_schema", None) + if isinstance(mts, (Reference, Reference30, Reference31)): + return f"data : {mts.ref.split('/')[-1]}" # type: ignore + if isinstance(mts, (Schema, Schema30, Schema31)): + return f"data : {type_converter(mts, True).converted_type}" # type: ignore + return None + + +def _parameter_dict_items(parameters: List[GeneratedParameter]) -> List[str]: + return [f"{p.wire_name!r}: {p.value_expression}" for p in parameters] + + +def _resolved_path_name(path_name: str, path_params: List[GeneratedParameter]) -> str: + resolved = path_name + for parameter in path_params: + resolved = re.sub( + r"\{" + re.escape(parameter.wire_name) + r"\}", + "{" + (parameter.value_expression or parameter.code_name) + "}", + resolved, + ) + return clean_up_path_name(resolved) + + +def _collect_client_parameters( + operations: List[ServiceOperation], + source: ParameterSource, +) -> List[GeneratedParameter]: + collected: Dict[str, GeneratedParameter] = {} + wire_by_code_name: Dict[str, str] = {} + + for operation in operations: + for parameter in operation.path_params + operation.query_params + operation.header_params: + if parameter.source != source: + continue + + if parameter.code_name in RESERVED_CLIENT_MEMBER_NAMES: + raise ValueError(f"Configured parameter code_name {parameter.code_name!r} is reserved") + + existing_wire_name = wire_by_code_name.get(parameter.code_name) + if existing_wire_name is not None and existing_wire_name != parameter.wire_name: + raise ValueError( + f"Configured parameter code_name {parameter.code_name!r} is used by both " + f"{existing_wire_name!r} and {parameter.wire_name!r}" + ) + + wire_by_code_name[parameter.code_name] = parameter.wire_name + existing = collected.get(parameter.code_name) + if existing is None: + collected[parameter.code_name] = parameter + continue + + if not existing.required and parameter.required: + existing.required = True + existing.field_type_hint = parameter.field_type_hint + existing.field_default = parameter.field_default + + return list(collected.values()) + + +def generate_params(operation: Operation) -> str: + def _schema_default(schema: Any) -> Any: + default = getattr(schema, "default", None) + if default is None: + return None + return default + + def _default_suffix(schema: Any, required: bool) -> str: + if required: + return "" + default = _schema_default(schema) + return f" = {default!r}" if default is not None else " = None" + + def _generate_params_from_content(content: Any): + # Accept reference from either 3.0 or 3.1 + if isinstance(content, (Reference, Reference30, Reference31)): + return f"data : {content.ref.split('/')[-1]}" # type: ignore + elif isinstance(content, (Schema, Schema30, Schema31)): + return f"data : {type_converter(content, True).converted_type}" # type: ignore + else: # pragma: no cover + raise Exception(f"Unsupported request body schema type: {type(content)}") + + if operation.parameters is None and operation.requestBody is None: + return "" + + params = "" + default_params = "" + if operation.parameters is not None: + for param in operation.parameters: + if not isinstance(param, (Parameter30, Parameter31)): + continue # pragma: no cover + converted_result = "" + required = False + param_name_cleaned = common.normalize_symbol(param.name) + + if isinstance(param.param_schema, Schema30) or isinstance(param.param_schema, Schema31): + converted_result = ( + f"{param_name_cleaned} : {type_converter(param.param_schema, param.required).converted_type}" + + _default_suffix(param.param_schema, param.required) + ) + required = param.required + elif isinstance(param.param_schema, Reference30) or isinstance(param.param_schema, Reference31): + converted_result = f"{param_name_cleaned} : {param.param_schema.ref.split('/')[-1]}" + ( + "" + if isinstance(param, Reference30) or isinstance(param, Reference31) or param.required + else " = None" + ) + required = isinstance(param, Reference) or param.required + + if required: + params += f"{converted_result}, " + else: + default_params += f"{converted_result}, " + + operation_request_body_types = [ + "application/json", + "text/plain", + "multipart/form-data", + "application/x-www-form-urlencoded", + "application/octet-stream", + ] + + if operation.requestBody is not None and not is_reference_type(operation.requestBody): + # Safe access only if it's a concrete RequestBody object + rb_content = getattr(operation.requestBody, "content", None) + if isinstance(rb_content, dict) and any(rb_content.get(i) is not None for i in operation_request_body_types): + get_keyword = [i for i in operation_request_body_types if rb_content.get(i)][0] + content = rb_content.get(get_keyword) + if content is not None and hasattr(content, "media_type_schema"): + mts = getattr(content, "media_type_schema", None) + if isinstance( + mts, + (Reference, Reference30, Reference31, Schema, Schema30, Schema31), + ): + params += f"{_generate_params_from_content(mts)}, " + else: # pragma: no cover + raise Exception(f"Unsupported media type schema for {str(operation)}: {type(mts)}") + # else: silently ignore unsupported body shapes (could extend later) + # Replace - with _ in params + params = params.replace("-", "_") + default_params = default_params.replace("-", "_") + + return params + default_params + + +def generate_operation_id(operation: Operation, http_op: str, path_name: Optional[str] = None) -> str: + if operation.operationId is not None: + return common.normalize_symbol(operation.operationId) + elif path_name is not None: + return common.normalize_symbol(f"{http_op}_{path_name}") + else: + raise Exception( + f"OperationId is not defined for {http_op} of path_name {path_name} --> {operation.summary}" + ) # pragma: no cover + + +def _generate_params(operation: Operation, param_in: Literal["query", "header"] = "query"): + if operation.parameters is None: + return [] + + params = [] + for param in operation.parameters: + if isinstance(param, (Parameter30, Parameter31)) and param.param_in == param_in: + param_name_cleaned = common.normalize_symbol(param.name) + params.append(f"{param.name!r} : {param_name_cleaned}") + + return params + + +def generate_query_params(operation: Operation) -> List[str]: + return _generate_params(operation, "query") + + +def generate_header_params(operation: Operation) -> List[str]: + return _generate_params(operation, "header") + + +def generate_operation_parameters( + operation: Operation, + config: PydanticOpenAPIGeneratorConfig, + param_in: Optional[Literal["path", "query", "header", "cookie"]] = None, +) -> List[GeneratedParameter]: + if operation.parameters is None: + return [] + + parameters: List[GeneratedParameter] = [] + for param in operation.parameters: + if not isinstance(param, (Parameter30, Parameter31)): + continue + if param_in is not None and param.param_in != param_in: + continue + parameters.append(_resolve_parameter(param, config)) + + return parameters + + +def _is_binary_schema(schema: Any) -> bool: + schema_format = getattr(schema, "schema_format", None) + schema_type = getattr(schema, "type", None) + return (schema_type == "string" or str(schema_type) == "DataType.STRING") and schema_format == "binary" + + +def _body_kind_for_content( + content_type: Optional[str], schema: Any = None +) -> Literal["empty", "json", "text", "binary"]: + if content_type is None: + return "empty" + lowered = content_type.lower() + if lowered == "application/json" or lowered.endswith("+json"): + return "json" + if lowered == "application/pdf" or lowered == "application/octet-stream" or _is_binary_schema(schema): + return "binary" + if lowered.startswith("text/"): + return "text" + return "binary" + + +def _response_variant_from_schema( + status_code: int, + content_type: Optional[str], + inner_schema: Any, +) -> ResponseVariant: + body_kind = _body_kind_for_content(content_type, inner_schema) + if body_kind == "empty": + return ResponseVariant(status_code=status_code, content_type=content_type, body_kind="empty") + + if body_kind == "binary": + return ResponseVariant( + status_code=status_code, + content_type=content_type, + type=TypeConversion(original_type=content_type or "binary", converted_type="bytes"), + body_kind="binary", + ) + + if body_kind == "text": + return ResponseVariant( + status_code=status_code, + content_type=content_type, + type=TypeConversion(original_type=content_type or "text", converted_type="str"), + body_kind="text", + ) + + if is_reference_type(inner_schema): + type_conv = TypeConversion( + original_type=inner_schema.ref, # type: ignore + converted_type=inner_schema.ref.split("/")[-1], # type: ignore + import_types=[inner_schema.ref.split("/")[-1]], # type: ignore + ) + return ResponseVariant( + status_code=status_code, + content_type=content_type, + type=type_conv, + complex_type=True, + body_kind="json", + ) + + if is_schema_type(inner_schema): + disc = getattr(inner_schema, "discriminator", None) + used = getattr(inner_schema, "oneOf", None) or getattr(inner_schema, "anyOf", None) + disc_key = getattr(disc, "propertyName", None) if disc is not None else None + + if disc_key and used and all(is_reference_type(s) for s in used): + member_models = [common.normalize_symbol(s.ref.split("/")[-1]) for s in used] # type: ignore + alias_name = common.normalize_symbol(_common_suffix_many(member_models)) or "Response" + + type_conv = TypeConversion( + original_type="discriminated_union", + converted_type=alias_name, + import_types=None, + ) + return ResponseVariant( + status_code=status_code, + content_type=content_type, + type=type_conv, + complex_type=True, + body_kind="json", + ) + + converted_result = type_converter(inner_schema, True) # type: ignore + list_type = None + if "array" in converted_result.original_type and isinstance(converted_result.import_types, list): + matched = re.findall(r"List\[(.+)\]", converted_result.converted_type) + if len(matched) > 0: + list_type = matched[0] + else: # pragma: no cover + raise Exception(f"Unable to parse list type from {converted_result.converted_type}") + + return ResponseVariant( + status_code=status_code, + content_type=content_type, + type=converted_result, + complex_type=bool(converted_result.import_types and len(converted_result.import_types) > 0), + list_type=list_type, + body_kind="json", + ) + + return ResponseVariant(status_code=status_code, content_type=content_type, body_kind="empty") + + +def _response_variants_for_response(status_code: int, response: Union[Response, Reference]) -> List[ResponseVariant]: + if is_reference_type(response): + media_type = create_media_type_for_reference(response) + inner_schema = getattr(media_type, "media_type_schema", None) + return [_response_variant_from_schema(status_code, "application/json", inner_schema)] + + if not is_response_type(response): + return [] + + content = getattr(response, "content", None) + if not isinstance(content, dict) or not content: + return [ResponseVariant(status_code=status_code, content_type=None, body_kind="empty")] + + variants: List[ResponseVariant] = [] + for content_type, media_type in content.items(): + if not is_media_type(media_type): + continue + inner_schema = getattr(media_type, "media_type_schema", None) + variants.append(_response_variant_from_schema(status_code, content_type, inner_schema)) + + return variants or [ResponseVariant(status_code=status_code, content_type=None, body_kind="empty")] + + +def _return_type_hint(variants: List[ResponseVariant]) -> str: + hints: List[str] = [] + for variant in variants: + if variant.body_kind == "empty" or variant.type is None: + hint = "None" + elif variant.list_type is not None: + hint = f"list[{variant.list_type}]" + else: + hint = variant.type.converted_type + if hint not in hints: + hints.append(hint) + if not hints: + return "None" + return hints[0] if len(hints) == 1 else " | ".join(hints) + + +def _variant_deserialization_key(variant: ResponseVariant) -> tuple: + return ( + variant.body_kind, + variant.type.converted_type if variant.type is not None else None, + variant.complex_type, + variant.list_type, + ) + + +def _unambiguous_content_handlers( + variants: List[ResponseVariant], +) -> List[ResponseContentHandler]: + variants_by_content_type: Dict[str, List[ResponseVariant]] = {} + for variant in variants: + if variant.content_type is None or variant.body_kind == "empty": + continue + variants_by_content_type.setdefault(variant.content_type.lower(), []).append(variant) + + handlers: List[ResponseContentHandler] = [] + for content_type, content_variants in variants_by_content_type.items(): + keys = {_variant_deserialization_key(variant) for variant in content_variants} + if len(keys) != 1: + continue + + variant = content_variants[0] + handlers.append( + ResponseContentHandler( + content_type=content_type, + type=variant.type, + complex_type=variant.complex_type, + list_type=variant.list_type, + body_kind=variant.body_kind, # type: ignore[arg-type] + ) + ) + + return handlers + + +def generate_return_type(operation: Operation) -> OpReturnType: + if operation.responses is None: + return OpReturnType(type=None, status_code=200, complex_type=False) + + good_responses: List[Tuple[int, Union[Response, Reference]]] = [ + (int(status_code), response) + for status_code, response in operation.responses.items() + if status_code.startswith("2") + ] + if len(good_responses) == 0: + return OpReturnType(type=None, status_code=200, complex_type=False) + + variants: List[ResponseVariant] = [] + for status_code, response in good_responses: + variants.extend(_response_variants_for_response(status_code, response)) + + first_variant = variants[0] if variants else ResponseVariant(status_code=good_responses[0][0]) + return OpReturnType( + type=first_variant.type, + status_code=first_variant.status_code, + complex_type=first_variant.complex_type, + list_type=first_variant.list_type, + variants=variants, + accept_content_types=list(dict.fromkeys(v.content_type for v in variants if v.content_type is not None)), + unambiguous_content_handlers=_unambiguous_content_handlers(variants), + return_type_hint=_return_type_hint(variants), + ) + + +def clean_up_path_name(path_name: str) -> str: + # Clean up path name: only replace dashes inside curly brackets for f-string compatibility, keep other dashes + def _replace_bracket_dashes(match): + return "{" + match.group(1).replace("-", "_") + "}" + + return re.sub(r"\{([^}/]+)\}", _replace_bracket_dashes, path_name) + + +def generate_clients( + openapi: Any, + paths: Dict[str, PathItem], + library_config: LibraryConfig, + env_token_name: Optional[str], + pydantic_version: PydanticVersion, + config: Optional[PydanticOpenAPIGeneratorConfig] = None, +) -> List[Model]: + """ + Generate two client modules: + - sync_client.py (SyncClient) + - async_client.py (AsyncClient) + """ + jinja_env = create_jinja_env() + + service_ops: List[ServiceOperation] = [] + generator_config = config or PydanticOpenAPIGeneratorConfig() + + def _generate_service_operation( + op: Operation, + path_obj: PathItem, + path_name: str, + http_operation: str, + async_type: bool, + ) -> ServiceOperation: + path_level_params = [] + if hasattr(path_obj, "parameters") and path_obj.parameters is not None: + path_level_params = [p for p in path_obj.parameters if p is not None] + if path_level_params: + existing_names = set() + if op.parameters is not None: + for p in op.parameters: + if isinstance(p, (Parameter30, Parameter31)): + existing_names.add(p.name) + for p in path_level_params: + if isinstance(p, (Parameter30, Parameter31)) and p.name not in existing_names: + if op.parameters is None: + op.parameters = [] # type: ignore + op.parameters.append(p) # type: ignore + + request_body = generate_request_body(op) + body_param = None if request_body is None else request_body.expression + path_params = generate_operation_parameters(op, generator_config, "path") + query_params = generate_operation_parameters(op, generator_config, "query") + header_params = generate_operation_parameters(op, generator_config, "header") + all_params = path_params + query_params + header_params + params = _method_signature(all_params, _body_signature_param(op) if body_param is not None else None) + path_name = _resolved_path_name(path_name, path_params) + + placeholder_names = [m.group(1) for m in re.finditer(r"\{([^}/]+)\}", path_name)] + existing_param_names = {p.code_name for p in all_params} + for ph in placeholder_names: + norm_ph = common.normalize_symbol(ph) + if norm_ph not in existing_param_names and norm_ph: + params = f"{norm_ph}: Any, " + params + + operation_id = generate_operation_id(op, http_operation, path_name) + return_type = generate_return_type(op) + + so = ServiceOperation( + params=params, + operation_id=operation_id, + path_params=path_params, + query_params=query_params, + header_params=header_params, + return_type=return_type, + operation=op, + pathItem=path_obj, + content="", + async_client=async_type, + body_param=body_param, + request_body=request_body, + path_name=path_name, + method=http_operation, + is_sse=operation_is_sse(op), + sse_data_handler=generate_sse_data_handler(op), + use_orjson=common.get_use_orjson(), + ) + + return so + + for path_name, path in paths.items(): + clean_path_name = clean_up_path_name(path_name) + for http_operation in HTTP_OPERATIONS: + op = getattr(path, http_operation) + if op is None: + continue + + if library_config.include_sync: + service_ops.append(_generate_service_operation(op, path, clean_path_name, http_operation, False)) + if library_config.include_async: + service_ops.append(_generate_service_operation(op, path, clean_path_name, http_operation, True)) + + sync_ops = [so for so in service_ops if not so.async_client] + async_ops = [so for so in service_ops if so.async_client] + client_fields = _collect_client_parameters(service_ops, ParameterSource.CLASS_VAR) + client_functions = _collect_client_parameters(service_ops, ParameterSource.FUNCTION) + + openapi_dump = openapi.model_dump() if hasattr(openapi, "model_dump") else {} + + sync_content = jinja_env.get_template(SYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2).render( + **openapi_dump, + env_token_name=env_token_name, + operations=[so.model_dump() for so in sync_ops], + client_fields=[p.model_dump() for p in client_fields], + client_functions=[p.model_dump() for p in client_functions], + ) + async_content = jinja_env.get_template(ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2).render( + **openapi_dump, + env_token_name=env_token_name, + operations=[so.model_dump() for so in async_ops], + client_fields=[p.model_dump() for p in client_fields], + client_functions=[p.model_dump() for p in client_functions], + ) + + compile(sync_content, "", "exec") + compile(async_content, "", "exec") + + clients: List[Model] = [ + Model( + file_name="sync_client", + content=sync_content, + openapi_object={}, + properties=[], + ), + Model( + file_name="async_client", + content=async_content, + openapi_object={}, + properties=[], + ), + ] + + return clients diff --git a/src/openapi_python_generator/language_converters/python/common.py b/src/pydantic_openapi_generator/language_converters/python/common.py similarity index 100% rename from src/openapi_python_generator/language_converters/python/common.py rename to src/pydantic_openapi_generator/language_converters/python/common.py diff --git a/src/pydantic_openapi_generator/language_converters/python/exception_generator.py b/src/pydantic_openapi_generator/language_converters/python/exception_generator.py new file mode 100644 index 0000000..162f5a3 --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/exception_generator.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from pydantic_openapi_generator.language_converters.python.jinja_config import ( + HTTP_EXCEPTION_TEMPLATE, + create_jinja_env, +) +from pydantic_openapi_generator.models import Model + + +def generate_exceptions() -> list[Model]: + """ + Generate shared exception modules (package-local support code). + """ + jinja_env = create_jinja_env() + + http_exception = Model( + file_name="http_exception", + content=jinja_env.get_template(HTTP_EXCEPTION_TEMPLATE).render(), + openapi_object=None, # Model.openapi_object is optional now + properties=[], + ) + + return [http_exception] diff --git a/src/pydantic_openapi_generator/language_converters/python/generator.py b/src/pydantic_openapi_generator/language_converters/python/generator.py new file mode 100644 index 0000000..7250705 --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/generator.py @@ -0,0 +1,65 @@ +from typing import Optional, Union + +from openapi_pydantic.v3.v3_0 import OpenAPI as OpenAPI30 +from openapi_pydantic.v3.v3_1 import OpenAPI as OpenAPI31 + +from pydantic_openapi_generator.common import PydanticVersion +from pydantic_openapi_generator.config.generator_config import ( + PydanticOpenAPIGeneratorConfig, +) +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.client_generator import ( + generate_clients, +) +from pydantic_openapi_generator.language_converters.python.exception_generator import ( + generate_exceptions, +) +from pydantic_openapi_generator.language_converters.python.model_generator import ( + generate_models, + generate_response_union_alias_models, +) +from pydantic_openapi_generator.models import ConversionResult, LibraryConfig + +# Type alias for both OpenAPI versions +OpenAPISpec = Union[OpenAPI30, OpenAPI31] + + +def generator( + data: OpenAPISpec, + library_config: LibraryConfig, + env_token_name: Optional[str] = None, + use_orjson: bool = False, + custom_template_path: Optional[str] = None, + pydantic_version: PydanticVersion = PydanticVersion.V2, + config: Optional[PydanticOpenAPIGeneratorConfig] = None, +) -> ConversionResult: + """ + Generate Python code from an OpenAPI 3.0+ specification. + """ + + common.set_use_orjson(use_orjson) + common.set_custom_template_path(custom_template_path) + + if data.components is not None: + models = generate_models(data.components, pydantic_version) + else: + models = [] + + # Generate response union alias models (e.g. AuthorizeResponse) from paths + # so client return types that reference those aliases have corresponding + # model modules available in the output. + if data.paths is not None: + resp_alias_models = generate_response_union_alias_models(data.paths, pydantic_version) + if resp_alias_models: + models.extend(resp_alias_models) + + if data.paths is not None: + clients = generate_clients(data, data.paths, library_config, env_token_name, pydantic_version, config) + else: + clients = [] + + return ConversionResult( + models=models, + clients=clients, + exceptions=generate_exceptions(), + ) diff --git a/src/openapi_python_generator/language_converters/python/jinja_config.py b/src/pydantic_openapi_generator/language_converters/python/jinja_config.py similarity index 61% rename from src/openapi_python_generator/language_converters/python/jinja_config.py rename to src/pydantic_openapi_generator/language_converters/python/jinja_config.py index 1a0704b..5373671 100644 --- a/src/openapi_python_generator/language_converters/python/jinja_config.py +++ b/src/pydantic_openapi_generator/language_converters/python/jinja_config.py @@ -1,16 +1,17 @@ from pathlib import Path -from jinja2 import ChoiceLoader, Environment, FileSystemLoader +from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape from . import common ENUM_TEMPLATE = "enum.jinja2" MODELS_TEMPLATE = "models.jinja2" MODELS_TEMPLATE_PYDANTIC_V2 = "models_pydantic_2.jinja2" -SERVICE_TEMPLATE = "service.jinja2" -HTTPX_TEMPLATE = "httpx.jinja2" -API_CONFIG_TEMPLATE = "apiconfig.jinja2" -API_CONFIG_TEMPLATE_PYDANTIC_V2 = "apiconfig_pydantic_2.jinja2" +ALIAS_UNION_TEMPLATE = "alias_union.jinja2" +DISCRIMINATOR_ENUM_TEMPLATE = "discriminator_enum.jinja2" +HTTP_EXCEPTION_TEMPLATE = "http_exception.jinja2" +SYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2 = "sync_client_httpx_pydantic_2.jinja2" +ASYNC_CLIENT_HTTPX_TEMPLATE_PYDANTIC_V2 = "async_client_httpx_pydantic_2.jinja2" TEMPLATE_PATH = Path(__file__).parent / "templates" @@ -27,8 +28,9 @@ def create_jinja_env(): if custom_template_path is not None else FileSystemLoader(TEMPLATE_PATH) ), - autoescape=True, + autoescape=select_autoescape(default_for_string=False), trim_blocks=True, + lstrip_blocks=True, ) environment.filters["normalize_symbol"] = common.normalize_symbol diff --git a/src/pydantic_openapi_generator/language_converters/python/model_generator.py b/src/pydantic_openapi_generator/language_converters/python/model_generator.py new file mode 100644 index 0000000..f1db954 --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/model_generator.py @@ -0,0 +1,1024 @@ +from __future__ import annotations + +import itertools +import re +from dataclasses import dataclass +from typing import Dict, List, Optional, Set, Tuple, Union + +import click +from openapi_pydantic.v3 import Operation, PathItem +from openapi_pydantic.v3.v3_0 import ( + Components as Components30, +) +from openapi_pydantic.v3.v3_0 import ( + Reference as Reference30, +) +from openapi_pydantic.v3.v3_0 import Response as Response30 +from openapi_pydantic.v3.v3_0 import ( + Schema as Schema30, +) +from openapi_pydantic.v3.v3_1 import ( + Components as Components31, +) +from openapi_pydantic.v3.v3_1 import ( + Reference as Reference31, +) +from openapi_pydantic.v3.v3_1 import Response as Response31 +from openapi_pydantic.v3.v3_1 import ( + Schema as Schema31, +) + +from pydantic_openapi_generator.common import PydanticVersion +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.jinja_config import ( + ALIAS_UNION_TEMPLATE, + DISCRIMINATOR_ENUM_TEMPLATE, + ENUM_TEMPLATE, + MODELS_TEMPLATE, + MODELS_TEMPLATE_PYDANTIC_V2, + create_jinja_env, +) +from pydantic_openapi_generator.models import Model, Property, TypeConversion + +# Type aliases for compatibility +Schema = Union[Schema30, Schema31] +Reference = Union[Reference30, Reference31] +Components = Union[Components30, Components31] + + +# Map of wrapper component name -> TypeConversion to use instead of generating wrapper module +_REFERENCE_TYPE_OVERRIDES: dict[str, TypeConversion] = {} + + +def _is_null_schema(s: object) -> bool: + t = getattr(s, "type", None) + return t == "null" or str(t) == "DataType.NULL" + + +def _build_nullable_wrapper_overrides(components: Components) -> dict[str, TypeConversion]: + """ + Collapse component schemas shaped like: + X = anyOf/oneOf: [ $ref: Y, {type: null} ] + into an override so refs to X become Optional[Y] without generating X.py. + """ + overrides: dict[str, TypeConversion] = {} + schemas = getattr(components, "schemas", None) + if not schemas: + return overrides + + for schema_name, schema in schemas.items(): + # Only non-discriminator wrappers + if getattr(schema, "discriminator", None) is not None: + continue + + variants = getattr(schema, "anyOf", None) or getattr(schema, "oneOf", None) + if not variants or len(variants) != 2: + continue + + ref = next((v for v in variants if isinstance(v, (Reference30, Reference31))), None) + nul = next((v for v in variants if isinstance(v, (Schema30, Schema31)) and _is_null_schema(v)), None) + if ref is None or nul is None: + continue + + wrapper_name = common.normalize_symbol(schema_name) + target_model = common.normalize_symbol(ref.ref.split("/")[-1]) + + overrides[wrapper_name] = TypeConversion( + original_type=ref.ref, + converted_type=f"Optional[{target_model}]", + import_types=[f"from .{target_model} import {target_model}"], + ) + + return overrides + + +def _get_discriminator_key(schema: Schema) -> Optional[str]: + """ + Return discriminator property name if present on the schema. + openapi-pydantic v3.x uses `schema.discriminator.propertyName` (common), + but we defensively check a couple of variants. + """ + disc = getattr(schema, "discriminator", None) + if disc is None: + return None + + # Most common: propertyName + key = getattr(disc, "propertyName", None) + if key: + return str(key) + + # Defensive fallbacks + key = getattr(disc, "property_name", None) + if key: + return str(key) + + return None + + +def _schema_is_union(schema: Schema) -> bool: + used = schema.oneOf if schema.oneOf is not None else schema.anyOf + return used is not None and len(used) > 0 + + +def _alias_name_for_property(prop_name: str) -> str: + # token_issuer -> TokenIssuer, foo-bar -> FooBar, etc. + parts = re.split(r"[^a-zA-Z0-9]+", prop_name.strip()) + parts = [p for p in parts if p] + return "".join(p[:1].upper() + p[1:] for p in parts) + + +def _dedupe_imports(imports: Optional[List[str]]) -> List[str]: + if not imports: + return [] + seen: Set[str] = set() + out: List[str] = [] + for imp in imports: + if imp and imp not in seen: + seen.add(imp) + out.append(imp) + return out + + +def _render_union_alias_module( + *, + jinja_env, + alias_name: str, + union_type: str, + discriminator_key: Optional[str], + member_imports: List[str], +) -> str: + return jinja_env.get_template(ALIAS_UNION_TEMPLATE).render( + alias_name=alias_name, + union_type=union_type, + discriminator_key=discriminator_key, + member_imports=_dedupe_imports(member_imports), + ) + + +@dataclass(frozen=True) +class DiscriminatorBinding: + enum_name: str + enum_member: str + discriminator_key: str + + +def _enum_member_name(value: str) -> str: + # Make a safe enum member name, e.g. "pkce" -> "PKCE", "oauth2" -> "OAUTH2" + return common.normalize_symbol(str(value)).upper() + + +def _pascal_discriminator(discriminator_key: str) -> str: + """ + Convert a discriminator property name (e.g. "type", "kind", "event_type") + into a PascalCase suffix ("Type", "Kind", "EventType"). + """ + sym = common.normalize_symbol(discriminator_key) + parts = [p for p in sym.replace("-", "_").split("_") if p] + return "".join(p[:1].upper() + p[1:] for p in parts) or "Discriminator" + + +def _pascal_schema_name(schema_name: str) -> str: + """ + Convert a schema name like "token_issuer" into "TokenIssuer" for generated type/enum names. + (We don't want enum class names like `token_issuerType`.) + """ + sym = common.normalize_symbol(schema_name) + parts = [p for p in sym.replace("-", "_").split("_") if p] + return "".join(p[:1].upper() + p[1:] for p in parts) or "Schema" + + +def _invert_discriminator_mapping(mapping: Optional[dict]) -> Dict[str, str]: + """ + discriminator.mapping is usually { "": "<$ref>" } + Return { "<$ref>": "" } + """ + if not mapping: + return {} + inv: Dict[str, str] = {} + for k, v in mapping.items(): + if isinstance(v, str): + inv[v] = str(k) + return inv + + +def _discover_discriminated_unions( + components: Components, +) -> Tuple[Dict[str, DiscriminatorBinding], Dict[str, List[Tuple[str, str]]]]: + """ + Discover discriminated unions in BOTH: + - top-level component schemas (schema.discriminator + schema.oneOf) + - inline/property schemas (property_schema.discriminator + property_schema.oneOf) + + Returns: + - bindings: { "": DiscriminatorBinding(...) } + - enum_members_by_name: { "": [(MEMBER_NAME, member_value), ...] } + """ + bindings: Dict[str, DiscriminatorBinding] = {} + enum_members_by_name: Dict[str, List[Tuple[str, str]]] = {} + + if not getattr(components, "schemas", None): + return bindings, enum_members_by_name + + def register_union(alias_name: str, union_schema: Schema) -> None: + disc = getattr(union_schema, "discriminator", None) + one_of = getattr(union_schema, "oneOf", None) + if disc is None or not one_of: + return + + # openapi_pydantic uses propertyName, but be defensive + discriminator_key = getattr(disc, "propertyName", None) or getattr(disc, "property_name", None) + if not discriminator_key: + return + + enum_name = f"{_pascal_schema_name(alias_name)}{_pascal_discriminator(discriminator_key)}" + ref_to_value = _invert_discriminator_mapping(getattr(disc, "mapping", None)) + + members: List[Tuple[str, str]] = [] + for sub in one_of: + if not isinstance(sub, (Reference30, Reference31)): + continue + ref = sub.ref + member_model = common.normalize_symbol(ref.split("/")[-1]) + disc_value = ref_to_value.get(ref) or member_model + + member_name = _enum_member_name(disc_value) + members.append((member_name, disc_value)) + + bindings[member_model] = DiscriminatorBinding( + enum_name=enum_name, + enum_member=member_name, + discriminator_key=discriminator_key, + ) + + if members: + # de-dupe by enum member name + seen = set() + deduped: List[Tuple[str, str]] = [] + for mn, mv in members: + if mn in seen: + continue + seen.add(mn) + deduped.append((mn, mv)) + enum_members_by_name[enum_name] = deduped + + # 1) top-level discriminated unions + for schema_name, schema in components.schemas.items(): + disc = getattr(schema, "discriminator", None) + one_of = getattr(schema, "oneOf", None) + if disc is not None and one_of: + register_union(schema_name, schema) + + # 2) inline/property discriminated unions + for _parent_name, parent_schema in components.schemas.items(): + props = getattr(parent_schema, "properties", None) or {} + for prop_name, prop_schema in props.items(): + disc = getattr(prop_schema, "discriminator", None) + one_of = getattr(prop_schema, "oneOf", None) + if disc is not None and one_of: + # alias name should be based on the property name (e.g. oauth2_client -> OAuth2Client) + register_union(prop_name, prop_schema) + + return bindings, enum_members_by_name + + +def _build_discriminator_bindings(components: Components) -> Dict[str, DiscriminatorBinding]: + """ + Scan components.schemas for discriminator-based oneOf schemas and return: + { "": DiscriminatorBinding(...) } + + We use: + - discriminator.propertyName as the key + - discriminator.mapping (preferred) to get per-member discriminator values + - fallback: schema name when mapping not present + """ + bindings: Dict[str, DiscriminatorBinding] = {} + + if not getattr(components, "schemas", None): + return bindings + + for schema_name, schema in components.schemas.items(): + disc = getattr(schema, "discriminator", None) + one_of = getattr(schema, "oneOf", None) + + if disc is None or not one_of: + continue + + discriminator_key = getattr(disc, "propertyName", None) or getattr(disc, "property_name", None) + if discriminator_key is None: + continue + + enum_name = f"{_pascal_schema_name(schema_name)}{_pascal_discriminator(discriminator_key)}" + + mapping = getattr(disc, "mapping", None) or {} + # invert mapping to get $ref -> value + ref_to_value: Dict[str, str] = {ref: value for value, ref in mapping.items()} + + for sub in one_of: + if not (isinstance(sub, Reference30) or isinstance(sub, Reference31)): + continue + + ref = sub.ref + member_model = common.normalize_symbol(ref.split("/")[-1]) + + disc_value = ref_to_value.get(ref) + if disc_value is None: + disc_value = member_model + + bindings[member_model] = DiscriminatorBinding( + enum_name=enum_name, + enum_member=_enum_member_name(disc_value), + discriminator_key=discriminator_key, + ) + + return bindings + + +def _common_suffix(a: str, b: str) -> str: + # longest common suffix + i = 1 + while i <= min(len(a), len(b)) and a[-i] == b[-i]: + i += 1 + return a[-(i - 1) :] if i > 1 else "" + + +def _common_suffix_many(names: List[str]) -> str: + if not names: + return "" + suf = names[0] + for n in names[1:]: + suf = _common_suffix(suf, n) + if not suf: + break + return suf + + +def _alias_name_for_response_union(member_models: List[str], fallback: str) -> str: + suf = _common_suffix_many(member_models) + suf = common.normalize_symbol(suf) + if len(suf) >= 4: + return suf + return common.normalize_symbol(fallback) + + +def generate_response_union_alias_models( + paths: Dict[str, PathItem], + pydantic_version: PydanticVersion = PydanticVersion.V2, +) -> List[Model]: + """ + Finds response schemas like: + content.application/json.schema.oneOf + discriminator + and emits a named alias module via alias_union.jinja2. + """ + jinja_env = create_jinja_env() + out: Dict[str, Model] = {} + + for _path_name, path in paths.items(): + for http_method in ["get", "post", "put", "delete", "patch", "head", "options", "trace"]: + op: Optional[Operation] = getattr(path, http_method, None) + if op is None or op.responses is None: + continue + + for status_code, resp in op.responses.items(): + if not str(status_code).startswith("2"): + continue + if not isinstance(resp, (Response30, Response31)): + continue + + content = getattr(resp, "content", None) + if not isinstance(content, dict): + continue + + mt = content.get("application/json") + if mt is None: + continue + + schema = getattr(mt, "media_type_schema", None) + if not isinstance(schema, (Schema30, Schema31)): + continue + + disc_key = _get_discriminator_key(schema) + used = schema.oneOf if schema.oneOf is not None else schema.anyOf + if not disc_key or not used: + continue + + # Only support ref-only unions (your case) + member_models: List[str] = [] + for sub in used: + if not isinstance(sub, (Reference30, Reference31)): + member_models = [] + break + member_models.append(common.normalize_symbol(sub.ref.split("/")[-1])) + if len(member_models) < 2: + continue + + alias_name = _alias_name_for_response_union( + member_models, + fallback=f"{common.normalize_symbol(op.operationId or 'Response')}Response", + ) + + # de-dupe + if alias_name in out: + continue + + union_type = "Union[" + ", ".join(member_models) + "]" + member_imports = [f"from .{m} import {m}" for m in member_models] + + alias_content = _render_union_alias_module( + jinja_env=jinja_env, + alias_name=alias_name, + union_type=union_type, + discriminator_key=disc_key, + member_imports=member_imports, + ) + + # placeholder schema for Model.openapi_object requirement + placeholder_schema = Schema31() if isinstance(schema, Schema31) else Schema30() + + out[alias_name] = Model( + file_name=alias_name, + content=alias_content, + openapi_object=placeholder_schema, + properties=[], + ) + + return list(out.values()) + + +def type_converter( # noqa: C901 + schema: Union[Schema, Reference], + required: bool = False, + model_name: Optional[str] = None, +) -> TypeConversion: + """ + Converts an OpenAPI type to a Python type. + :param schema: Schema or Reference containing the type to be converted + :param model_name: Name of the original model on which the type is defined + :param required: Flag indicating if the type is required by the class + :return: The converted type + """ + # Handle Reference objects by converting them to type references + if isinstance(schema, Reference30) or isinstance(schema, Reference31): + import_type = common.normalize_symbol(schema.ref.split("/")[-1]) + # Nullable-wrapper collapse: ref to X may be overridden to Optional[Y] + override = _REFERENCE_TYPE_OVERRIDES.get(import_type) + if override is not None: + return TypeConversion( + original_type=schema.ref, + converted_type=override.converted_type, + import_types=override.import_types, + ) + + if required: + converted_type = import_type + else: + converted_type = f"Optional[{import_type}]" + + return TypeConversion( + original_type=schema.ref, + converted_type=converted_type, + import_types=([f"from .{import_type} import {import_type}"] if import_type != model_name else None), + ) + + if required: + pre_type = "" + post_type = "" + else: + pre_type = "Optional[" + post_type = "]" + + original_type = ( + schema.type.value + if hasattr(schema.type, "value") and schema.type is not None + else str(schema.type) + if schema.type is not None + else "object" + ) + import_types: Optional[List[str]] = None + + if schema.allOf is not None: + conversions = [] + for sub_schema in schema.allOf: + if isinstance(sub_schema, Schema30) or isinstance(sub_schema, Schema31): + conversions.append(type_converter(sub_schema, True)) + else: + import_type = common.normalize_symbol(sub_schema.ref.split("/")[-1]) + if import_type == model_name and model_name is not None: + conversions.append( + TypeConversion( + original_type=sub_schema.ref, + converted_type='"' + model_name + '"', + import_types=None, + ) + ) + else: + import_types = [f"from .{import_type} import {import_type}"] + conversions.append( + TypeConversion( + original_type=sub_schema.ref, + converted_type=import_type, + import_types=import_types, + ) + ) + + original_type = "tuple<" + ",".join([i.original_type for i in conversions]) + ">" + if len(conversions) == 1: + converted_type = conversions[0].converted_type + else: + converted_type = "Tuple[" + ",".join([i.converted_type for i in conversions]) + "]" + + converted_type = pre_type + converted_type + post_type + # Collect *all* imports from sub-schemas (not just the first), then dedupe + import_types = ( + _dedupe_imports(list(itertools.chain.from_iterable(i.import_types for i in conversions if i.import_types))) + or None + ) + + elif schema.oneOf is not None or schema.anyOf is not None: + used = schema.oneOf if schema.oneOf is not None else schema.anyOf + used = used if used is not None else [] + # Special-case inline nullable wrapper: (ref | null) => Optional[Ref] + if len(used) == 2: + ref = next((v for v in used if isinstance(v, (Reference30, Reference31))), None) + nul = next((v for v in used if isinstance(v, (Schema30, Schema31)) and _is_null_schema(v)), None) + if ref is not None and nul is not None: + import_type = common.normalize_symbol(ref.ref.split("/")[-1]) + override = _REFERENCE_TYPE_OVERRIDES.get(import_type) + if override is not None: + return TypeConversion( + original_type=f"union<{ref.ref},null>", + converted_type=override.converted_type, + import_types=override.import_types, + ) + return TypeConversion( + original_type=f"union<{ref.ref},null>", + converted_type=f"Optional[{import_type}]", + import_types=([f"from .{import_type} import {import_type}"] if import_type != model_name else None), + ) + conversions = [] + for sub_schema in used: + if isinstance(sub_schema, Schema30) or isinstance(sub_schema, Schema31): + conversions.append(type_converter(sub_schema, True)) + else: + import_type = common.normalize_symbol(sub_schema.ref.split("/")[-1]) + import_types = [f"from .{import_type} import {import_type}"] + conversions.append( + TypeConversion( + original_type=sub_schema.ref, + converted_type=import_type, + import_types=import_types, + ) + ) + original_type = "union<" + ",".join([i.original_type for i in conversions]) + ">" + + if len(conversions) == 1: + converted_type = conversions[0].converted_type + else: + converted_type = "Union[" + ",".join([i.converted_type for i in conversions]) + "]" + + converted_type = pre_type + converted_type + post_type + import_types = list(itertools.chain(*[i.import_types for i in conversions if i.import_types is not None])) + # We only want to auto convert to datetime if orjson is used throghout the code, otherwise we can not + # serialize it to JSON. + elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and ( + schema.schema_format is None or not common.get_use_orjson() + ): + converted_type = pre_type + "str" + post_type + elif ( + (schema.type == "string" or str(schema.type) == "DataType.STRING") + and schema.schema_format is not None + and schema.schema_format.startswith("uuid") + and common.get_use_orjson() + ): + if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric(): + uuid_type = schema.schema_format.upper() + converted_type = pre_type + uuid_type + post_type + import_types = ["from pydantic import " + uuid_type] + else: + converted_type = pre_type + "UUID" + post_type + import_types = ["from uuid import UUID"] + elif (schema.type == "string" or str(schema.type) == "DataType.STRING") and schema.schema_format == "date-time": + converted_type = pre_type + "datetime" + post_type + import_types = ["from datetime import datetime"] + elif schema.type == "integer" or str(schema.type) == "DataType.INTEGER": + converted_type = pre_type + "int" + post_type + elif schema.type == "number" or str(schema.type) == "DataType.NUMBER": + converted_type = pre_type + "float" + post_type + elif schema.type == "boolean" or str(schema.type) == "DataType.BOOLEAN": + converted_type = pre_type + "bool" + post_type + elif schema.type == "array" or str(schema.type) == "DataType.ARRAY": + retVal = pre_type + "List[" + item_imports: List[str] = [] + if isinstance(schema.items, Reference30) or isinstance(schema.items, Reference31): + converted_reference = _generate_property_from_reference( + model_name or "", "", schema.items, schema, required + ) + import_types = converted_reference.type.import_types + original_type = "array<" + converted_reference.type.original_type + ">" + retVal += converted_reference.type.converted_type + elif isinstance(schema.items, Schema30) or isinstance(schema.items, Schema31): + type_str = schema.items.type + if hasattr(type_str, "value"): + type_value = str(type_str.value) if type_str is not None else "unknown" + else: + type_value = str(type_str) if type_str is not None else "unknown" + original_type = "array<" + type_value + ">" + # IMPORTANT: propagate imports from the nested schema (e.g. Union[$ref...]) + item_conv = type_converter(schema.items, True, model_name=model_name) + retVal += item_conv.converted_type + if item_conv.import_types: + item_imports.extend(item_conv.import_types) + else: + original_type = "array" + retVal += "Any" + + converted_type = retVal + "]" + post_type + + # Merge imports from the items schema (when items is a Schema, not a Reference) + if item_imports: + import_types = _dedupe_imports((import_types or []) + item_imports) + elif schema.type == "object" or str(schema.type) == "DataType.OBJECT": + # Support "map" objects: type=object + additionalProperties schema/ref + addl = getattr(schema, "additionalProperties", None) + if isinstance(addl, (Reference30, Reference31)): + # Dict[str, ] + v = type_converter(addl, required=True, model_name=model_name) + converted_type = f"{pre_type}Dict[str, {v.converted_type}]{post_type}" + import_types = _dedupe_imports((import_types or []) + (v.import_types or [])) or None + elif isinstance(addl, (Schema30, Schema31)): + # Dict[str, ], including Union[...] etc. + v = type_converter(addl, required=True, model_name=model_name) + converted_type = f"{pre_type}Dict[str, {v.converted_type}]{post_type}" + import_types = _dedupe_imports((import_types or []) + (v.import_types or [])) or None + else: + converted_type = pre_type + "Dict[str, Any]" + post_type + elif schema.type == "null" or str(schema.type) == "DataType.NULL": + converted_type = pre_type + "None" + post_type + elif schema.type is None: + converted_type = pre_type + "Any" + post_type + else: + # Handle DataType enum types as strings + if hasattr(schema.type, "value"): + # Single DataType enum + if schema.type.value == "string": + # Check for UUID format first + if ( + schema.schema_format is not None + and schema.schema_format.startswith("uuid") + and common.get_use_orjson() + ): + if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric(): + uuid_type = schema.schema_format.upper() + converted_type = pre_type + uuid_type + post_type + import_types = ["from pydantic import " + uuid_type] + else: + converted_type = pre_type + "UUID" + post_type + import_types = ["from uuid import UUID"] + # Check for date-time format + elif schema.schema_format == "date-time": + converted_type = pre_type + "datetime" + post_type + import_types = ["from datetime import datetime"] + else: + converted_type = pre_type + "str" + post_type + elif schema.type.value == "integer": + converted_type = pre_type + "int" + post_type + elif schema.type.value == "number": + converted_type = pre_type + "float" + post_type + elif schema.type.value == "boolean": + converted_type = pre_type + "bool" + post_type + elif schema.type.value == "array": + converted_type = pre_type + "List[Any]" + post_type + elif schema.type.value == "object": + converted_type = pre_type + "Dict[str, Any]" + post_type + elif schema.type.value == "null": + converted_type = pre_type + "None" + post_type + else: + converted_type = pre_type + "str" + post_type # Default fallback + elif isinstance(schema.type, list) and len(schema.type) > 0: + # List of DataType enums - use first one + first_type = schema.type[0] + if hasattr(first_type, "value"): + if first_type.value == "string": + # Check for UUID format first + if ( + schema.schema_format is not None + and schema.schema_format.startswith("uuid") + and common.get_use_orjson() + ): + if len(schema.schema_format) > 4 and schema.schema_format[4].isnumeric(): + uuid_type = schema.schema_format.upper() + converted_type = pre_type + uuid_type + post_type + import_types = ["from pydantic import " + uuid_type] + else: + converted_type = pre_type + "UUID" + post_type + import_types = ["from uuid import UUID"] + # Check for date-time format + elif schema.schema_format == "date-time": + converted_type = pre_type + "datetime" + post_type + import_types = ["from datetime import datetime"] + else: + converted_type = pre_type + "str" + post_type + elif first_type.value == "integer": + converted_type = pre_type + "int" + post_type + elif first_type.value == "number": + converted_type = pre_type + "float" + post_type + elif first_type.value == "boolean": + converted_type = pre_type + "bool" + post_type + elif first_type.value == "array": + converted_type = pre_type + "List[Any]" + post_type + elif first_type.value == "object": + converted_type = pre_type + "Dict[str, Any]" + post_type + elif first_type.value == "null": + converted_type = pre_type + "None" + post_type + else: + converted_type = pre_type + "str" + post_type # Default fallback + else: + converted_type = pre_type + "str" + post_type # Default fallback + else: + converted_type = pre_type + "str" + post_type # Default fallback + + return TypeConversion( + original_type=original_type, + converted_type=converted_type, + import_types=import_types, + ) + + +def _generate_property_from_schema( + model_name: str, name: str, schema: Schema, parent_schema: Optional[Schema] = None +) -> Property: + """ + Generates a property from a schema. It takes the type of the schema and converts it to a python type, and then + creates the according property. + :param model_name: Name of the model this property belongs to + :param name: Name of the schema + :param schema: schema to be converted + :param parent_schema: Component this belongs to + :return: Property + """ + required = parent_schema is not None and parent_schema.required is not None and name in parent_schema.required + + # Pick up OpenAPI default (if present) + default_val = getattr(schema, "default", None) + + if required: + rendered_default = None + else: + # If OpenAPI provides a default, use it. Otherwise fall back to None. + rendered_default = repr(default_val) if default_val is not None else "None" + + return Property( + name=name, + type=type_converter(schema, required, model_name), + required=required, + default=rendered_default, + import_type=None if not required else ([] if name == model_name else [name]), + ) + + +def _generate_property_from_reference( + model_name: str, + name: str, + reference: Reference, + parent_schema: Optional[Schema] = None, + force_required: bool = False, +) -> Property: + """ + Generates a property from a reference. It takes the name of the reference as the type, and then + returns a property type + :param name: Name of the schema + :param reference: reference to be converted + :param parent_schema: Component this belongs to + :param force_required: Force the property to be required + :return: Property and model to be imported by the file + """ + required = ( + parent_schema is not None and parent_schema.required is not None and name in parent_schema.required + ) or force_required + import_model = common.normalize_symbol(reference.ref.split("/")[-1]) + + if import_model == model_name: + type_conv = TypeConversion( + original_type=reference.ref, + converted_type=(import_model if required else 'Optional["' + import_model + '"]'), + import_types=None, + ) + else: + type_conv = TypeConversion( + original_type=reference.ref, + converted_type=(import_model if required else "Optional[" + import_model + "]"), + import_types=[f"from .{import_model} import {import_model}"], + ) + return Property( + name=name, + type=type_conv, + required=required, + default=None if required else "None", + import_type=[import_model], + ) + + +def generate_models(components: Components, pydantic_version: PydanticVersion = PydanticVersion.V2) -> List[Model]: + """ + Receives components from an OpenAPI 3.0+ specification and generates the models from it. + Additionally: + - Detects unions / discriminated unions in property schemas (oneOf/anyOf) + - Emits a named alias module (e.g. TokenIssuer.py) + - Rewrites the property type to use that alias (instead of Union[...]) + """ + models: List[Model] = [] + + if components.schemas is None: + return models + + jinja_env = create_jinja_env() + # Build nullable-wrapper overrides so refs to simple wrappers (X = anyOf[$ref Y, null]) + # are collapsed to Optional[Y] and we avoid generating X.py (which can collide on Windows). + global _REFERENCE_TYPE_OVERRIDES + _REFERENCE_TYPE_OVERRIDES = _build_nullable_wrapper_overrides(components) + + discriminator_bindings, enum_members_by_name = _discover_discriminated_unions(components) + + # Track alias modules so we only create each once + alias_models_by_name: Dict[str, Model] = {} + + for schema_name, schema_or_reference in components.schemas.items(): + name = common.normalize_symbol(schema_name) + + # Don't generate standalone modules for nullable wrapper components + if name in _REFERENCE_TYPE_OVERRIDES: + continue + + # -------------------------- + # Enums + # -------------------------- + if schema_or_reference.enum is not None: + value_dict = schema_or_reference.model_dump() + value_dict["enum"] = [(common.normalize_symbol(str(i)).upper(), i) for i in value_dict["enum"]] + m = Model( + file_name=name, + content=jinja_env.get_template(ENUM_TEMPLATE).render(name=name, **value_dict), + openapi_object=schema_or_reference, + properties=[], + ) + try: + compile(m.content, "", "exec") + models.append(m) + except SyntaxError as e: # pragma: no cover + click.echo(f"Error in model {name}: {e}") + + continue # pragma: no cover + + # -------------------------- + # Normal models + # -------------------------- + properties: List[Property] = [] + property_iterator = schema_or_reference.properties.items() if schema_or_reference.properties is not None else {} + + for prop_name, prop_schema in property_iterator: + # Reference property + if isinstance(prop_schema, Reference30) or isinstance(prop_schema, Reference31): + conv_property = _generate_property_from_reference(name, prop_name, prop_schema, schema_or_reference) + properties.append(conv_property) + continue + + # Schema property + conv_property = _generate_property_from_schema(name, prop_name, prop_schema, schema_or_reference) + + # -------------------------- + # NEW: const / single-value enum -> Literal[...] with default + # -------------------------- + if isinstance(prop_schema, (Schema30, Schema31)): + const_val = getattr(prop_schema, "const", None) + enum_vals = getattr(prop_schema, "enum", None) + + literal_val = None + if const_val is not None: + literal_val = const_val + elif isinstance(enum_vals, list) and len(enum_vals) == 1: + literal_val = enum_vals[0] + + if literal_val is not None: + # make sure discriminator is present for unions: required + default + conv_property.required = True + conv_property.default = repr(literal_val) + + conv_property.type = TypeConversion( + original_type=conv_property.type.original_type, + converted_type=f"Literal[{repr(literal_val)}]", + import_types=_dedupe_imports( + (conv_property.type.import_types or []) + ["from typing import Literal"] + ), + ) + + # If this model is a discriminated union member, and this property + # is the discriminator key, make it a Literal[...] with a default + binding = discriminator_bindings.get(name) + if binding and common.normalize_symbol(conv_property.name) == common.normalize_symbol( + binding.discriminator_key + ): + conv_property.required = True + conv_property.default = f"{binding.enum_name}.{binding.enum_member}" + + extra_imports = [ + "from typing import Literal", + f"from .{binding.enum_name} import {binding.enum_name}", + ] + + conv_property.type = TypeConversion( + original_type=conv_property.type.original_type, + converted_type=f"Literal[{binding.enum_name}.{binding.enum_member}]", + import_types=extra_imports, + ) + + # ----------------------------------------- + # NEW: union / discriminated union factoring + # ----------------------------------------- + if isinstance(prop_schema, (Schema30, Schema31)) and _schema_is_union(prop_schema): + alias_name = _alias_name_for_property(prop_name) + discriminator_key = _get_discriminator_key(prop_schema) + + # Only generate standalone alias modules for DISCRIMINATED unions. + # Plain unions (including nullable wrappers) are left inline. + if discriminator_key is not None: + # Build the union type and gather imports from members. + # Important: we want a NON-optional union for the alias definition. + union_conv = type_converter(prop_schema, required=True, model_name=name) + union_type_str = union_conv.converted_type # e.g. Union[A,B,C] + member_imports = union_conv.import_types or [] + + # Create alias module once + if alias_name not in alias_models_by_name: + alias_content = _render_union_alias_module( + jinja_env=jinja_env, + alias_name=alias_name, + union_type=union_type_str, + discriminator_key=discriminator_key, + member_imports=member_imports, + ) + + # Validate alias module compiles + try: + compile(alias_content, "", "exec") + except SyntaxError as e: # pragma: no cover + click.echo(f"Error in union alias {alias_name}: {e}") # pragma: no cover + + alias_models_by_name[alias_name] = Model( + file_name=alias_name, + content=alias_content, + openapi_object=prop_schema, + properties=[], + ) + + # Rewrite property type to use alias + rewritten_type = alias_name if conv_property.required else f"Optional[{alias_name}]" + conv_property.type = TypeConversion( + original_type=conv_property.type.original_type, + converted_type=rewritten_type, + import_types=[f"from .{alias_name} import {alias_name}"], + ) + + properties.append(conv_property) + + template_name = MODELS_TEMPLATE_PYDANTIC_V2 if pydantic_version == PydanticVersion.V2 else MODELS_TEMPLATE + + generated_content = jinja_env.get_template(template_name).render( + schema_name=name, schema=schema_or_reference, properties=properties + ) + + try: + compile(generated_content, "", "exec") + except SyntaxError as e: # pragma: no cover + click.echo(f"Error in model {name}: {e}") # pragma: no cover + + models.append( + Model( + file_name=name, + content=generated_content, + openapi_object=schema_or_reference, + properties=properties, + ) + ) + + # Ensure enum modules for discriminators are included + enum_models: List[Model] = [] + for enum_name, members in enum_members_by_name.items(): + enum_content = jinja_env.get_template(DISCRIMINATOR_ENUM_TEMPLATE).render(enum_name=enum_name, members=members) + try: + compile(enum_content, "", "exec") + except SyntaxError as e: # pragma: no cover + click.echo(f"Error in enum {enum_name}: {e}") # pragma: no cover + + # Model.openapi_object is required (non-Optional). Enum modules don't map to a real schema, + # so attach a tiny placeholder schema to satisfy validation. + placeholder_schema = Schema31() if isinstance(components, Components31) else Schema30() + + enum_models.append( + Model(file_name=enum_name, content=enum_content, openapi_object=placeholder_schema, properties=[]) + ) + + # Ensure alias modules are included in output + models.extend(alias_models_by_name.values()) + # Append enum modules last + models.extend(enum_models) + + return models diff --git a/src/pydantic_openapi_generator/language_converters/python/templates/alias_union.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/alias_union.jinja2 new file mode 100644 index 0000000..8a2696b --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/templates/alias_union.jinja2 @@ -0,0 +1,17 @@ +{# Renders ONE standalone python module that defines a named Union/Annotated alias #} +{% if discriminator_key -%} +from typing import Annotated, Union +from pydantic import Field +{% else -%} +from typing import Union +{% endif %} + +{% for imp in member_imports -%} +{{ imp }} +{% endfor %} + +{% if discriminator_key -%} +{{ alias_name }} = Annotated[{{ union_type }}, Field(discriminator="{{ discriminator_key }}")] +{% else -%} +{{ alias_name }} = {{ union_type }} +{% endif -%} diff --git a/src/pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 new file mode 100644 index 0000000..cfb99b4 --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/templates/async_client_httpx_pydantic_2.jinja2 @@ -0,0 +1,233 @@ +{% if env_token_name is not none %}import os{% endif %} + +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Any, Dict, Optional, Union +import json + +import httpx +from pydantic import BaseModel, SecretStr, TypeAdapter + +from ..models import * + +{% set _base_url = servers[0].url if servers|length > 0 else "/" %} + + +class AsyncClient(BaseModel): + model_config = {"validate_assignment": True} + + base_url: str = "{{ _base_url }}" + verify: Union[bool, str] = True +{% if env_token_name is none %} + access_token: Optional[str] = None +{% endif %} +{% for field in client_fields %} + {{ field.code_name }}: {{ field.field_type_hint }}{% if field.field_default is not none %} = {{ field.field_default }}{% endif %}{{ "\n" }} +{% endfor %} + + async def get_access_token(self) -> Optional[str]: +{% if env_token_name is not none %} + try: + return os.environ["{{ env_token_name }}"] + except KeyError: + return None +{% else %} + return self.access_token +{% endif %} + + async def set_access_token(self, value: str) -> None: +{% if env_token_name is not none %} + raise Exception( + "This client was generated with an environment variable for the access token. " + "Please set '{{ env_token_name }}'." + ) +{% else %} + self.access_token = value +{% endif %} + +{% for function in client_functions %} + async def {{ function.getter_name }}(self) -> {{ function.base_type_hint }}: + raise NotImplementedError + +{% endfor %} +{% for op in operations %} + async def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%} +AsyncGenerator[str | {% if op.sse_data_handler and op.sse_data_handler.type %}{% if op.sse_data_handler.list_type %}list[{{ op.sse_data_handler.list_type }}]{% else %}{{ op.sse_data_handler.type.converted_type }}{% endif %}{% else %}dict[str, Any]{% endif %}, None] +{%- else -%} +{{ op.return_type.return_type_hint or "None" }} +{%- endif -%}: + base_url = self.base_url +{% for param in op.path_params + op.query_params + op.header_params %} +{% if param.source == "ClassVar" %} +{% if param.is_secret %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else (self.{{ param.code_name }}.get_secret_value() if self.{{ param.code_name }} is not None else None) +{% else %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.code_name }} +{% endif %} +{% elif param.source == "Function" %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else await self.{{ param.getter_name }}() +{% endif %} +{% endfor %} + path = f"{{ op.path_name }}" + + headers = { +{% if op.request_body and op.request_body.content_type %} + "Content-Type": "{{ op.request_body.content_type }}", +{% elif op.body_param and not op.request_body %} + "Content-Type": "application/json", +{% endif %} +{% if op.is_sse %} + "Accept": "text/event-stream", +{% else %} + "Accept": "{{ op.return_type.accept_content_types | join(', ') or 'application/json' }}", +{% endif %} +{% for hp in op.header_params %} + "{{ hp.wire_name }}": {{ hp.value_expression }}, +{% endfor %} + } + headers = {k: v for (k, v) in headers.items() if v is not None} + + _token = await self.get_access_token() + if _token: + headers["Authorization"] = f"Bearer {_token}" + + query_params: Dict[str, Any] = { +{% for qp in op.query_params %} + "{{ qp.wire_name }}": {{ qp.value_expression }}, +{% endfor %} + } + query_params = {k: v for (k, v) in query_params.items() if v is not None} + +{% if op.is_sse %} + async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client: + async with client.stream( + "{{ op.method }}", + httpx.URL(path), + headers=headers, + params=query_params, +{% if op.body_param %} +{% if op.request_body and op.request_body.encoding == "json" %} + json={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "multipart" %} + files={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "form" %} + data={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding in ["binary", "text"] %} + content={{ op.request_body.expression }}, +{% else %} + json={{ op.body_param }}, +{% endif %} +{% endif %} + ) as response: + response.raise_for_status() + + async for line in response.aiter_lines(): + if not line: + continue + if line.startswith("data:"): + payload = line[len("data:"):].strip() + if not payload: + continue + if payload == "[DONE]": + break + try: +{% if op.sse_data_handler and op.sse_data_handler.type %} + obj = json.loads(payload) + yield TypeAdapter({% if op.sse_data_handler.list_type %}list[{{ op.sse_data_handler.list_type }}]{% else %}{{ op.sse_data_handler.type.converted_type }}{% endif %}).validate_python(obj) +{% else %} + obj = json.loads(payload) + if isinstance(obj, dict): + yield obj + else: + yield payload +{% endif %} + except Exception: + yield payload + else: + # Non-data lines: yield as raw text for debugging/visibility + yield line +{% else %} + async with httpx.AsyncClient(base_url=base_url, verify=self.verify) as client: + response = await client.request( + "{{ op.method }}", + httpx.URL(path), + headers=headers, + params=query_params, +{% if op.body_param %} +{% if op.request_body and op.request_body.encoding == "json" %} + json={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "multipart" %} + files={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "form" %} + data={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding in ["binary", "text"] %} + content={{ op.request_body.expression }}, +{% else %} + json={{ op.body_param }}, +{% endif %} +{% endif %} + ) + + response.raise_for_status() + +{% set empty_variants = op.return_type.variants | selectattr("body_kind", "equalto", "empty") | list %} +{% set non_empty_variants = op.return_type.variants | rejectattr("body_kind", "equalto", "empty") | list %} +{% if empty_variants|length > 0 and non_empty_variants|length > 0 %} + if not response.content: + return None + +{% endif %} +{% if op.return_type.unambiguous_content_handlers|length == 1 %} +{% set handler = op.return_type.unambiguous_content_handlers[0] %} +{% if handler.body_kind == "binary" %} + return response.content +{% elif handler.body_kind == "text" %} + return response.text +{% elif handler.type is not none and handler.type.converted_type is not none %} +{% if handler.list_type is none %} + return TypeAdapter({{ handler.type.converted_type }}).validate_python(response.json()) +{% else %} + return TypeAdapter(list[{{ handler.list_type }}]).validate_python(response.json()) +{% endif %} +{% else %} + return response.json() +{% endif %} +{% else %} +{% set json_variants = non_empty_variants | selectattr("body_kind", "equalto", "json") | list %} +{% set binary_variants = non_empty_variants | selectattr("body_kind", "equalto", "binary") | list %} +{% set text_variants = non_empty_variants | selectattr("body_kind", "equalto", "text") | list %} +{% if non_empty_variants|length == 0 %} + return None +{% elif json_variants|length == non_empty_variants|length %} + return TypeAdapter({{ op.return_type.return_type_hint | replace(" | None", "") | replace("None | ", "") }}).validate_python(response.json()) +{% elif binary_variants|length == non_empty_variants|length %} + return response.content +{% elif text_variants|length == non_empty_variants|length %} + return response.text +{% else %} +{% for variant in op.return_type.variants %} +{% if variant.body_kind != "empty" %} + if response.status_code == {{ variant.status_code }}: +{% if variant.body_kind == "binary" %} + return response.content +{% elif variant.body_kind == "text" %} + return response.text +{% elif variant.type is not none and variant.type.converted_type is not none %} +{% if variant.list_type is none %} + return TypeAdapter({{ variant.type.converted_type }}).validate_python(response.json()) +{% else %} + return TypeAdapter(list[{{ variant.list_type }}]).validate_python(response.json()) +{% endif %} +{% else %} + return response.json() +{% endif %} + +{% endif %} +{% endfor %} + return TypeAdapter({{ op.return_type.return_type_hint | replace(" | None", "") | replace("None | ", "") }}).validate_python(response.json()) +{% endif %} +{% endif %} +{% endif %} + +{% endfor %} diff --git a/src/pydantic_openapi_generator/language_converters/python/templates/discriminator_enum.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/discriminator_enum.jinja2 new file mode 100644 index 0000000..5f0959c --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/templates/discriminator_enum.jinja2 @@ -0,0 +1,7 @@ +from enum import Enum + + +class {{ enum_name }}(str, Enum): +{% for member_name, member_value in members %} + {{ member_name }} = "{{ member_value }}" +{% endfor %} diff --git a/src/openapi_python_generator/language_converters/python/templates/enum.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/enum.jinja2 similarity index 100% rename from src/openapi_python_generator/language_converters/python/templates/enum.jinja2 rename to src/pydantic_openapi_generator/language_converters/python/templates/enum.jinja2 diff --git a/src/pydantic_openapi_generator/language_converters/python/templates/http_exception.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/http_exception.jinja2 new file mode 100644 index 0000000..f2f998a --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/templates/http_exception.jinja2 @@ -0,0 +1,8 @@ +class HTTPException(Exception): + def __init__(self, status_code: int, message: str): + self.status_code = status_code + self.message = message + super().__init__(f"{status_code} {message}") + + def __str__(self) -> str: + return f"{self.status_code} {self.message}" diff --git a/src/openapi_python_generator/language_converters/python/templates/models.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/models.jinja2 similarity index 82% rename from src/openapi_python_generator/language_converters/python/templates/models.jinja2 rename to src/pydantic_openapi_generator/language_converters/python/templates/models.jinja2 index 3ff37e4..478dd48 100644 --- a/src/openapi_python_generator/language_converters/python/templates/models.jinja2 +++ b/src/pydantic_openapi_generator/language_converters/python/templates/models.jinja2 @@ -20,5 +20,5 @@ class {{ schema_name }}(BaseModel): """ {% for property in properties %} - {{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(alias="{{ property.name }}" {% if not property.required %}, default = {{ property.default }} {% endif %}) + {{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(alias="{{ property.name }}"{% if property.default is not none %}, default = {{ property.default }}{% endif %}) {% endfor %} diff --git a/src/openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/models_pydantic_2.jinja2 similarity index 90% rename from src/openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 rename to src/pydantic_openapi_generator/language_converters/python/templates/models_pydantic_2.jinja2 index 99bdfcd..a13896d 100644 --- a/src/openapi_python_generator/language_converters/python/templates/models_pydantic_2.jinja2 +++ b/src/pydantic_openapi_generator/language_converters/python/templates/models_pydantic_2.jinja2 @@ -24,5 +24,5 @@ class {{ schema_name }}(BaseModel): } {% for property in properties %} - {{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(validation_alias="{{ property.name }}" {% if not property.required %}, default = {{ property.default }} {% endif %}) + {{ property.name | normalize_symbol }} : {{ property.type.converted_type | safe }} = Field(validation_alias="{{ property.name }}"{% if property.default is not none %}, default = {{ property.default }}{% endif %}) {% endfor %} diff --git a/src/pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 b/src/pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 new file mode 100644 index 0000000..5652bb0 --- /dev/null +++ b/src/pydantic_openapi_generator/language_converters/python/templates/sync_client_httpx_pydantic_2.jinja2 @@ -0,0 +1,232 @@ +{% if env_token_name is not none %}import os{% endif %} + +from __future__ import annotations + +from collections.abc import Generator +from typing import Any, Dict, Optional, Union +import json + +import httpx +from pydantic import BaseModel, SecretStr, TypeAdapter + +from ..models import * + +{% set _base_url = servers[0].url if servers|length > 0 else "/" %} + + +class SyncClient(BaseModel): + model_config = {"validate_assignment": True} + + base_url: str = "{{ _base_url }}" + verify: Union[bool, str] = True +{% if env_token_name is none %} + access_token: Optional[str] = None +{% endif %} +{% for field in client_fields %} + {{ field.code_name }}: {{ field.field_type_hint }}{% if field.field_default is not none %} = {{ field.field_default }}{% endif %}{{ "\n" }} +{% endfor %} + + def get_access_token(self) -> Optional[str]: +{% if env_token_name is not none %} + try: + return os.environ["{{ env_token_name }}"] + except KeyError: + return None +{% else %} + return self.access_token +{% endif %} + + def set_access_token(self, value: str) -> None: +{% if env_token_name is not none %} + raise Exception( + "This client was generated with an environment variable for the access token. " + "Please set '{{ env_token_name }}'." + ) +{% else %} + self.access_token = value +{% endif %} + +{% for function in client_functions %} + def {{ function.getter_name }}(self) -> {{ function.base_type_hint }}: + raise NotImplementedError + +{% endfor %} +{% for op in operations %} + def {{ op.operation_id }}(self{% if op.params %}, {{ op.params }}{% endif %}) -> {%- if op.is_sse -%} +Generator[str | {% if op.sse_data_handler and op.sse_data_handler.type %}{% if op.sse_data_handler.list_type %}list[{{ op.sse_data_handler.list_type }}]{% else %}{{ op.sse_data_handler.type.converted_type }}{% endif %}{% else %}dict[str, Any]{% endif %}, None, None] +{%- else -%} +{{ op.return_type.return_type_hint or "None" }} +{%- endif -%}: + base_url = self.base_url +{% for param in op.path_params + op.query_params + op.header_params %} +{% if param.source == "ClassVar" %} +{% if param.is_secret %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else (self.{{ param.code_name }}.get_secret_value() if self.{{ param.code_name }} is not None else None) +{% else %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.code_name }} +{% endif %} +{% elif param.source == "Function" %} + {{ param.local_name }} = {{ param.code_name }} if {{ param.code_name }} is not None else self.{{ param.getter_name }}() +{% endif %} +{% endfor %} + path = f"{{ op.path_name }}" + + headers = { +{% if op.request_body and op.request_body.content_type %} + "Content-Type": "{{ op.request_body.content_type }}", +{% elif op.body_param and not op.request_body %} + "Content-Type": "application/json", +{% endif %} +{% if op.is_sse %} + "Accept": "text/event-stream", +{% else %} + "Accept": "{{ op.return_type.accept_content_types | join(', ') or 'application/json' }}", +{% endif %} +{% for hp in op.header_params %} + "{{ hp.wire_name }}": {{ hp.value_expression }}, +{% endfor %} + } + headers = {k: v for (k, v) in headers.items() if v is not None} + + _token = self.get_access_token() + if _token: + headers["Authorization"] = f"Bearer {_token}" + + query_params: Dict[str, Any] = { +{% for qp in op.query_params %} + "{{ qp.wire_name }}": {{ qp.value_expression }}, +{% endfor %} + } + query_params = {k: v for (k, v) in query_params.items() if v is not None} + +{% if op.is_sse %} + with httpx.Client(base_url=base_url, verify=self.verify) as client: + with client.stream( + "{{ op.method }}", + httpx.URL(path), + headers=headers, + params=query_params, +{% if op.body_param %} +{% if op.request_body and op.request_body.encoding == "json" %} + json={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "multipart" %} + files={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "form" %} + data={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding in ["binary", "text"] %} + content={{ op.request_body.expression }}, +{% else %} + json={{ op.body_param }}, +{% endif %} +{% endif %} + ) as response: + response.raise_for_status() + + for line in response.iter_lines(): + if not line: + continue + if line.startswith("data:"): + payload = line[len("data:"):].strip() + if not payload: + continue + if payload == "[DONE]": + break + try: +{% if op.sse_data_handler and op.sse_data_handler.type %} + obj = json.loads(payload) + yield TypeAdapter({% if op.sse_data_handler.list_type %}list[{{ op.sse_data_handler.list_type }}]{% else %}{{ op.sse_data_handler.type.converted_type }}{% endif %}).validate_python(obj) +{% else %} + obj = json.loads(payload) + if isinstance(obj, dict): + yield obj + else: + yield payload +{% endif %} + except Exception: + yield payload + else: + yield line +{% else %} + with httpx.Client(base_url=base_url, verify=self.verify) as client: + response = client.request( + "{{ op.method }}", + httpx.URL(path), + headers=headers, + params=query_params, +{% if op.body_param %} +{% if op.request_body and op.request_body.encoding == "json" %} + json={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "multipart" %} + files={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding == "form" %} + data={{ op.request_body.expression }}, +{% elif op.request_body and op.request_body.encoding in ["binary", "text"] %} + content={{ op.request_body.expression }}, +{% else %} + json={{ op.body_param }}, +{% endif %} +{% endif %} + ) + + response.raise_for_status() + +{% set empty_variants = op.return_type.variants | selectattr("body_kind", "equalto", "empty") | list %} +{% set non_empty_variants = op.return_type.variants | rejectattr("body_kind", "equalto", "empty") | list %} +{% if empty_variants|length > 0 and non_empty_variants|length > 0 %} + if not response.content: + return None + +{% endif %} +{% if op.return_type.unambiguous_content_handlers|length == 1 %} +{% set handler = op.return_type.unambiguous_content_handlers[0] %} +{% if handler.body_kind == "binary" %} + return response.content +{% elif handler.body_kind == "text" %} + return response.text +{% elif handler.type is not none and handler.type.converted_type is not none %} +{% if handler.list_type is none %} + return TypeAdapter({{ handler.type.converted_type }}).validate_python(response.json()) +{% else %} + return TypeAdapter(list[{{ handler.list_type }}]).validate_python(response.json()) +{% endif %} +{% else %} + return response.json() +{% endif %} +{% else %} +{% set json_variants = non_empty_variants | selectattr("body_kind", "equalto", "json") | list %} +{% set binary_variants = non_empty_variants | selectattr("body_kind", "equalto", "binary") | list %} +{% set text_variants = non_empty_variants | selectattr("body_kind", "equalto", "text") | list %} +{% if non_empty_variants|length == 0 %} + return None +{% elif json_variants|length == non_empty_variants|length %} + return TypeAdapter({{ op.return_type.return_type_hint | replace(" | None", "") | replace("None | ", "") }}).validate_python(response.json()) +{% elif binary_variants|length == non_empty_variants|length %} + return response.content +{% elif text_variants|length == non_empty_variants|length %} + return response.text +{% else %} +{% for variant in op.return_type.variants %} +{% if variant.body_kind != "empty" %} + if response.status_code == {{ variant.status_code }}: +{% if variant.body_kind == "binary" %} + return response.content +{% elif variant.body_kind == "text" %} + return response.text +{% elif variant.type is not none and variant.type.converted_type is not none %} +{% if variant.list_type is none %} + return TypeAdapter({{ variant.type.converted_type }}).validate_python(response.json()) +{% else %} + return TypeAdapter(list[{{ variant.list_type }}]).validate_python(response.json()) +{% endif %} +{% else %} + return response.json() +{% endif %} + +{% endif %} +{% endfor %} + return TypeAdapter({{ op.return_type.return_type_hint | replace(" | None", "") | replace("None | ", "") }}).validate_python(response.json()) +{% endif %} +{% endif %} +{% endif %} + +{% endfor %} diff --git a/src/pydantic_openapi_generator/models.py b/src/pydantic_openapi_generator/models.py new file mode 100644 index 0000000..b01ca04 --- /dev/null +++ b/src/pydantic_openapi_generator/models.py @@ -0,0 +1,135 @@ +from typing import List, Literal, Optional, Union + +from openapi_pydantic.v3.v3_0 import ( + Operation as Operation30, +) +from openapi_pydantic.v3.v3_0 import ( + PathItem as PathItem30, +) +from openapi_pydantic.v3.v3_0 import ( + Schema as Schema30, +) +from openapi_pydantic.v3.v3_1 import ( + Operation as Operation31, +) +from openapi_pydantic.v3.v3_1 import ( + PathItem as PathItem31, +) +from openapi_pydantic.v3.v3_1 import ( + Schema as Schema31, +) +from pydantic import BaseModel, Field + +from pydantic_openapi_generator.config.parameter_source import ParameterSource + +# Type unions for compatibility with both OpenAPI 3.0 and 3.1 +Operation = Union[Operation30, Operation31] +PathItem = Union[PathItem30, PathItem31] +Schema = Union[Schema30, Schema31] + + +class LibraryConfig(BaseModel): + name: str + library_name: str + template_name: str + include_async: bool + include_sync: bool + + +class TypeConversion(BaseModel): + original_type: str + converted_type: str + import_types: Optional[List[str]] = None + + +class OpReturnType(BaseModel): + type: Optional[TypeConversion] = None + status_code: int + complex_type: bool = False + list_type: Optional[str] = None + variants: List["ResponseVariant"] = Field(default_factory=list) + accept_content_types: List[str] = Field(default_factory=list) + unambiguous_content_handlers: List["ResponseContentHandler"] = Field(default_factory=list) + return_type_hint: Optional[str] = None + + +class ResponseVariant(BaseModel): + status_code: int + content_type: Optional[str] = None + type: Optional[TypeConversion] = None + complex_type: bool = False + list_type: Optional[str] = None + body_kind: Literal["empty", "json", "text", "binary"] = "empty" + + +class ResponseContentHandler(BaseModel): + content_type: str + type: Optional[TypeConversion] = None + complex_type: bool = False + list_type: Optional[str] = None + body_kind: Literal["json", "text", "binary"] + + +class RequestBodyDefinition(BaseModel): + content_type: Optional[str] = None + encoding: Literal["json", "form", "multipart", "binary", "text"] + expression: str + + +class GeneratedParameter(BaseModel): + wire_name: str + code_name: str + location: Literal["path", "query", "header", "cookie"] + type_hint: str + base_type_hint: str + required: bool + default: Optional[str] = None + source: ParameterSource = ParameterSource.KWARG + is_secret: bool = False + field_type_hint: Optional[str] = None + field_default: Optional[str] = None + getter_name: Optional[str] = None + local_name: Optional[str] = None + value_expression: Optional[str] = None + + +class ServiceOperation(BaseModel): + params: str + operation_id: str + path_params: List[GeneratedParameter] = Field(default_factory=list) + query_params: List[GeneratedParameter] + header_params: List[GeneratedParameter] + return_type: OpReturnType + operation: Operation + pathItem: PathItem + content: str + async_client: Optional[bool] = False + tag: Optional[str] = None + path_name: str + body_param: Optional[str] = None + request_body: Optional[RequestBodyDefinition] = None + method: str + is_sse: bool = False + sse_data_handler: Optional[ResponseContentHandler] = None + use_orjson: bool = False + + +class Property(BaseModel): + name: str + type: TypeConversion + required: bool + default: Optional[str] + import_type: Optional[List[str]] = None + + +class Model(BaseModel): + file_name: str + content: str + openapi_object: Optional[Schema] = None + properties: List[Property] = [] + + +class ConversionResult(BaseModel): + models: List[Model] = Field(default_factory=list) + clients: List[Model] = Field(default_factory=list) + exceptions: List[Model] = Field(default_factory=list) diff --git a/src/openapi_python_generator/parsers/__init__.py b/src/pydantic_openapi_generator/parsers/__init__.py similarity index 100% rename from src/openapi_python_generator/parsers/__init__.py rename to src/pydantic_openapi_generator/parsers/__init__.py diff --git a/src/openapi_python_generator/parsers/openapi_30.py b/src/pydantic_openapi_generator/parsers/openapi_30.py similarity index 77% rename from src/openapi_python_generator/parsers/openapi_30.py rename to src/pydantic_openapi_generator/parsers/openapi_30.py index ee02426..c28df3b 100644 --- a/src/openapi_python_generator/parsers/openapi_30.py +++ b/src/pydantic_openapi_generator/parsers/openapi_30.py @@ -6,11 +6,14 @@ from openapi_pydantic.v3.v3_0 import OpenAPI -from openapi_python_generator.common import HTTPLibrary, PydanticVersion -from openapi_python_generator.language_converters.python.generator import ( +from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion +from pydantic_openapi_generator.config.generator_config import ( + PydanticOpenAPIGeneratorConfig, +) +from pydantic_openapi_generator.language_converters.python.generator import ( generator as base_generator, ) -from openapi_python_generator.models import ConversionResult +from pydantic_openapi_generator.models import ConversionResult def parse_openapi_3_0(spec_data: dict) -> OpenAPI: @@ -36,6 +39,7 @@ def generate_code_3_0( use_orjson: bool = False, custom_template_path: Optional[str] = None, pydantic_version: PydanticVersion = PydanticVersion.V2, + config: Optional[PydanticOpenAPIGeneratorConfig] = None, ) -> ConversionResult: """ Generate Python code from OpenAPI 3.0 specification. @@ -51,7 +55,7 @@ def generate_code_3_0( Returns: ConversionResult: Generated code and metadata """ - from openapi_python_generator.common import library_config_dict + from pydantic_openapi_generator.common import library_config_dict library_config = library_config_dict[library] @@ -62,4 +66,5 @@ def generate_code_3_0( use_orjson=use_orjson, custom_template_path=custom_template_path, pydantic_version=pydantic_version, + config=config, ) diff --git a/src/openapi_python_generator/parsers/openapi_31.py b/src/pydantic_openapi_generator/parsers/openapi_31.py similarity index 77% rename from src/openapi_python_generator/parsers/openapi_31.py rename to src/pydantic_openapi_generator/parsers/openapi_31.py index b0c94e9..5a3406e 100644 --- a/src/openapi_python_generator/parsers/openapi_31.py +++ b/src/pydantic_openapi_generator/parsers/openapi_31.py @@ -6,11 +6,14 @@ from openapi_pydantic.v3.v3_1 import OpenAPI -from openapi_python_generator.common import HTTPLibrary, PydanticVersion -from openapi_python_generator.language_converters.python.generator import ( +from pydantic_openapi_generator.common import HTTPLibrary, PydanticVersion +from pydantic_openapi_generator.config.generator_config import ( + PydanticOpenAPIGeneratorConfig, +) +from pydantic_openapi_generator.language_converters.python.generator import ( generator as base_generator, ) -from openapi_python_generator.models import ConversionResult +from pydantic_openapi_generator.models import ConversionResult def parse_openapi_3_1(spec_data: dict) -> OpenAPI: @@ -36,6 +39,7 @@ def generate_code_3_1( use_orjson: bool = False, custom_template_path: Optional[str] = None, pydantic_version: PydanticVersion = PydanticVersion.V2, + config: Optional[PydanticOpenAPIGeneratorConfig] = None, ) -> ConversionResult: """ Generate Python code from OpenAPI 3.1 specification. @@ -51,7 +55,7 @@ def generate_code_3_1( Returns: ConversionResult: Generated code and metadata """ - from openapi_python_generator.common import library_config_dict + from pydantic_openapi_generator.common import library_config_dict library_config = library_config_dict[library] @@ -62,4 +66,5 @@ def generate_code_3_1( use_orjson=use_orjson, custom_template_path=custom_template_path, pydantic_version=pydantic_version, + config=config, ) diff --git a/src/openapi_python_generator/py.typed b/src/pydantic_openapi_generator/py.typed similarity index 100% rename from src/openapi_python_generator/py.typed rename to src/pydantic_openapi_generator/py.typed diff --git a/src/openapi_python_generator/version_detector.py b/src/pydantic_openapi_generator/version_detector.py similarity index 92% rename from src/openapi_python_generator/version_detector.py rename to src/pydantic_openapi_generator/version_detector.py index be90799..dd69b16 100644 --- a/src/openapi_python_generator/version_detector.py +++ b/src/pydantic_openapi_generator/version_detector.py @@ -48,10 +48,7 @@ def detect_openapi_version(spec_data: Dict[str, Any]) -> OpenAPIVersion: elif openapi_version.startswith("3.1"): return "3.1" else: - raise ValueError( - f"Unsupported OpenAPI version: {openapi_version}. " - f"Only OpenAPI 3.0.x and 3.1.x are supported." - ) + raise ValueError(f"Unsupported OpenAPI version: {openapi_version}. Only OpenAPI 3.0.x and 3.1.x are supported.") def is_openapi_30(spec_data: Dict[str, Any]) -> bool: diff --git a/tests/build_test_api/api.py b/tests/build_test_api/api.py index 4c86648..ba67994 100644 --- a/tests/build_test_api/api.py +++ b/tests/build_test_api/api.py @@ -134,9 +134,9 @@ def custom_openapi(): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( - title="openapi-python-generator test api", + title="pydantic-openapi-generator test api", version="1.0.0", - description="API Schema for openapi-python-generator test api", + description="API Schema for pydantic-openapi-generator test api", routes=app.routes, ) openapi_schema["info"]["x-logo"] = { diff --git a/tests/conftest.py b/tests/conftest.py index a9ade02..129722e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,8 +6,8 @@ import pytest -from openapi_python_generator.version_detector import detect_openapi_version -from openapi_python_generator.parsers import parse_openapi_3_0, parse_openapi_3_1 +from pydantic_openapi_generator.version_detector import detect_openapi_version +from pydantic_openapi_generator.parsers import parse_openapi_3_0, parse_openapi_3_1 test_data_folder = Path(__file__).parent / "test_data" test_data_path = test_data_folder / "test_api.json" diff --git a/tests/regression/test_issue_11.py b/tests/regression/test_issue_11.py deleted file mode 100644 index 33a5a70..0000000 --- a/tests/regression/test_issue_11.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.__main__ import main -from openapi_python_generator.common import HTTPLibrary -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.requests, HTTPLibrary.aiohttp], -) -def test_issue_11(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/7 - """ - result = runner.invoke( - main, - [ - str(test_data_folder / "openapi_gitea_converted.json"), - str(test_result_path), - "--library", - library.value, - ], - ) - assert result.exit_code == 0 diff --git a/tests/regression/test_issue_117.py b/tests/regression/test_issue_117.py deleted file mode 100644 index 1dd0372..0000000 --- a/tests/regression/test_issue_117.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.common import HTTPLibrary, library_config_dict -from openapi_python_generator.generate_data import generate_data -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_117(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/117 - """ - generate_data(test_data_folder / "issue_117.json", test_result_path, library) - - if library_config_dict[library].include_sync: - assert (test_result_path / "services" / "default_service.py").exists() - assert (test_result_path / "services" / "default_service.py").read_text().find( - 'path = f"/bar-boz/{foo_bar}"' - ) != -1 - - if library_config_dict[library].include_async: - assert (test_result_path / "services" / "async_default_service.py").exists() - assert ( - test_result_path / "services" / "async_default_service.py" - ).read_text().find('path = f"/bar-boz/{foo_bar}"') != -1 diff --git a/tests/regression/test_issue_120.py b/tests/regression/test_issue_120.py deleted file mode 100644 index cf75578..0000000 --- a/tests/regression/test_issue_120.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.common import HTTPLibrary, library_config_dict -from openapi_python_generator.generate_data import generate_data -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_120(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/120 - """ - generate_data(test_data_folder / "issue_120.json", test_result_path, library) - - if library_config_dict[library].include_sync: - assert (test_result_path / "services" / "default_service.py").exists() - assert (test_result_path / "services" / "default_service.py").read_text().find( - "language: Optional[str] = None" - ) != -1 - - if library_config_dict[library].include_async: - assert (test_result_path / "services" / "async_default_service.py").exists() - assert ( - test_result_path / "services" / "async_default_service.py" - ).read_text().find("language: Optional[str] = None") != -1 diff --git a/tests/regression/test_issue_17.py b/tests/regression/test_issue_17.py deleted file mode 100644 index 99a43cf..0000000 --- a/tests/regression/test_issue_17.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import generate_data -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_11(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/7 - """ - assert ( - generate_data(test_data_folder / "issue_17.json", test_result_path, library) - is None - ) diff --git a/tests/regression/test_issue_30_87.py b/tests/regression/test_issue_30_87.py deleted file mode 100644 index 7456f2f..0000000 --- a/tests/regression/test_issue_30_87.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest - -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import get_open_api -from openapi_python_generator.parsers import generate_code_3_1 -from tests.conftest import test_data_folder - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_30_87(library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/30 - https://github.com/MarcoMuellner/openapi-python-generator/issues/87 - """ - openapi_obj, version = get_open_api(str(test_data_folder / "issue_30_87.json")) - result = generate_code_3_1(openapi_obj, library) # type: ignore - - expected_model = [m for m in result.models if m.openapi_object.title == "UserType"][ - 0 - ] - assert "ADMIN_USER = 'admin-user'" in expected_model.content diff --git a/tests/regression/test_issue_51.py b/tests/regression/test_issue_51.py deleted file mode 100644 index 5e519ee..0000000 --- a/tests/regression/test_issue_51.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import generate_data -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_11(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/7 - """ - assert ( - generate_data(test_data_folder / "issue_51.json", test_result_path, library) - is None - ) diff --git a/tests/regression/test_issue_55.py b/tests/regression/test_issue_55.py deleted file mode 100644 index 572d610..0000000 --- a/tests/regression/test_issue_55.py +++ /dev/null @@ -1,23 +0,0 @@ -import pytest - -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import get_open_api -from openapi_python_generator.parsers import generate_code_3_1 -from tests.conftest import test_data_folder - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_55(library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/55 - """ - openapi_obj, version = get_open_api(str(test_data_folder / "issue_55.json")) - result = generate_code_3_1(openapi_obj, library) # type: ignore - - expected_model = [m for m in result.models if m.openapi_object.title == "UserType"][ - 0 - ] - assert "ADMIN_USER = 'admin user'" in expected_model.content diff --git a/tests/regression/test_issue_7.py b/tests/regression/test_issue_7.py deleted file mode 100644 index 1f6d1bb..0000000 --- a/tests/regression/test_issue_7.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.__main__ import main -from openapi_python_generator.common import HTTPLibrary -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.requests, HTTPLibrary.aiohttp], -) -def test_issue_7(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/7 - """ - result = runner.invoke( - main, - [ - str(test_data_folder / "openapi_gitea_converted.json"), - str(test_result_path), - "--library", - library.value, - ], - ) - assert result.exit_code == 0 - pass diff --git a/tests/regression/test_issue_71.py b/tests/regression/test_issue_71.py deleted file mode 100644 index c257b28..0000000 --- a/tests/regression/test_issue_71.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import generate_data -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.aiohttp, HTTPLibrary.requests], -) -def test_issue_71(runner: CliRunner, model_data_with_cleanup, library) -> None: - """ - https://github.com/MarcoMuellner/openapi-python-generator/issues/7 - """ - assert ( - generate_data(test_data_folder / "issue_71.json", test_result_path, library) - is None - ) diff --git a/tests/regression/test_issue_illegal_py_symbols.py b/tests/regression/test_issue_illegal_py_symbols.py deleted file mode 100644 index 4d397c7..0000000 --- a/tests/regression/test_issue_illegal_py_symbols.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest -from click.testing import CliRunner - -from openapi_python_generator.__main__ import main -from openapi_python_generator.common import HTTPLibrary -from tests.conftest import test_data_folder -from tests.conftest import test_result_path - - -@pytest.fixture -def runner() -> CliRunner: - """Fixture for invoking command-line interfaces.""" - return CliRunner() - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.requests, HTTPLibrary.aiohttp], -) -def test_issue_keyword_parameter_name( - runner: CliRunner, model_data_with_cleanup, library -) -> None: - result = runner.invoke( - main, - [ - str(test_data_folder / "issue_keyword_parameter_name.json"), - str(test_result_path), - "--library", - library.value, - ], - ) - assert result.exit_code == 0 - - -@pytest.mark.parametrize( - "library", - [HTTPLibrary.httpx, HTTPLibrary.requests, HTTPLibrary.aiohttp], -) -def test_issue_illegal_character_in_operation_id( - runner: CliRunner, model_data_with_cleanup, library -) -> None: - result = runner.invoke( - main, - [ - str(test_data_folder / "issue_illegal_character_in_operation_id.json"), - str(test_result_path), - "--library", - library.value, - ], - ) - assert result.exit_code == 0 diff --git a/tests/test_api_config.py b/tests/test_api_config.py deleted file mode 100644 index f0cd63f..0000000 --- a/tests/test_api_config.py +++ /dev/null @@ -1,10 +0,0 @@ -from openapi_pydantic.v3 import OpenAPI - -from openapi_python_generator.language_converters.python.api_config_generator import ( - generate_api_config, -) - - -def test_generate_api_config(model_data: OpenAPI): - api_config = generate_api_config(model_data) - assert api_config.file_name == "api_config" diff --git a/tests/test_client_generator_contracts.py b/tests/test_client_generator_contracts.py new file mode 100644 index 0000000..0e20fa3 --- /dev/null +++ b/tests/test_client_generator_contracts.py @@ -0,0 +1,513 @@ +import asyncio +import importlib +import json +import shutil +import sys +import tempfile +from pathlib import Path +from uuid import uuid4 + +import httpx +import pytest +from pydantic import ValidationError + +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.generate_data import generate_data + +CONTRACT_SPEC = { + "openapi": "3.0.3", + "info": {"title": "Contract Test API", "version": "1.0.0"}, + "servers": [{"url": "http://testserver"}], + "paths": { + "/things": { + "get": { + "operationId": "getThing", + "parameters": [ + { + "name": "X-Test-Header", + "in": "header", + "required": True, + "schema": {"type": "string"}, + }, + { + "name": "X-Optional-Header", + "in": "header", + "required": False, + "schema": {"type": "string", "default": "AU"}, + }, + { + "name": "X-Function-Header", + "in": "header", + "required": False, + "schema": {"type": "string"}, + }, + { + "name": "brand", + "in": "query", + "required": False, + "schema": {"type": "string", "default": "NRMA"}, + }, + ], + "responses": { + "200": { + "description": "Thing", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ThingResponse"} + } + }, + }, + "206": { + "description": "Validation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResponse" + } + } + }, + }, + }, + } + }, + "/upload": { + "post": { + "operationId": "uploadDocument", + "requestBody": { + "required": True, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": {"type": "string", "format": "binary"}, + "requestMetadata": { + "type": "string", + "format": "binary", + }, + }, + } + } + }, + }, + "responses": { + "200": { + "description": "Uploaded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadResponse" + } + } + }, + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadResponse" + } + } + }, + }, + }, + } + }, + "/download": { + "get": { + "operationId": "downloadDocument", + "responses": { + "200": { + "description": "PDF", + "content": { + "application/pdf": { + "schema": {"type": "string", "format": "binary"} + } + }, + } + }, + } + }, + "/empty": { + "delete": { + "operationId": "deleteThing", + "responses": {"204": {"description": "Deleted"}}, + } + }, + "/events": { + "get": { + "operationId": "getEvents", + "responses": { + "200": { + "description": "Successful event stream connection.", + "content": { + "text/event-stream": { + "schema": { + "type": "object", + "required": ["data"], + "properties": { + "event": {"type": "string"}, + "data": { + "$ref": "#/components/schemas/EventPayload" + }, + "id": {"type": "string"}, + }, + } + } + }, + } + }, + } + }, + }, + "components": { + "schemas": { + "ThingResponse": { + "type": "object", + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "ValidationResponse": { + "type": "object", + "required": ["message"], + "properties": {"message": {"type": "string"}}, + }, + "UploadResponse": { + "type": "object", + "required": ["documentId"], + "properties": {"documentId": {"type": "string"}}, + }, + "EventPayload": { + "type": "object", + "properties": { + "message": {"type": "string"}, + "timestamp": {"type": "string", "format": "date-time"}, + "value": {"type": "number"}, + }, + }, + } + }, +} + + +@pytest.fixture +def generated_contract_package(): + yield from _generated_package() + + +def _generated_package(config_content: str | None = None): + package_name = f"contract_result_{uuid4().hex}" + temp_dir = Path(tempfile.gettempdir()) / package_name + spec_path = temp_dir.parent / f"{package_name}.json" + config_path = temp_dir.parent / f"{package_name}.yaml" + temp_dir.mkdir(parents=True, exist_ok=True) + spec_path.write_text(json.dumps(CONTRACT_SPEC)) + if config_content is not None: + config_path.write_text(config_content) + sys.path.insert(0, str(temp_dir.parent)) + + try: + generate_data( + spec_path, + temp_dir, + HTTPLibrary.httpx, + config_path=config_path if config_content is not None else None, + ) + yield package_name + finally: + if str(temp_dir.parent) in sys.path: + sys.path.remove(str(temp_dir.parent)) + shutil.rmtree(temp_dir, ignore_errors=True) + spec_path.unlink(missing_ok=True) + config_path.unlink(missing_ok=True) + for module_name in list(sys.modules): + if module_name == package_name or module_name.startswith( + f"{package_name}." + ): + sys.modules.pop(module_name, None) + + +@pytest.mark.respx(assert_all_called=False, assert_all_mocked=True) +@pytest.mark.parametrize("async_client", [False, True]) +def test_generated_clients_honor_openapi_request_response_contracts( + generated_contract_package, + respx_mock, + async_client, +): + requests: list[httpx.Request] = [] + + def thing_handler(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response(206, json={"message": "partial"}) + + def upload_handler(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response(202, json={"documentId": "doc-123"}) + + def download_handler(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response( + 200, + content=b"%PDF-1.4", + headers={"content-type": "application/pdf"}, + ) + + respx_mock.get("http://testserver/things").mock(side_effect=thing_handler) + respx_mock.post("http://testserver/upload").mock(side_effect=upload_handler) + respx_mock.get("http://testserver/download").mock(side_effect=download_handler) + respx_mock.delete("http://testserver/empty").mock(return_value=httpx.Response(204)) + + models = importlib.import_module(f"{generated_contract_package}.models") + + if async_client: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.async_client" + ) + client = client_module.AsyncClient() + thing, upload, download, empty = asyncio.run(_run_async_contract(client)) + else: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.sync_client" + ) + client = client_module.SyncClient() + thing = client.getThing(X_Test_Header="required") + upload = client.uploadDocument( + data={"file": ("doc.txt", b"hello", "text/plain")} + ) + download = client.downloadDocument() + empty = client.deleteThing() + + assert isinstance(thing, models.ValidationResponse) + assert upload.documentId == "doc-123" + assert download == b"%PDF-1.4" + assert empty is None + + thing_request = requests[0] + assert thing_request.headers["X-Test-Header"] == "required" + assert thing_request.headers["X-Optional-Header"] == "AU" + assert thing_request.url.params["brand"] == "NRMA" + + upload_request = requests[1] + assert upload_request.headers["content-type"].startswith("multipart/form-data") + assert b'form-data; name="file"; filename="doc.txt"' in upload_request.content + assert b"hello" in upload_request.content + + download_request = requests[2] + assert download_request.headers["accept"] == "application/pdf" + + +@pytest.mark.respx(assert_all_called=False, assert_all_mocked=True) +@pytest.mark.parametrize("async_client", [False, True]) +def test_generated_clients_validate_undocumented_successes_against_contract( + generated_contract_package, + respx_mock, + async_client, +): + respx_mock.get("http://testserver/things").mock( + return_value=httpx.Response(201, json={"unexpected": True}) + ) + respx_mock.post("http://testserver/upload").mock( + return_value=httpx.Response(201, json={"documentId": "doc-201"}) + ) + + if async_client: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.async_client" + ) + client = client_module.AsyncClient() + upload = asyncio.run(_run_async_undocumented_successes(client)) + with pytest.raises(ValidationError): + asyncio.run(client.getThing(X_Test_Header="required")) + else: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.sync_client" + ) + client = client_module.SyncClient() + upload = client.uploadDocument( + data={"file": ("doc.txt", b"hello", "text/plain")} + ) + with pytest.raises(ValidationError): + client.getThing(X_Test_Header="required") + + assert upload.documentId == "doc-201" + + +CONFIG_CONTENT = """ +parameters: + - name: X-Test-Header + type: ClassVar + code_name: test_header + is_secret: true + - name: X-Optional-Header + type: ClassVar + code_name: optional_header + - name: X-Function-Header + type: Function + code_name: dynamic_header + - name: brand + type: Kwarg + code_name: product_brand +""" + + +@pytest.fixture +def generated_configured_contract_package(): + yield from _generated_package(CONFIG_CONTENT) + + +@pytest.mark.respx(assert_all_called=False, assert_all_mocked=True) +@pytest.mark.parametrize("async_client", [False, True]) +def test_generated_clients_resolve_configured_parameter_sources( + generated_configured_contract_package, + respx_mock, + async_client, +): + requests: list[httpx.Request] = [] + + def thing_handler(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response(206, json={"message": "partial"}) + + respx_mock.get("http://testserver/things").mock(side_effect=thing_handler) + + if async_client: + client_module = importlib.import_module( + f"{generated_configured_contract_package}.clients.async_client" + ) + + class Client(client_module.AsyncClient): + getter_calls: int = 0 + + async def get_dynamic_header(self) -> str: + self.getter_calls += 1 + return "from-getter" + + client = Client(test_header="from-client", optional_header="from-class") + asyncio.run(_run_async_configured_contract(client)) + assert client.getter_calls == 1 + else: + client_module = importlib.import_module( + f"{generated_configured_contract_package}.clients.sync_client" + ) + + class Client(client_module.SyncClient): + getter_calls: int = 0 + + def get_dynamic_header(self) -> str: + self.getter_calls += 1 + return "from-getter" + + client = Client(test_header="from-client", optional_header="from-class") + client.getThing(product_brand="CGU") + client.getThing( + test_header="from-override", + optional_header="optional-override", + dynamic_header="function-override", + product_brand="AMI", + ) + assert client.getter_calls == 1 + + first_request = requests[0] + assert first_request.headers["X-Test-Header"] == "from-client" + assert first_request.headers["X-Optional-Header"] == "from-class" + assert first_request.headers["X-Function-Header"] == "from-getter" + assert first_request.url.params["brand"] == "CGU" + + second_request = requests[1] + assert second_request.headers["X-Test-Header"] == "from-override" + assert second_request.headers["X-Optional-Header"] == "optional-override" + assert second_request.headers["X-Function-Header"] == "function-override" + assert second_request.url.params["brand"] == "AMI" + + assert "**********" not in first_request.headers["X-Test-Header"] + assert "**********" not in second_request.headers["X-Test-Header"] + + +@pytest.mark.respx(assert_all_called=False, assert_all_mocked=True) +@pytest.mark.parametrize("async_client", [False, True]) +def test_generated_sse_data_payloads_use_declared_schema( + generated_contract_package, + respx_mock, + async_client, +): + respx_mock.get("http://testserver/events").mock( + return_value=httpx.Response( + 200, + content=( + b"event: update\n" + b'data: {"message": "ok", "timestamp": "2026-08-14T00:00:00Z", "value": 42.5}\n' + b"\n" + ), + headers={"content-type": "text/event-stream"}, + ) + ) + models = importlib.import_module(f"{generated_contract_package}.models") + + if async_client: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.async_client" + ) + client = client_module.AsyncClient() + first_data_item = asyncio.run(_first_async_sse_data_item(client)) + else: + client_module = importlib.import_module( + f"{generated_contract_package}.clients.sync_client" + ) + client = client_module.SyncClient() + first_data_item = next( + item for item in client.getEvents() if not isinstance(item, str) + ) + + assert isinstance(first_data_item, models.EventPayload) + assert first_data_item.message == "ok" + assert first_data_item.value == 42.5 + + +async def _run_async_configured_contract(client): + await client.getThing(product_brand="CGU") + await client.getThing( + test_header="from-override", + optional_header="optional-override", + dynamic_header="function-override", + product_brand="AMI", + ) + + +async def _first_async_sse_data_item(client): + async for item in client.getEvents(): + if not isinstance(item, str): + return item + raise AssertionError("No typed SSE data item yielded") + + +async def _run_async_undocumented_successes(client): + upload = await client.uploadDocument( + data={"file": ("doc.txt", b"hello", "text/plain")} + ) + return upload + + +def test_duplicate_configured_code_name_fails_generation(): + config_content = """ +parameters: + - name: X-Test-Header + type: ClassVar + code_name: shared + - name: X-Optional-Header + type: ClassVar + code_name: shared +""" + + with pytest.raises(ValueError, match="code_name 'shared'"): + list(_generated_package(config_content)) + + +async def _run_async_contract(client): + thing = await client.getThing(X_Test_Header="required") + upload = await client.uploadDocument( + data={"file": ("doc.txt", b"hello", "text/plain")} + ) + download = await client.downloadDocument() + empty = await client.deleteThing() + return thing, upload, download, empty diff --git a/tests/test_common_normalize_symbol.py b/tests/test_common_normalize_symbol.py index f9f5743..008e2a0 100644 --- a/tests/test_common_normalize_symbol.py +++ b/tests/test_common_normalize_symbol.py @@ -1,4 +1,4 @@ -from openapi_python_generator.language_converters.python.common import normalize_symbol +from pydantic_openapi_generator.language_converters.python.common import normalize_symbol def test_normalize_symbol_keyword_and_chars(): diff --git a/tests/test_data/test_api.json b/tests/test_data/test_api.json index 85bb7ce..5e172c2 100644 --- a/tests/test_data/test_api.json +++ b/tests/test_data/test_api.json @@ -1,8 +1,8 @@ { "openapi": "3.0.2", "info": { - "title": "openapi-python-generator test api", - "description": "API Schema for openapi-python-generator test api", + "title": "pydantic-openapi-generator test api", + "description": "API Schema for pydantic-openapi-generator test api", "version": "1.0.0", "x-logo": { "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" diff --git a/tests/test_generate_data.py b/tests/test_generate_data.py index 7564d76..888da83 100644 --- a/tests/test_generate_data.py +++ b/tests/test_generate_data.py @@ -1,6 +1,7 @@ -from pathlib import Path +import json import shutil import subprocess +from pathlib import Path import orjson import pytest @@ -8,12 +9,12 @@ from httpx import ConnectError from pydantic import ValidationError -from openapi_python_generator.common import FormatOptions, Formatter, HTTPLibrary -from openapi_python_generator.common import library_config_dict -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.generate_data import get_open_api -from openapi_python_generator.generate_data import write_data -from openapi_python_generator.language_converters.python.generator import generator +from pydantic_openapi_generator.common import FormatOptions, Formatter, HTTPLibrary +from pydantic_openapi_generator.common import library_config_dict +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.generate_data import get_open_api +from pydantic_openapi_generator.generate_data import write_data +from pydantic_openapi_generator.language_converters.python.generator import generator from tests.conftest import test_data_folder from tests.conftest import test_data_path from tests.conftest import test_result_path @@ -53,19 +54,76 @@ def test_get_open_api(model_data): get_open_api(test_data_folder / "file_does_not_exist.json") +def test_get_open_api_deep_merges_json_and_yaml_overlays_in_order(tmp_path): + source_path = tmp_path / "openapi.yaml" + first_overlay_path = tmp_path / "first-overlay.json" + second_overlay_path = tmp_path / "second-overlay.yaml" + source_path.write_text( + """ +openapi: 3.0.1 +info: + title: Overlay test + version: "1" +paths: {} +components: + schemas: + CommunicationDetails: + type: object + required: + - received_date + - sent_date + properties: + received_date: + type: string + format: date-time + sent_date: + type: string + format: date-time +""" + ) + first_overlay_path.write_text( + json.dumps( + { + "components": { + "schemas": { + "CommunicationDetails": {"required": ["received_date"]}, + } + } + } + ) + ) + second_overlay_path.write_text( + """ +components: + schemas: + CommunicationDetails: + required: + - sent_date +""" + ) + + openapi_obj, version = get_open_api(source_path, [first_overlay_path, second_overlay_path]) + + communication_details = openapi_obj.components.schemas["CommunicationDetails"] + assert version == "3.0" + assert communication_details.required == ["sent_date"] + assert set(communication_details.properties) == {"received_date", "sent_date"} + + def test_generate_data(model_data_with_cleanup): generate_data(test_data_path, test_result_path) assert test_result_path.exists() assert test_result_path.is_dir() - assert (test_result_path / "api_config.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").is_file() assert (test_result_path / "models").exists() assert (test_result_path / "models").is_dir() - assert (test_result_path / "services").exists() - assert (test_result_path / "services").is_dir() assert (test_result_path / "models" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").is_file() assert (test_result_path / "models" / "__init__.py").is_file() + assert (test_result_path / "clients").exists() + assert (test_result_path / "clients").is_dir() + assert (test_result_path / "clients" / "__init__.py").exists() + assert (test_result_path / "clients" / "__init__.py").is_file() assert (test_result_path / "__init__.py").exists() assert (test_result_path / "__init__.py").is_file() @@ -76,15 +134,16 @@ def test_write_data(model_data_with_cleanup): assert test_result_path.exists() assert test_result_path.is_dir() - assert (test_result_path / "api_config.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").is_file() assert (test_result_path / "models").exists() assert (test_result_path / "models").is_dir() - assert (test_result_path / "services").exists() - assert (test_result_path / "services").is_dir() assert (test_result_path / "models" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").is_file() assert (test_result_path / "models" / "__init__.py").is_file() + assert (test_result_path / "clients").exists() + assert (test_result_path / "clients").is_dir() + assert (test_result_path / "clients" / "__init__.py").exists() + assert (test_result_path / "clients" / "__init__.py").is_file() assert (test_result_path / "__init__.py").exists() assert (test_result_path / "__init__.py").is_file() @@ -100,15 +159,16 @@ def test_write_data(model_data_with_cleanup): assert test_result_path.exists() assert test_result_path.is_dir() - assert (test_result_path / "api_config.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").is_file() assert (test_result_path / "models").exists() assert (test_result_path / "models").is_dir() - assert (test_result_path / "services").exists() - assert (test_result_path / "services").is_dir() assert (test_result_path / "models" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").is_file() assert (test_result_path / "models" / "__init__.py").is_file() + assert (test_result_path / "clients").exists() + assert (test_result_path / "clients").is_dir() + assert (test_result_path / "clients" / "__init__.py").exists() + assert (test_result_path / "clients" / "__init__.py").is_file() assert (test_result_path / "__init__.py").exists() assert (test_result_path / "__init__.py").is_file() @@ -121,15 +181,16 @@ def test_write_formatted_data(model_data_with_cleanup): assert test_result_path.exists() assert test_result_path.is_dir() - assert (test_result_path / "api_config.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").is_file() assert (test_result_path / "models").exists() assert (test_result_path / "models").is_dir() - assert (test_result_path / "services").exists() - assert (test_result_path / "services").is_dir() assert (test_result_path / "models" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").is_file() assert (test_result_path / "models" / "__init__.py").is_file() + assert (test_result_path / "clients").exists() + assert (test_result_path / "clients").is_dir() + assert (test_result_path / "clients" / "__init__.py").exists() + assert (test_result_path / "clients" / "__init__.py").is_file() assert (test_result_path / "__init__.py").exists() assert (test_result_path / "__init__.py").is_file() @@ -147,15 +208,16 @@ def test_write_formatted_data(model_data_with_cleanup): assert test_result_path.exists() assert test_result_path.is_dir() - assert (test_result_path / "api_config.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").exists() + assert (test_result_path / "exceptions" / "__init__.py").is_file() assert (test_result_path / "models").exists() assert (test_result_path / "models").is_dir() - assert (test_result_path / "services").exists() - assert (test_result_path / "services").is_dir() assert (test_result_path / "models" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").exists() - assert (test_result_path / "services" / "__init__.py").is_file() assert (test_result_path / "models" / "__init__.py").is_file() + assert (test_result_path / "clients").exists() + assert (test_result_path / "clients").is_dir() + assert (test_result_path / "clients" / "__init__.py").exists() + assert (test_result_path / "clients" / "__init__.py").is_file() assert (test_result_path / "__init__.py").exists() assert (test_result_path / "__init__.py").is_file() diff --git a/tests/test_generate_data_negative.py b/tests/test_generate_data_negative.py index 59c8f8e..30d22db 100644 --- a/tests/test_generate_data_negative.py +++ b/tests/test_generate_data_negative.py @@ -4,8 +4,8 @@ import pytest from httpx import ConnectError -from openapi_python_generator.common import Formatter -from openapi_python_generator.generate_data import get_open_api +from pydantic_openapi_generator.common import Formatter +from pydantic_openapi_generator.generate_data import get_open_api def test_get_open_api_file_not_found(tmp_path: Path): @@ -32,7 +32,7 @@ def test_generate_data_invalid_version(tmp_path: Path, monkeypatch): spec_path = tmp_path / "spec.json" spec_path.write_text(json.dumps(spec)) - import openapi_python_generator.generate_data as gd + import pydantic_openapi_generator.generate_data as gd monkeypatch.setattr(gd, "detect_openapi_version", lambda d: "2.5") with pytest.raises(ValueError): diff --git a/tests/test_generated_code.py b/tests/test_generated_code.py index 687f2a3..6580eeb 100644 --- a/tests/test_generated_code.py +++ b/tests/test_generated_code.py @@ -1,197 +1,137 @@ import asyncio +import importlib +import shutil +import sys +import tempfile from datetime import datetime, timezone +from pathlib import Path import httpx import pytest -import responses -from aiohttp import web -from urllib.parse import urlparse -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.generate_data import generate_data +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.generate_data import generate_data from .conftest import test_data_path from .conftest import test_result_path -def test_get_auth_token_without_env(model_data_with_cleanup): +def test_sync_client_access_token(model_data_with_cleanup): generate_data(test_data_path, test_result_path) - _locals = locals() + from .test_result.clients.sync_client import SyncClient - exec( - "from .test_result import *\nassert APIConfig().get_access_token() is None", - globals(), - _locals, - ) + client = SyncClient() + assert client.get_access_token() is None + client.set_access_token("foo_bar") + assert client.get_access_token() == "foo_bar" -def test_set_auth_token(): +def test_async_client_access_token(model_data_with_cleanup): generate_data(test_data_path, test_result_path) - _locals = locals() - program = """from .test_result import APIConfig -api_config = APIConfig() -assert api_config.get_access_token() is None -api_config.set_access_token('foo_bar') -assert api_config.get_access_token() == 'foo_bar' - """ - exec( - program, - globals(), - _locals, - ) + from .test_result.clients.async_client import AsyncClient + + async def check_client(): + client = AsyncClient() + assert await client.get_access_token() is None + await client.set_access_token("foo_bar") + assert await client.get_access_token() == "foo_bar" + + asyncio.run(check_client()) -@pytest.mark.respx(assert_all_called=False, assert_all_mocked=False) @pytest.mark.parametrize( - "library, use_orjson, custom_ip, openapi_version", + "library, use_orjson, openapi_version", [ - # OpenAPI 3.0 tests - (HTTPLibrary.httpx, False, None, "3.0"), - (HTTPLibrary.requests, False, None, "3.0"), - (HTTPLibrary.httpx, True, None, "3.0"), - (HTTPLibrary.requests, True, None, "3.0"), - (HTTPLibrary.httpx, False, "http://localhost:5000", "3.0"), - (HTTPLibrary.requests, False, "http://localhost:5000", "3.0"), - (HTTPLibrary.httpx, True, "http://localhost:5000", "3.0"), - (HTTPLibrary.requests, True, "http://localhost:5000", "3.0"), - # OpenAPI 3.1 tests (same spec for now if 3.1 test file not present) - (HTTPLibrary.httpx, False, None, "3.1"), - (HTTPLibrary.requests, False, None, "3.1"), - (HTTPLibrary.httpx, True, None, "3.1"), - (HTTPLibrary.requests, True, None, "3.1"), - (HTTPLibrary.httpx, False, "http://localhost:5000", "3.1"), - (HTTPLibrary.requests, False, "http://localhost:5000", "3.1"), - (HTTPLibrary.httpx, True, "http://localhost:5000", "3.1"), - (HTTPLibrary.requests, True, "http://localhost:5000", "3.1"), - # aiohttp (async) tests - (HTTPLibrary.aiohttp, True, None, "3.0"), - (HTTPLibrary.aiohttp, False, None, "3.0"), - (HTTPLibrary.aiohttp, True, "http://127.0.0.1:5001", "3.0"), - (HTTPLibrary.aiohttp, False, "http://127.0.0.1:5002", "3.0"), - (HTTPLibrary.aiohttp, True, None, "3.1"), - (HTTPLibrary.aiohttp, False, None, "3.1"), - (HTTPLibrary.aiohttp, True, "http://127.0.0.1:5003", "3.1"), - (HTTPLibrary.aiohttp, False, "http://127.0.0.1:5004", "3.1"), + (HTTPLibrary.httpx, False, "3.0"), + (HTTPLibrary.httpx, True, "3.0"), + (HTTPLibrary.requests, False, "3.0"), + (HTTPLibrary.aiohttp, False, "3.0"), + (HTTPLibrary.httpx, False, "3.1"), + (HTTPLibrary.httpx, True, "3.1"), + (HTTPLibrary.requests, False, "3.1"), + (HTTPLibrary.aiohttp, False, "3.1"), ], ) -def test_generate_code( - model_data_with_cleanup, library, use_orjson, custom_ip, openapi_version, respx_mock -): - # Create unique temp directory for this test combination - import tempfile - import shutil - import sys - import importlib - from pathlib import Path - - # Select appropriate test data file based on OpenAPI version +def test_generate_code_structure(library, use_orjson, openapi_version): + temp_dir, package_name = _generate_temp_package( + library=library, + use_orjson=use_orjson, + openapi_version=openapi_version, + ) + + try: + assert (temp_dir / "__init__.py").exists() + assert (temp_dir / "models").exists() + assert (temp_dir / "clients").exists() + assert (temp_dir / "exceptions").exists() + + clients_dir = temp_dir / "clients" + assert (clients_dir / "__init__.py").exists() + assert (clients_dir / "sync_client.py").exists() + assert (clients_dir / "async_client.py").exists() + + assert importlib.import_module(f"{package_name}.clients.sync_client").SyncClient + assert importlib.import_module(f"{package_name}.clients.async_client").AsyncClient + finally: + shutil.rmtree(temp_dir, ignore_errors=True) + + +@pytest.mark.respx(assert_all_called=False, assert_all_mocked=False) +@pytest.mark.parametrize("async_client", [False, True]) +def test_httpx_client_e2e(async_client, respx_mock): + temp_dir, package_name = _generate_temp_package( + library=HTTPLibrary.httpx, + use_orjson=False, + openapi_version="3.0", + ) + + try: + models_module = importlib.import_module(f"{package_name}.models") + base_url = "http://localhost:5000" + _setup_httpx_mocks(respx_mock, base_url) + + if async_client: + client_module = importlib.import_module(f"{package_name}.clients.async_client") + client = client_module.AsyncClient(base_url=base_url) + asyncio.run(_run_async_client_tests(client, models_module)) + else: + client_module = importlib.import_module(f"{package_name}.clients.sync_client") + client = client_module.SyncClient(base_url=base_url) + _run_sync_client_tests(client, models_module) + finally: + shutil.rmtree(temp_dir, ignore_errors=True) + + +def _generate_temp_package( + *, + library: HTTPLibrary, + use_orjson: bool, + openapi_version: str, +) -> tuple[Path, str]: test_data_folder = Path(__file__).parent / "test_data" spec_31 = test_data_folder / "test_api_31.json" - if openapi_version == "3.1" and spec_31.exists(): - spec_file = spec_31 - else: - spec_file = test_data_folder / "test_api.json" - - # Create unique test directory based on parameters - test_name = ( - f"test_result_{library.value}_{use_orjson}_{custom_ip or 'none'}_{openapi_version}".replace( - ":", "_" - ) - .replace("/", "_") - .replace(".", "_") - ) - temp_dir = Path(tempfile.gettempdir()) / test_name + spec_file = spec_31 if openapi_version == "3.1" and spec_31.exists() else test_data_path + + package_name = f"test_result_{library.value}_{use_orjson}_{openapi_version}".replace(".", "_") + temp_dir = Path(tempfile.gettempdir()) / package_name - # Clean up any existing directory if temp_dir.exists(): shutil.rmtree(temp_dir) - # Generate data to unique directory generate_data(spec_file, temp_dir, library, use_orjson=use_orjson) - - # Add temp directory to sys.path for imports sys.path.insert(0, str(temp_dir.parent)) - # Import generated modules - api_config_module = importlib.import_module(f"{temp_dir.name}.api_config") - if library == HTTPLibrary.aiohttp: - general_service_module = importlib.import_module( - f"{temp_dir.name}.services.async_general_service" - ) - else: - general_service_module = importlib.import_module( - f"{temp_dir.name}.services.general_service" - ) - models_module = importlib.import_module(f"{temp_dir.name}.models") - - # Create API config instance - api_config_instance = api_config_module.APIConfig() - - # Get the base URL from the API config - if custom_ip is not None: - api_config_instance.base_path = custom_ip - base_url = custom_ip - else: - base_url = api_config_instance.base_path - - # Ensure base_url doesn't have trailing slash for consistent URL construction - base_url = base_url.rstrip("/") - - # Set up mocking based on HTTP library - if library == HTTPLibrary.httpx: - # Use respx for httpx - root_route, get_users_route, get_teams_route = _setup_httpx_mocks( - respx_mock, base_url - ) - elif library == HTTPLibrary.requests: - # Use responses for requests library - with responses.RequestsMock() as responses_mock: - routes = _setup_requests_mocks(responses_mock, base_url) - root_route, get_users_route, get_teams_route = routes - _run_service_tests( - general_service_module, - models_module, - api_config_instance, - custom_ip, - root_route, - get_users_route, - get_teams_route, - library, - ) - return # Early return for requests to avoid running tests outside context - elif library == HTTPLibrary.aiohttp: - # Run async aiohttp server and client tests - asyncio.run( - _run_service_tests_aiohttp( - general_service_module, models_module, api_config_instance, custom_ip - ) - ) - return - - # Run tests for httpx (respx context is already active) - _run_service_tests( - general_service_module, - models_module, - api_config_instance, - custom_ip, - root_route, - get_users_route, - get_teams_route, - library, - ) + return temp_dir, package_name def _setup_httpx_mocks(respx_mock, base_url): - """Set up HTTP mocks for httpx using respx""" - root_url = f"{base_url}/" - - root_route = respx_mock.get(root_url).mock( + respx_mock.get(f"{base_url}/").mock( return_value=httpx.Response(200, json={"message": "Hello World"}) ) - get_users_route = respx_mock.get(f"{base_url}/users").mock( + respx_mock.get(f"{base_url}/users").mock( return_value=httpx.Response( 200, json=[ @@ -215,7 +155,7 @@ def _setup_httpx_mocks(respx_mock, base_url): ) ) - get_teams_route = respx_mock.get(f"{base_url}/teams").mock( + respx_mock.get(f"{base_url}/teams").mock( return_value=httpx.Response( 200, json=[ @@ -239,214 +179,28 @@ def _setup_httpx_mocks(respx_mock, base_url): ) ) - return root_route, get_users_route, get_teams_route +def _run_sync_client_tests(client, models_module): + root = client.root__get() + assert isinstance(root, models_module.RootResponse) -def _setup_requests_mocks(responses_mock, base_url): - root_url = f"{base_url}/" + users = client.get_users_users_get() + assert isinstance(users, list) + assert isinstance(users[0], models_module.User) + assert isinstance(users[1], models_module.User) - root_route = responses_mock.add( - responses.GET, root_url, json={"message": "Hello World"}, status=200 - ) + teams = client.get_teams_teams_get() + assert isinstance(teams, list) - get_users_route = responses_mock.add( - responses.GET, - f"{base_url}/users", - json=[ - dict( - id="1", # String ID for compatibility - username="user1", - email="x@y.com", - password="123456", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - ), - dict( - id="2", # String ID for compatibility - username="user2", - email="x@y.com", - password="123456", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - ), - ], - status=200, - ) - get_teams_route = responses_mock.add( - responses.GET, - f"{base_url}/teams", - json=[ - dict( - id="1", # String ID for compatibility - name="team1", - description="team1", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - updated_at=datetime.now(timezone.utc).isoformat(), - ), - dict( - id="2", # String ID for compatibility - name="team2", - description="team2", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - updated_at=datetime.now(timezone.utc).isoformat(), - ), - ], - status=200, - ) +async def _run_async_client_tests(client, models_module): + root = await client.root__get() + assert isinstance(root, models_module.RootResponse) - return root_route, get_users_route, get_teams_route - - -def _run_service_tests( - general_service_module, - models_module, - api_config_instance, - custom_ip, - root_route, - get_users_route, - get_teams_route, - library, -): - """Run the actual service tests""" - passed_api_config = None - - if custom_ip: - passed_api_config = api_config_instance - - # Test root endpoint - resp_result = general_service_module.root__get(passed_api_config) - assert isinstance(resp_result, models_module.RootResponse) - - # Check if route was called (different APIs for respx vs responses) - if library == HTTPLibrary.httpx: - assert root_route.called - else: - assert root_route.call_count > 0 - - # Test get users - resp_result = general_service_module.get_users_users_get(passed_api_config) - assert isinstance(resp_result, list) - assert isinstance(resp_result[0], models_module.User) - assert isinstance(resp_result[1], models_module.User) - - if library == HTTPLibrary.httpx: - assert get_users_route.called - else: - assert get_users_route.call_count > 0 - - # Test get teams - resp_result = general_service_module.get_teams_teams_get(passed_api_config) - assert isinstance(resp_result, list) - - if library == HTTPLibrary.httpx: - assert get_teams_route.called - else: - assert get_teams_route.call_count > 0 - - print("Service generator E2E passed") - - -async def _run_service_tests_aiohttp( - general_service_module, models_module, api_config_instance, custom_ip -): - """Run service tests against a live aiohttp test server.""" - - async def handle_root(request): - return web.json_response({"message": "Hello World"}) - - async def handle_users(request): - return web.json_response( - [ - dict( - id=1, - username="user1", - email="x@y.com", - password="123456", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - ), - dict( - id=2, - username="user2", - email="x@y.com", - password="123456", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - ), - ] - ) - - async def handle_teams(request): - return web.json_response( - [ - dict( - id=1, - name="team1", - description="team1", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - updated_at=datetime.now(timezone.utc).isoformat(), - ), - dict( - id=2, - name="team2", - description="team2", - is_active=True, - created_at=datetime.now(timezone.utc).isoformat(), - updated_at=datetime.now(timezone.utc).isoformat(), - ), - ] - ) - - app = web.Application() - app.router.add_get("/", handle_root) - app.router.add_get("/users", handle_users) - app.router.add_get("/teams", handle_teams) - - runner = web.AppRunner(app) - await runner.setup() - - host = "127.0.0.1" - port = 0 - scheme = "http" - if custom_ip: - parsed = urlparse(custom_ip) - if parsed.hostname: - host = parsed.hostname - if parsed.port: - port = parsed.port - if parsed.scheme: - scheme = parsed.scheme - - site = web.TCPSite(runner, host, port) - await site.start() - - if port == 0: - # Retrieve the assigned ephemeral port - sockets = site._server.sockets # type: ignore[attr-defined] - assert sockets and len(sockets) > 0 - port = sockets[0].getsockname()[1] - - base_url = f"{scheme}://{host}:{port}" - api_config_instance.base_path = base_url - - try: - # Call async generated functions - resp_result = await general_service_module.root__get(api_config_instance) - assert isinstance(resp_result, models_module.RootResponse) + users = await client.get_users_users_get() + assert isinstance(users, list) + assert isinstance(users[0], models_module.User) + assert isinstance(users[1], models_module.User) - resp_users = await general_service_module.get_users_users_get( - api_config_instance - ) - assert isinstance(resp_users, list) - assert isinstance(resp_users[0], models_module.User) - - resp_teams = await general_service_module.get_teams_teams_get( - api_config_instance - ) - assert isinstance(resp_teams, list) - finally: - await runner.cleanup() + teams = await client.get_teams_teams_get() + assert isinstance(teams, list) diff --git a/tests/test_jinja_no_autoescape.py b/tests/test_jinja_no_autoescape.py new file mode 100644 index 0000000..596c51c --- /dev/null +++ b/tests/test_jinja_no_autoescape.py @@ -0,0 +1,10 @@ +def test_jinja_no_autoescape(): + from pydantic_openapi_generator.language_converters.python.jinja_config import ( + create_jinja_env, + ) + + env = create_jinja_env() + template = env.from_string("{{ s }}") + out = template.render(s="'scope' : scope,") + assert "'" not in out + assert out == "'scope' : scope," diff --git a/tests/test_main.py b/tests/test_main.py index edc8f74..ff93512 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,12 +1,16 @@ """Test cases for the __main__ module.""" +import json +from unittest.mock import patch + import pytest from click.testing import CliRunner -from openapi_python_generator.__main__ import main -from openapi_python_generator.common import HTTPLibrary +from pydantic_openapi_generator.__main__ import main +from pydantic_openapi_generator.common import HTTPLibrary from tests.conftest import test_data_path from tests.conftest import test_result_path +from tests.test_client_generator_contracts import CONTRACT_SPEC @pytest.fixture @@ -26,3 +30,55 @@ def test_main_succeeds(runner: CliRunner, model_data_with_cleanup, library) -> N [str(test_data_path), str(test_result_path), "--library", library.value], ) assert result.exit_code == 0 + + +def test_main_accepts_config_path(runner: CliRunner, tmp_path) -> None: + spec_path = tmp_path / "openapi.json" + output_path = tmp_path / "generated" + config_path = tmp_path / "generator.yaml" + spec_path.write_text(json.dumps(CONTRACT_SPEC)) + config_path.write_text(""" +parameters: + - name: X-Test-Header + type: ClassVar + code_name: test_header +""") + + result = runner.invoke( + main, + [ + str(spec_path), + str(output_path), + "--config-path", + str(config_path), + ], + ) + + assert result.exit_code == 0 + assert (output_path / "clients" / "sync_client.py").exists() + + +def test_main_accepts_multiple_overlays_in_order(runner: CliRunner, tmp_path) -> None: + source_path = tmp_path / "openapi.json" + output_path = tmp_path / "generated" + first_overlay_path = tmp_path / "first.yaml" + second_overlay_path = tmp_path / "second.json" + source_path.write_text(json.dumps(CONTRACT_SPEC)) + first_overlay_path.write_text("components: {}") + second_overlay_path.write_text("{}") + + with patch("pydantic_openapi_generator.__main__.generate_data") as generate_data_mock: + result = runner.invoke( + main, + [ + str(source_path), + str(output_path), + "--overlay", + str(first_overlay_path), + "--overlay", + str(second_overlay_path), + ], + ) + + assert result.exit_code == 0 + assert generate_data_mock.call_args.args[-1] == (str(first_overlay_path), str(second_overlay_path)) diff --git a/tests/test_model_docstring.py b/tests/test_model_docstring.py index 61cd707..8fad761 100644 --- a/tests/test_model_docstring.py +++ b/tests/test_model_docstring.py @@ -1,9 +1,9 @@ from openapi_pydantic.v3 import Schema, Components, DataType -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.language_converters.python.model_generator import ( generate_models, ) -from openapi_python_generator.common import PydanticVersion +from pydantic_openapi_generator.common import PydanticVersion def test_model_docstring_title_used_when_present_and_fallback_to_name(): diff --git a/tests/test_model_generator.py b/tests/test_model_generator.py index 007a724..1fc350d 100644 --- a/tests/test_model_generator.py +++ b/tests/test_model_generator.py @@ -1,23 +1,23 @@ import pytest from openapi_pydantic.v3 import Schema, Reference, DataType, OpenAPI -from openapi_python_generator.common import PydanticVersion -from openapi_python_generator.language_converters.python import common -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.common import PydanticVersion +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.model_generator import ( _generate_property_from_reference, ) -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.language_converters.python.model_generator import ( _generate_property_from_schema, ) -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.language_converters.python.model_generator import ( generate_models, ) -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.language_converters.python.model_generator import ( type_converter, ) -from openapi_python_generator.models import Model -from openapi_python_generator.models import Property -from openapi_python_generator.models import TypeConversion +from pydantic_openapi_generator.models import Model +from pydantic_openapi_generator.models import Property +from pydantic_openapi_generator.models import TypeConversion @pytest.mark.parametrize( diff --git a/tests/test_model_generator_edges.py b/tests/test_model_generator_edges.py index 07926d2..bc44598 100644 --- a/tests/test_model_generator_edges.py +++ b/tests/test_model_generator_edges.py @@ -1,8 +1,8 @@ import pytest from openapi_pydantic.v3 import Schema, DataType -from openapi_python_generator.language_converters.python import common -from openapi_python_generator.language_converters.python.model_generator import ( +from pydantic_openapi_generator.language_converters.python import common +from pydantic_openapi_generator.language_converters.python.model_generator import ( type_converter, ) diff --git a/tests/test_openapi_30.py b/tests/test_openapi_30.py index 1c1c51d..c120986 100644 --- a/tests/test_openapi_30.py +++ b/tests/test_openapi_30.py @@ -8,9 +8,9 @@ import pytest -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.version_detector import detect_openapi_version -from openapi_python_generator.parsers import parse_openapi_3_0 +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.version_detector import detect_openapi_version +from pydantic_openapi_generator.parsers import parse_openapi_3_0 class TestOpenAPI30: @@ -189,8 +189,8 @@ def test_generate_code_30(self, openapi_30_spec): # Check that files were generated assert (output_dir / "__init__.py").exists() assert (output_dir / "models").exists() - assert (output_dir / "services").exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "clients").exists() + assert (output_dir / "exceptions").exists() # Check model structure assert (output_dir / "models" / "__init__.py").exists() @@ -204,17 +204,17 @@ def test_generate_code_30(self, openapi_30_spec): user_model_files = [f for f in model_files if "User" in f.name] assert len(user_model_files) >= 1 # At least User.py should exist - # Check service structure - services_dir = output_dir / "services" - assert (services_dir / "__init__.py").exists() - service_files = list(services_dir.glob("*_service.py")) - assert len(service_files) >= 1 + # Check client structure + clients_dir = output_dir / "clients" + assert (clients_dir / "__init__.py").exists() + client_files = list(clients_dir.glob("*_client.py")) + assert len(client_files) >= 1 # Check that httpx is used (since we updated to latest) - service_content = "" - for service_file in service_files: - service_content += service_file.read_text() - assert "import httpx" in service_content + client_content = "" + for client_file in client_files: + client_content += client_file.read_text() + assert "import httpx" in client_content def test_parameter_handling_30(self, openapi_30_spec): """Test that path parameters in OpenAPI 3.0 are handled correctly.""" diff --git a/tests/test_openapi_31.py b/tests/test_openapi_31.py index 8cfbb58..038010d 100644 --- a/tests/test_openapi_31.py +++ b/tests/test_openapi_31.py @@ -8,9 +8,9 @@ import pytest -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.version_detector import detect_openapi_version -from openapi_python_generator.parsers import parse_openapi_3_1 +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.version_detector import detect_openapi_version +from pydantic_openapi_generator.parsers import parse_openapi_3_1 class TestOpenAPI31: @@ -241,8 +241,8 @@ def test_generate_code_31(self, openapi_31_spec): # Check that files were generated assert (output_dir / "__init__.py").exists() assert (output_dir / "models").exists() - assert (output_dir / "services").exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "clients").exists() + assert (output_dir / "exceptions").exists() # Check model structure assert (output_dir / "models" / "__init__.py").exists() @@ -256,17 +256,17 @@ def test_generate_code_31(self, openapi_31_spec): product_model_files = [f for f in model_files if "Product" in f.name] assert len(product_model_files) >= 1 # At least Product.py should exist - # Check service structure - services_dir = output_dir / "services" - assert (services_dir / "__init__.py").exists() - service_files = list(services_dir.glob("*_service.py")) - assert len(service_files) >= 1 + # Check client structure + clients_dir = output_dir / "clients" + assert (clients_dir / "__init__.py").exists() + client_files = list(clients_dir.glob("*_client.py")) + assert len(client_files) >= 1 # Check that httpx is used (since we updated to latest) - service_content = "" - for service_file in service_files: - service_content += service_file.read_text() - assert "import httpx" in service_content + client_content = "" + for client_file in client_files: + client_content += client_file.read_text() + assert "import httpx" in client_content def test_uuid_parameter_31(self, openapi_31_spec): """Test that UUID parameters in OpenAPI 3.1 are handled correctly.""" diff --git a/tests/test_openapi_31_completeness.py b/tests/test_openapi_31_completeness.py index 4f2e7fc..e045e87 100644 --- a/tests/test_openapi_31_completeness.py +++ b/tests/test_openapi_31_completeness.py @@ -9,9 +9,9 @@ import pytest -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.parsers import parse_openapi_3_1 +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.parsers import parse_openapi_3_1 class TestOpenAPI31Completeness: @@ -356,25 +356,19 @@ def test_comprehensive_31_with_different_libraries( # Verify basic structure assert (temp_path / "__init__.py").exists() assert (temp_path / "models").exists() - assert (temp_path / "services").exists() - assert (temp_path / "api_config.py").exists() - - # Verify library-specific imports in services - services_dir = temp_path / "services" - service_files = list(services_dir.glob("*_service.py")) - assert len(service_files) >= 1 - - service_content = "" - for service_file in service_files: - service_content += service_file.read_text() - - # Check library-specific imports - if library == HTTPLibrary.httpx: - assert "import httpx" in service_content - elif library == HTTPLibrary.requests: - assert "import requests" in service_content - elif library == HTTPLibrary.aiohttp: - assert "import aiohttp" in service_content + assert (temp_path / "clients").exists() + assert (temp_path / "exceptions").exists() + + # Verify library-specific imports in clients + clients_dir = temp_path / "clients" + client_files = list(clients_dir.glob("*_client.py")) + assert len(client_files) >= 1 + + client_content = "" + for client_file in client_files: + client_content += client_file.read_text() + + assert "import httpx" in client_content def test_detailed_model_generation_31(self, comprehensive_31_spec): """Test detailed model generation for OpenAPI 3.1 (matching 3.0 coverage).""" @@ -538,7 +532,7 @@ def test_serialization_options_31(self, comprehensive_31_spec, use_orjson): # Verify files exist assert (temp_path / "__init__.py").exists() assert (temp_path / "models").exists() - assert (temp_path / "services").exists() + assert (temp_path / "clients").exists() # Check for orjson usage if enabled if use_orjson: diff --git a/tests/test_openapi_31_coverage.py b/tests/test_openapi_31_coverage.py index 59c2daf..f4b4b4a 100644 --- a/tests/test_openapi_31_coverage.py +++ b/tests/test_openapi_31_coverage.py @@ -7,9 +7,9 @@ import pytest -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.parsers import parse_openapi_3_1 +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.parsers import parse_openapi_3_1 class TestOpenAPI31SupportedFeatures: @@ -327,8 +327,8 @@ def test_code_generation_with_31_features(self, supported_openapi_31_spec): # Verify files exist assert (temp_path / "models").exists() - assert (temp_path / "services").exists() - assert (temp_path / "api_config.py").exists() + assert (temp_path / "clients").exists() + assert (temp_path / "exceptions").exists() # Check that the code compiles models_dir = temp_path / "models" @@ -436,7 +436,7 @@ def test_31_vs_30_feature_comparison(self): }, } - from openapi_python_generator.parsers import parse_openapi_3_0 + from pydantic_openapi_generator.parsers import parse_openapi_3_0 parsed_30 = parse_openapi_3_0(spec_30_no_const) diff --git a/tests/test_openapi_31_schema_features.py b/tests/test_openapi_31_schema_features.py index f7adb66..65f74c2 100644 --- a/tests/test_openapi_31_schema_features.py +++ b/tests/test_openapi_31_schema_features.py @@ -10,9 +10,9 @@ import pytest -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.parsers import parse_openapi_3_1 +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.parsers import parse_openapi_3_1 @pytest.mark.xfail( @@ -394,9 +394,9 @@ def test_comprehensive_code_generation(self, comprehensive_openapi_31_spec): ) # Verify files are generated - assert (temp_path / "models.py").exists() - assert (temp_path / "services" / "general_service.py").exists() - assert (temp_path / "api_config.py").exists() + assert (temp_path / "models").exists() + assert (temp_path / "clients").exists() + assert (temp_path / "exceptions").exists() # Verify the generated code compiles models_content = (temp_path / "models.py").read_text() @@ -426,7 +426,7 @@ def test_31_feature_parsing_vs_30(): }, } - from openapi_python_generator.parsers import parse_openapi_3_0 + from pydantic_openapi_generator.parsers import parse_openapi_3_0 try: parsed = parse_openapi_3_0(openapi_30_spec) diff --git a/tests/test_service_generator.py b/tests/test_service_generator.py index bee07cf..4846c6e 100644 --- a/tests/test_service_generator.py +++ b/tests/test_service_generator.py @@ -1,4 +1,9 @@ import pytest +pytest.skip( + "service_generator was removed; services now lives on the client classes.", + allow_module_level=True, +) + from openapi_pydantic.v3 import ( Operation, Reference, @@ -11,9 +16,9 @@ ParameterLocation, ) -from openapi_python_generator.common import HTTPLibrary -from openapi_python_generator.common import library_config_dict -from openapi_python_generator.language_converters.python.service_generator import ( +from pydantic_openapi_generator.common import HTTPLibrary +from pydantic_openapi_generator.common import library_config_dict +from pydantic_openapi_generator.language_converters.python.service_generator import ( generate_body_param, generate_operation_id, generate_params, @@ -21,8 +26,8 @@ generate_return_type, generate_services, ) -from openapi_python_generator.models import OpReturnType -from openapi_python_generator.models import TypeConversion +from pydantic_openapi_generator.models import OpReturnType +from pydantic_openapi_generator.models import TypeConversion default_responses = { "200": Response( diff --git a/tests/test_service_generator_edges.py b/tests/test_service_generator_edges.py index df0d6e6..ae465b5 100644 --- a/tests/test_service_generator_edges.py +++ b/tests/test_service_generator_edges.py @@ -1,6 +1,12 @@ +import pytest +pytest.skip( + "service_generator was removed; services now lives on the client classes.", + allow_module_level=True, +) + from openapi_pydantic.v3 import Response, MediaType, Schema, DataType, Operation -from openapi_python_generator.language_converters.python import service_generator -from openapi_python_generator.models import OpReturnType +from pydantic_openapi_generator.language_converters.python import service_generator +from pydantic_openapi_generator.models import OpReturnType def test_is_schema_type_helper(): @@ -24,5 +30,6 @@ def test_generate_return_type_no_json_content(): ) rt = service_generator.generate_return_type(op) assert isinstance(rt, OpReturnType) - assert rt.type is None + assert rt.type is not None + assert rt.type.converted_type == "str" assert rt.complex_type is False diff --git a/tests/test_swagger_petstore_30.py b/tests/test_swagger_petstore_30.py index 1499e24..0dcecf2 100644 --- a/tests/test_swagger_petstore_30.py +++ b/tests/test_swagger_petstore_30.py @@ -8,10 +8,10 @@ import pytest import yaml -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.version_detector import detect_openapi_version -from openapi_python_generator.parsers import parse_openapi_3_0 -from openapi_python_generator.common import HTTPLibrary +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.version_detector import detect_openapi_version +from pydantic_openapi_generator.parsers import parse_openapi_3_0 +from pydantic_openapi_generator.common import HTTPLibrary class TestSwaggerPetstore30: @@ -128,8 +128,8 @@ def test_generate_code_petstore_30(self, petstore_30_spec_path): # Check that files were generated assert (output_dir / "__init__.py").exists() assert (output_dir / "models").exists() - assert (output_dir / "services").exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "clients").exists() + assert (output_dir / "exceptions").exists() # Check model files models_dir = output_dir / "models" @@ -149,14 +149,14 @@ def test_generate_code_petstore_30(self, petstore_30_spec_path): models_dir / model_file ).exists(), f"Missing model file: {model_file}" - # Check service files - services_dir = output_dir / "services" - assert (services_dir / "__init__.py").exists() + # Check client files + clients_dir = output_dir / "clients" + assert (clients_dir / "__init__.py").exists() - # Should have service files for different tags - service_files = list(services_dir.glob("*.py")) - service_files = [f for f in service_files if f.name != "__init__.py"] - assert len(service_files) > 0, "No service files generated" + # Should have client files for different tags + client_files = list(clients_dir.glob("*.py")) + client_files = [f for f in client_files if f.name != "__init__.py"] + assert len(client_files) > 0, "No client files generated" @pytest.mark.parametrize("library", [HTTPLibrary.httpx, HTTPLibrary.requests]) def test_petstore_30_with_different_libraries(self, petstore_30_spec_path, library): @@ -169,7 +169,7 @@ def test_petstore_30_with_different_libraries(self, petstore_30_spec_path, libra # Basic validation that output was created assert output_dir.exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "exceptions").exists() def test_petstore_30_model_generation(self, petstore_30_spec): """Test that model generation works correctly for Petstore 3.0.""" diff --git a/tests/test_swagger_petstore_31.py b/tests/test_swagger_petstore_31.py index 527dd69..555e246 100644 --- a/tests/test_swagger_petstore_31.py +++ b/tests/test_swagger_petstore_31.py @@ -8,10 +8,10 @@ import pytest import yaml -from openapi_python_generator.generate_data import generate_data -from openapi_python_generator.version_detector import detect_openapi_version -from openapi_python_generator.parsers import parse_openapi_3_1 -from openapi_python_generator.common import HTTPLibrary +from pydantic_openapi_generator.generate_data import generate_data +from pydantic_openapi_generator.version_detector import detect_openapi_version +from pydantic_openapi_generator.parsers import parse_openapi_3_1 +from pydantic_openapi_generator.common import HTTPLibrary class TestSwaggerPetstore31: @@ -148,8 +148,8 @@ def test_generate_code_petstore_31(self, petstore_31_spec_path): # Check that files were generated assert (output_dir / "__init__.py").exists() assert (output_dir / "models").exists() - assert (output_dir / "services").exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "clients").exists() + assert (output_dir / "exceptions").exists() # Check model files models_dir = output_dir / "models" @@ -169,14 +169,14 @@ def test_generate_code_petstore_31(self, petstore_31_spec_path): models_dir / model_file ).exists(), f"Missing model file: {model_file}" - # Check service files - services_dir = output_dir / "services" - assert (services_dir / "__init__.py").exists() + # Check client files + clients_dir = output_dir / "clients" + assert (clients_dir / "__init__.py").exists() - # Should have service files for different tags - service_files = list(services_dir.glob("*.py")) - service_files = [f for f in service_files if f.name != "__init__.py"] - assert len(service_files) > 0, "No service files generated" + # Should have client files for different tags + client_files = list(clients_dir.glob("*.py")) + client_files = [f for f in client_files if f.name != "__init__.py"] + assert len(client_files) > 0, "No client files generated" @pytest.mark.parametrize("library", [HTTPLibrary.httpx, HTTPLibrary.requests]) def test_petstore_31_with_different_libraries(self, petstore_31_spec_path, library): @@ -189,7 +189,7 @@ def test_petstore_31_with_different_libraries(self, petstore_31_spec_path, libra # Basic validation that output was created assert output_dir.exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "exceptions").exists() @pytest.mark.parametrize("use_orjson", [True, False]) def test_petstore_31_with_orjson_options(self, petstore_31_spec_path, use_orjson): @@ -207,7 +207,7 @@ def test_petstore_31_with_orjson_options(self, petstore_31_spec_path, use_orjson # Basic validation that output was created assert output_dir.exists() - assert (output_dir / "api_config.py").exists() + assert (output_dir / "exceptions").exists() def test_petstore_31_uuid_parameters(self, petstore_31_spec): """Test that UUID parameters in Petstore 3.1 are handled correctly.""" @@ -236,8 +236,8 @@ def test_petstore_31_model_generation_basic(self, petstore_31_spec): for schema_name in expected_schemas: assert schema_name in schemas, f"Missing schema: {schema_name}" - def test_petstore_31_service_operations_basic(self, petstore_31_spec): - """Test basic service operations for Petstore 3.1.""" + def test_petstore_31_client_operations_basic(self, petstore_31_spec): + """Test basic client operations for Petstore 3.1.""" openapi_obj = parse_openapi_3_1(petstore_31_spec) assert openapi_obj.paths is not None diff --git a/tests/test_version_detector_edges.py b/tests/test_version_detector_edges.py index 919f249..12f8bd8 100644 --- a/tests/test_version_detector_edges.py +++ b/tests/test_version_detector_edges.py @@ -1,6 +1,6 @@ import pytest -from openapi_python_generator.version_detector import ( +from pydantic_openapi_generator.version_detector import ( detect_openapi_version, is_openapi_30, is_openapi_31,