diff --git a/.github/DISCUSSION_TEMPLATE/questions.yml b/.github/DISCUSSION_TEMPLATE/questions.yml deleted file mode 100644 index 524d5c2d08..0000000000 --- a/.github/DISCUSSION_TEMPLATE/questions.yml +++ /dev/null @@ -1,158 +0,0 @@ -labels: [question] -body: - - type: markdown - attributes: - value: | - Thanks for your interest in SQLModel! 🚀 - - Please follow these instructions, fill every question, and do every step. 🙏 - - I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time. - - I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions. - - All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others. - - If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅). - - By asking questions in a structured way (following this) it will be much easier to help you. - - And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎 - - As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓 - - type: checkboxes - id: checks - attributes: - label: First Check - description: Please confirm and check all the following options. - options: - - label: I added a very descriptive title here. - required: true - - label: I used the GitHub search to find a similar question and didn't find it. - required: true - - label: I searched the SQLModel documentation, with the integrated search. - required: true - - label: I already searched in Google "How to X in SQLModel" and didn't find any information. - required: true - - label: I already read and followed all the tutorial in the docs and didn't find an answer. - required: true - - label: I already checked if it is not related to SQLModel but to [Pydantic](https://github.com/samuelcolvin/pydantic). - required: true - - label: I already checked if it is not related to SQLModel but to [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy). - required: true - - type: checkboxes - id: help - attributes: - label: Commit to Help - description: | - After submitting this, I commit to one of: - - * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there. - * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future. - * Review one Pull Request by downloading the code and following all the review process](https://sqlmodel.tiangolo.com/help/#review-pull-requests). - - options: - - label: I commit to help with one of those options 👆 - required: true - - type: textarea - id: example - attributes: - label: Example Code - description: | - Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. - - If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you. - - placeholder: | - from sqlmodel import Field, Session, SQLModel, create_engine - - - class Hero(SQLModel, table=True): - id: int | None = Field(default=None, primary_key=True) - name: str - secret_name: str - age: int | None = None - - - hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") - - engine = create_engine("sqlite:///database.db") - - - SQLModel.metadata.create_all(engine) - - with Session(engine) as session: - session.add(hero_1) - session.commit() - session.refresh(hero_1) - print(hero_1) - render: python - validations: - required: true - - type: textarea - id: description - attributes: - label: Description - description: | - What is the problem, question, or error? - - Write a short description telling me what you are doing, what you expect to happen, and what is currently happening. - placeholder: | - * Create a Hero model. - * Create a Hero instance. - * Save it to a SQLite database. - * Check the data with DB Browser for SQLite. - * There's only one hero there, but I expected it to include many others recruited by the first one. - validations: - required: true - - type: dropdown - id: os - attributes: - label: Operating System - description: What operating system are you on? - multiple: true - options: - - Linux - - Windows - - macOS - - Other - validations: - required: true - - type: textarea - id: os-details - attributes: - label: Operating System Details - description: You can add more details about your operating system here, in particular if you chose "Other". - - type: input - id: sqlmodel-version - attributes: - label: SQLModel Version - description: | - What SQLModel version are you using? - - You can find the SQLModel version with: - - ```bash - python -c "import sqlmodel; print(sqlmodel.__version__)" - ``` - validations: - required: true - - type: input - id: python-version - attributes: - label: Python Version - description: | - What Python version are you using? - - You can find the Python version with: - - ```bash - python --version - ``` - validations: - required: true - - type: textarea - id: context - attributes: - label: Additional Context - description: Add any additional context information or screenshots you think are useful. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 0ffc101a3f..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [tiangolo] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index b4f46138cd..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Security Contact - about: Please report security vulnerabilities to security@tiangolo.com - - name: Question or Problem - about: Ask a question or ask about a problem in GitHub Discussions. - url: https://github.com/fastapi/sqlmodel/discussions/categories/questions - - name: Feature Request - about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. - url: https://github.com/fastapi/sqlmodel/discussions/categories/questions - - name: Show and tell - about: Show what you built with SQLModel or to be used with SQLModel. - url: https://github.com/fastapi/sqlmodel/discussions/categories/show-and-tell diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml deleted file mode 100644 index 8cc6002633..0000000000 --- a/.github/ISSUE_TEMPLATE/privileged.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Privileged -description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇 -body: - - type: markdown - attributes: - value: | - Thanks for your interest in SQLModel! 🚀 - - If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/fastapi/sqlmodel/discussions/categories/questions) instead. - - type: checkboxes - id: privileged - attributes: - label: Privileged issue - description: Confirm that you are allowed to create an issue here. - options: - - label: I'm @tiangolo or he asked me directly to create an issue here. - required: true - - type: textarea - id: content - attributes: - label: Issue Content - description: Add the content of the issue here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 959ec970fb..2e0061f5e5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,13 +4,30 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "monthly" + cooldown: + default-days: 7 commit-message: prefix: ⬆ + labels: + - "internal" + - "dependencies" + - "github_actions" + groups: + github-actions: + patterns: + - "*" # Python - package-ecosystem: "uv" directory: "/" schedule: - interval: "daily" + interval: "monthly" + cooldown: + default-days: 7 commit-message: prefix: ⬆ + groups: + python-packages: + dependency-type: "development" + patterns: + - "*" diff --git a/.github/latest-changes.yml b/.github/latest-changes.yml new file mode 100644 index 0000000000..dc1d7dc25e --- /dev/null +++ b/.github/latest-changes.yml @@ -0,0 +1,11 @@ +auto-labels: + docs: + - docs/** + - docs_src/** + internal: + - .github/** + - scripts/** + - .gitignore + - .pre-commit-config.yaml + - requirements*.txt + - uv.lock diff --git a/.github/pr-push.yml b/.github/pr-push.yml new file mode 100644 index 0000000000..1591365077 --- /dev/null +++ b/.github/pr-push.yml @@ -0,0 +1,2 @@ +workflows: + - .github/workflows/pre-commit.yml diff --git a/.github/pr-submit.yml b/.github/pr-submit.yml new file mode 100644 index 0000000000..c9aa524192 --- /dev/null +++ b/.github/pr-submit.yml @@ -0,0 +1,3 @@ +workflows: + - .github/workflows/bump-pre-commit-hooks.yml + - .github/workflows/prepare-release.yml diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index dd531666eb..4399519e29 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -4,9 +4,7 @@ on: branches: - main pull_request: - types: - - opened - - synchronize +permissions: {} jobs: changes: @@ -14,13 +12,16 @@ jobs: # Required permissions permissions: pull-requests: read + timeout-minutes: 5 # Set job outputs to values from filter step outputs: docs: ${{ steps.filter.outputs.docs }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # For pull requests it's not necessary to checkout the code but for the main branch it is - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + with: + persist-credentials: false + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter with: filters: | @@ -41,29 +42,29 @@ jobs: - changes if: ${{ needs.changes.outputs.docs == 'true' }} runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Install docs extras run: uv sync --locked --no-dev --group docs - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - key: mkdocs-cards-${{ github.ref }} - path: .cache - name: Build Docs run: uv run ./scripts/docs.py build - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -78,9 +79,10 @@ jobs: needs: - build-docs runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 with: jobs: ${{ toJSON(needs) }} allowed-skips: build-docs diff --git a/.github/workflows/detect-conflicts.yml b/.github/workflows/detect-conflicts.yml index 3ac6f65e2f..ecd288ba19 100644 --- a/.github/workflows/detect-conflicts.yml +++ b/.github/workflows/detect-conflicts.yml @@ -1,18 +1,21 @@ name: "Conflict detector" on: push: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] types: [synchronize] +permissions: {} + jobs: main: permissions: contents: read pull-requests: write runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 + uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0 with: dirtyLabel: "conflicts" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/guard-dependencies.yml b/.github/workflows/guard-dependencies.yml new file mode 100644 index 0000000000..11bbf09b5e --- /dev/null +++ b/.github/workflows/guard-dependencies.yml @@ -0,0 +1,52 @@ +name: Guard Dependencies + +on: + pull_request_target: # zizmor: ignore[dangerous-triggers] -- This workflow only reads context.payload metadata, never checks out PR code + branches: [main] + paths: + - pyproject.toml + - uv.lock + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + check-author: + runs-on: ubuntu-latest + steps: + - name: Check if author is org member or allowed bot + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const pr = context.payload.pull_request; + const author = pr.user.login; + const assoc = pr.author_association; + + const botAllowlist = new Set(['dependabot[bot]']); + const orgAuthorAssociations = new Set(['MEMBER', 'OWNER']); + + const allowed = + botAllowlist.has(author) || + (assoc != null && orgAuthorAssociations.has(assoc)); + + if (!allowed) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `This PR modifies dependency files (\`pyproject.toml\` or \`uv.lock\`), which is restricted to members of the **${context.repo.owner}** organization on GitHub.\n\nIf you need a dependency change, please [open a discussion](https://github.com/${context.repo.owner}/${context.repo.repo}/discussions/new) describing what you need and why.\n\nClosing this PR automatically.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + state: 'closed' + }); + + core.setFailed('Dependency changes are restricted to organization members.'); + } else { + console.log(`Author ${author} (author_association=${assoc}) is allowed to make dependency changes.`); + } diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index 065ee4ceb9..8b5ebc913a 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -9,47 +9,26 @@ on: issues: types: - labeled - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] types: - labeled workflow_dispatch: -permissions: - issues: write - pull-requests: write +permissions: {} jobs: issue-manager: if: github.repository_owner == 'fastapi' runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + timeout-minutes: 5 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: tiangolo/issue-manager@2fb3484ec9279485df8659e8ec73de262431737d # 0.6.0 + - uses: tiangolo/issue-manager@dc846170c36eb62fb434b3d943b36399fe240fb5 # 0.8.1 with: token: ${{ secrets.GITHUB_TOKEN }} - config: > - { - "answered": { - "delay": 864000, - "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." - }, - "waiting": { - "delay": 2628000, - "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.", - "reminder": { - "before": "P3D", - "message": "Heads-up: this will be closed in 3 days unless there's new activity." - } - }, - "invalid": { - "delay": 0, - "message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details." - }, - "maybe-ai": { - "delay": 0, - "message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI." - } - } diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml deleted file mode 100644 index 1325a6813f..0000000000 --- a/.github/workflows/latest-changes.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Latest Changes - -on: - pull_request_target: - branches: - - main - types: - - closed - workflow_dispatch: - inputs: - number: - description: PR number - required: true - debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' - required: false - default: 'false' - -jobs: - latest-changes: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - # To allow latest-changes to commit to the main branch - token: ${{ secrets.SQLMODEL_LATEST_CHANGES }} - # Allow debugging with tmate - - name: Setup tmate session - uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3.23 - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} - with: - limit-access-to-actor: true - - uses: tiangolo/latest-changes@c9d329cb147f0ddf4fb631214e3f838ff17ccbbd # 0.4.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - latest_changes_file: docs/release-notes.md - latest_changes_header: '## Latest Changes' - end_regex: '^## ' - debug_logs: true - label_header_prefix: '### ' diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index e5e9c5740e..4ed26a1c89 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -2,48 +2,50 @@ name: pre-commit on: pull_request: - types: - - opened - - synchronize +permissions: {} env: - # Forks and Dependabot don't have access to secrets - HAS_SECRETS: ${{ secrets.PRE_COMMIT != '' }} + CAN_PUSH: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' }} jobs: pre-commit: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + timeout-minutes: 5 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 name: Checkout PR for own repo - if: env.HAS_SECRETS == 'true' + if: env.CAN_PUSH == 'true' with: # To be able to commit it needs to fetch the head of the branch, not the # merge commit ref: ${{ github.head_ref }} # And it needs the full history to be able to compute diffs fetch-depth: 0 - # A token other than the default GITHUB_TOKEN is needed to be able to trigger CI - token: ${{ secrets.PRE_COMMIT }} + persist-credentials: false # pre-commit lite ci needs the default checkout configs to work - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 name: Checkout PR for fork - if: env.HAS_SECRETS == 'false' + if: env.CAN_PUSH == 'false' with: # To be able to commit it needs the head branch of the PR, the remote one ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" cache-dependency-glob: | pyproject.toml uv.lock @@ -51,13 +53,28 @@ jobs: run: uv sync --locked - name: Run prek - pre-commit id: precommit - run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure + run: uv run prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure continue-on-error: true + - name: Check for changes + id: changes + run: | + if [[ -n "$(git status --porcelain)" ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + - name: Get PR Push token + id: pr-push + if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true' + uses: tiangolo/pr-push@f336b3817f32ea9b8273a8c15f8ecb739ac38167 # 0.0.4 - name: Commit and push changes - if: env.HAS_SECRETS == 'true' + if: env.CAN_PUSH == 'true' && steps.changes.outputs.changed == 'true' + env: + GH_TOKEN: ${{ steps.pr-push.outputs.token }} run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "pr-push[bot]" + git config user.email "pr-push[bot]@users.noreply.github.com" + gh auth setup-git git add -A if git diff --staged --quiet; then echo "No changes to commit" @@ -66,7 +83,7 @@ jobs: git push fi - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 - if: env.HAS_SECRETS == 'false' + if: env.CAN_PUSH == 'false' with: msg: 🎨 Auto format - name: Error out on pre-commit errors @@ -79,12 +96,13 @@ jobs: needs: - pre-commit runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 473c026346..2782945955 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,8 @@ on: release: types: [published] +permissions: {} + jobs: publish: if: ${{ github.repository == 'Dandelion-Science/sqlmodel' }} @@ -13,11 +15,16 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + version: "latest-known" + enable-cache: false - run: uv build - uses: softprops/action-gh-release@v2 with: diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 919054ebe3..4faf9825c3 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -1,24 +1,29 @@ name: Smokeshow on: - workflow_run: + workflow_run: # zizmor: ignore[dangerous-triggers] workflows: [Test] types: [completed] -permissions: - statuses: write +permissions: {} jobs: smokeshow: runs-on: ubuntu-latest + permissions: + statuses: write + timeout-minutes: 5 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" cache-dependency-glob: | pyproject.toml uv.lock @@ -46,4 +51,4 @@ jobs: SMOKESHOW_GITHUB_CONTEXT: coverage SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} + SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} # zizmor: ignore[secrets-outside-env] diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml index 88c24e58b1..4de2afc1ac 100644 --- a/.github/workflows/test-redistribute.yml +++ b/.github/workflows/test-redistribute.yml @@ -5,21 +5,22 @@ on: branches: - main pull_request: - types: - - opened - - synchronize +permissions: {} jobs: test-redistribute: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - name: Install build dependencies @@ -49,8 +50,9 @@ jobs: needs: - test-redistribute runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ac9663789..935955e6e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,6 @@ on: branches: - main pull_request: - types: - - opened - - synchronize workflow_dispatch: inputs: debug_enabled: @@ -18,11 +15,14 @@ on: # cron every week on monday - cron: "0 0 * * 1" +permissions: {} + env: UV_NO_SYNC: true jobs: test: + name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }}, ${{ matrix.uv-resolution }}, ${{ matrix.database || 'sqlite' }}) strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] @@ -30,9 +30,18 @@ jobs: uv-resolution: - highest include: + - os: ubuntu-latest + python-version: "3.14" + database: postgres + database-url: postgresql+psycopg://sqlmodel:sqlmodel@127.0.0.1:15432/sqlmodel_test - os: ubuntu-latest python-version: "3.10" uv-resolution: lowest-direct + database: mysql + database-url: mysql+pymysql://sqlmodel:sqlmodel@127.0.0.1:13306/sqlmodel_test + - os: ubuntu-latest + python-version: "3.11" + uv-resolution: highest - os: macos-latest python-version: "3.11" uv-resolution: highest @@ -42,47 +51,64 @@ jobs: - os: ubuntu-latest python-version: "3.13" uv-resolution: highest + database: mariadb + database-url: mariadb+pymysql://sqlmodel:sqlmodel@127.0.0.1:13307/sqlmodel_test - os: macos-latest python-version: "3.13" uv-resolution: highest fail-fast: false runs-on: ${{ matrix.os }} + timeout-minutes: 5 env: UV_PYTHON: ${{ matrix.python-version }} UV_RESOLUTION: ${{ matrix.uv-resolution }} + DATABASE: ${{ matrix.database || 'sqlite' }} + DATABASE_URL: ${{ matrix.database-url || 'sqlite://' }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock # Allow debugging with tmate - name: Setup tmate session - uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3.23 + uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24 if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - name: Install Dependencies run: uv sync --no-dev --group tests + - name: Start Database + if: matrix.database + run: docker compose up --wait --wait-timeout 120 "$DATABASE" - run: mkdir coverage - name: Test run: uv run bash scripts/test.sh env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.pydantic-version }} - CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.database || 'sqlite' }} + CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.database || 'sqlite' }} - name: Store coverage files uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }} + name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.database || 'sqlite' }} path: coverage include-hidden-files: true + - name: Show Database Logs + if: ${{ failure() && matrix.database }} + run: docker compose logs --no-color "$DATABASE" + - name: Stop Database + if: ${{ always() && matrix.database }} + run: docker compose down --volumes test-sqlalchemy-prerelease: # Run tests against the latest SQLAlchemy pre-release to catch compatibility @@ -93,14 +119,17 @@ jobs: UV_RESOLUTION: highest UV_PRERELEASE: allow steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" enable-cache: true cache-dependency-glob: | pyproject.toml @@ -120,14 +149,18 @@ jobs: needs: - test runs-on: ubuntu-latest + timeout-minutes: 5 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version-file: ".python-version" - name: Setup uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "latest-known" enable-cache: true cache-dependency-glob: | pyproject.toml @@ -156,9 +189,11 @@ jobs: if: always() needs: - coverage-combine + - test runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 with: jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000000..612e6bb725 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,25 @@ +name: Zizmor + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. + timeout-minutes: 5 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9e13ea775..96a5e5448b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/crate-ci/typos + rev: 8a48f81b6c64dcfea44b3633223084c4be58ac5f # frozen: v1.49.0 + hooks: + - id: typos + args: [--force-exclude] + - repo: local hooks: - id: local-ruff-check @@ -30,7 +36,9 @@ repos: - id: local-ty name: ty check - entry: uv run ty check sqlmodel tests/test_select_typing.py + entry: >- + uv run ty check sqlmodel tests/test_dataclass_transform.py tests/test_field_sa_type.py + tests/test_select_typing.py require_serial: true language: unsupported pass_filenames: false @@ -54,3 +62,11 @@ repos: entry: uv run ./scripts/docs.py generate-readme files: ^docs/index\.md|scripts/docs\.py$ pass_filenames: false + + - id: zizmor + name: zizmor + language: python + entry: uv run zizmor . + files: ^\.github\/workflows\/ + require_serial: true + pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 47524515d5..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -Please read the [Development - Contributing](https://sqlmodel.tiangolo.com/contributing/) guidelines in the documentation site. diff --git a/README.md b/README.md index 712167ffd6..f5864de908 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,27 @@ SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.

- + Test - - Publish - - + Coverage - + Package version

--- -**Documentation**: https://sqlmodel.tiangolo.com +**Documentation**: [https://sqlmodel.tiangolo.com](https://sqlmodel.tiangolo.com) -**Source Code**: https://github.com/fastapi/sqlmodel +**Source Code**: [https://github.com/fastapi/sqlmodel](https://github.com/fastapi/sqlmodel) --- SQLModel is a library for interacting with SQL databases from Python code, with Python objects. It is designed to be intuitive, easy to use, highly compatible, and robust. -**SQLModel** is based on Python type annotations, and powered by Pydantic and SQLAlchemy. +**SQLModel** is based on Python type annotations, and powered by [Pydantic](https://pydantic-docs.helpmanual.io/) and [SQLAlchemy](https://sqlalchemy.org/). The key features are: @@ -49,9 +46,9 @@ The key features are: ## SQL Databases in FastAPI - + -**SQLModel** is designed to simplify interacting with SQL databases in FastAPI applications, it was created by the same author. 😁 +**SQLModel** is designed to simplify interacting with SQL databases in [FastAPI](https://fastapi.tiangolo.com) applications, it was created by the same [author](https://tiangolo.com/). 😁 It combines SQLAlchemy and Pydantic and tries to simplify the code you write as much as possible, allowing you to reduce the **code duplication to a minimum**, but while getting the **best developer experience** possible. @@ -59,27 +56,28 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python. +A recent and currently supported [version of Python](https://www.python.org/downloads/). As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. ## Installation -Make sure you create a virtual environment, activate it, and then install SQLModel, for example with: +First, [install `uv`](https://docs.astral.sh/uv/getting-started/installation/), and then add SQLModel to your project:
```console -$ pip install sqlmodel +$ uv add sqlmodel ---> 100% -Successfully installed sqlmodel ```
+If you prefer to use `pip`, install `sqlmodel` inside a virtual environment. See the [installation guide](install.md) for the alternative steps. + ## Example -For an introduction to databases, SQL, and everything else, see the SQLModel documentation. +For an introduction to databases, SQL, and everything else, see the [SQLModel documentation](https://sqlmodel.tiangolo.com/databases/). Here's a quick example. ✨ diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 5b8c622b90..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,29 +0,0 @@ -# Security Policy - -Security is very important for SQLModel and its community. 🔒 - -Learn more about it below. 👇 - -## Versions - -The latest versions of SQLModel are supported. - -You are encouraged to [write tests](https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/) for your application and update your SQLModel version frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. - -## Reporting a Vulnerability - -If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. - -I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you. - -## Public Discussions - -Please restrain from publicly discussing a potential security vulnerability. 🙊 - -It's better to discuss privately and try to find a solution first, to limit the potential impact as much as possible. - ---- - -Thanks for your help! - -The SQLModel community and I thank you for that. 🙇 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000000..d1706fd9b5 --- /dev/null +++ b/compose.yml @@ -0,0 +1,50 @@ +services: + postgres: + image: postgres:18 + environment: + POSTGRES_USER: sqlmodel + POSTGRES_PASSWORD: sqlmodel + POSTGRES_DB: sqlmodel_test + ports: + - "127.0.0.1:15432:5432" + healthcheck: + test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-U", "sqlmodel", "-d", "sqlmodel_test"] + interval: 2s + timeout: 5s + retries: 30 + + mysql: + image: mysql:8.4 + environment: + MYSQL_ROOT_PASSWORD: sqlmodel + MYSQL_USER: sqlmodel + MYSQL_PASSWORD: sqlmodel + MYSQL_DATABASE: sqlmodel_test + ports: + - "127.0.0.1:13306:3306" + healthcheck: + test: ["CMD", "mysql", "--host=127.0.0.1", "--user=sqlmodel", "--password=sqlmodel", "--database=sqlmodel_test", "--execute=SELECT 1"] + interval: 2s + timeout: 5s + retries: 30 + + mariadb: + image: mariadb:11.8 + environment: + MARIADB_ROOT_PASSWORD: sqlmodel + MARIADB_USER: sqlmodel + MARIADB_PASSWORD: sqlmodel + MARIADB_DATABASE: sqlmodel_test + ports: + - "127.0.0.1:13307:3306" + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 2s + timeout: 5s + retries: 30 + + adminer: + image: adminer:5 + profiles: ["debug"] + ports: + - "127.0.0.1:18080:8080" diff --git a/data/members.yml b/data/members.yml deleted file mode 100644 index 7a51cbc9ac..0000000000 --- a/data/members.yml +++ /dev/null @@ -1,3 +0,0 @@ -members: -- login: tiangolo -- login: alejsdev diff --git a/docs/advanced/datetime.md b/docs/advanced/datetime.md new file mode 100644 index 0000000000..a67f9c0fd1 --- /dev/null +++ b/docs/advanced/datetime.md @@ -0,0 +1,180 @@ +# Datetimes and Timezones + +You can use Python's `datetime` type to store dates and times, for example when a record was created. + +A datetime can also contain **timezone information**: + +* An **aware datetime** has timezone information. For example, `datetime.now(UTC)` creates an aware datetime in **UTC**. +* A **naive datetime** has no timezone information. On its own, it doesn't tell us which timezone the time belongs to. + +/// note + +The default UTC datetime behavior described here is available since SQLModel version `0.0.45`. + +In version `0.0.44` and earlier, `datetime` fields used `DateTime(timezone=False)` by default. See [Upgrade Existing Applications](#upgrade-existing-applications) when upgrading. + +/// + +## Models with Datetimes + +Let's say that each event in our application has a `created_at` field. + +We can declare it with `datetime` and use `default_factory` to generate the current time in UTC: + +{* ./docs_src/advanced/datetime/tutorial001_py311.py ln[1:8] hl[1,8] *} + +/// tip + +The [`UTC` constant](https://docs.python.org/3/library/datetime.html#datetime.UTC) is available on Python 3.11 and newer. It is an alias for `timezone.utc`. + +For Python 3.10, expand **Other versions and variants** below the example. That version imports `timezone` and uses `datetime.now(timezone.utc)` instead. + +/// + +Here, `lambda` creates a small **function without a name**. It takes no arguments and returns `datetime.now(UTC)` when called. + +We pass this function to `default_factory` so SQLModel can call it each time we create an `Event` without providing `created_at`. This way, each event gets its own creation time. + +Passing `datetime.now(UTC)` directly would call it immediately, when defining the model class. `default_factory` needs a **function** to call later, not a datetime value. + +We can create an event without passing a value for `created_at`: + +{* ./docs_src/advanced/datetime/tutorial001_py311.py ln[17:19] hl[18] *} + +For the database column, **SQLModel** uses `UTCDateTime`, a custom SQLAlchemy type based on `DateTime(timezone=True)`. It uses native timezone support when the database provides it. + +### Write and Read Datetimes + +Let's save the event to `database.db` and read it back using SQLite: + +{* ./docs_src/advanced/datetime/tutorial001_py311.py ln[11:14,21:26] hl[23:25] *} + +The datetime goes through these steps: + +1. When we create the event, the default factory returns an **aware UTC datetime**. +2. When SQLModel sends the value to the database, `UTCDateTime` converts it to UTC. If it is already in UTC, the time stays the same. +3. When SQLModel reads the value back, `UTCDateTime` returns an **aware UTC datetime**, including when the database stores datetimes without timezone information. + +For example, a value of `14:00+02:00` would come back as `12:00+00:00`. Both represent the same instant. The original timezone name or offset is not preserved. + +This processing happens when executing database statements and reading their results. It doesn't change values when constructing a model or assigning attributes. The original value on an existing instance stays unchanged until it is refreshed or reloaded. + +## Aware and Naive Datetimes + +Pydantic provides [`AwareDatetime` and `NaiveDatetime`](https://docs.pydantic.dev/latest/api/standard_library_types/#datetimes) to validate whether a datetime has timezone information. + +We can use them as field types: + +{* ./docs_src/advanced/datetime/tutorial002_py310.py ln[1:8] hl[1,7:8] *} + +Here, `starts_at` requires timezone information during validation. It uses the same database type as `datetime`: `UTCDateTime`. + +The field `local_time` must have **no timezone information**. It uses `DateTime(timezone=False)`, for values that intentionally represent a local time without a timezone. + +These types also work with `| None` and with `Annotated`, for example `Annotated[datetime, NaiveDatetime]`. + +### Validate the Data + +We can use `Event.model_validate()` to validate the data before creating an event: + +{* ./docs_src/advanced/datetime/tutorial002_py310.py ln[11:17] hl[13:14,16] *} + +The `Z` at the end of `starts_at` means **UTC**. The value for `local_time` has no timezone, so both values pass validation. + +If we pass a naive value for `starts_at`, or an aware value for `local_time`, Pydantic raises a validation error. + +/// note + +For table models, constructing an instance directly with `Event(...)` currently does not enforce these Pydantic constraints. This will change in a future version of SQLModel. For now, use `Event.model_validate(data)` when you need validation. + +Loading an instance from the database does not enforce these Pydantic constraints either. + +/// + +Plain `datetime` still accepts both aware and naive values during **Pydantic validation**. But `UTCDateTime` rejects naive database parameters. It doesn't assume that a naive input means UTC or the computer's local timezone. + +For example, using `datetime.now()` without a timezone would fail when the session flushes, including during `session.commit()`. SQLAlchemy raises a `StatementError` wrapping a `ValueError` that explains how to supply an aware datetime or use `NaiveDatetime`. + +The same rule applies to datetime parameters in updates and queries, such as comparing a field to a datetime. Nullable fields still accept `None`. + +Explicit `Field(sa_type=...)` and `Field(sa_column=...)` declarations continue to take precedence over the inferred database type. They bypass SQLModel's UTC processing unless they explicitly use `UTCDateTime`, which you can import from `sqlmodel`. + +## Database Support + +The underlying storage depends on the [SQLAlchemy dialect and database type](https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DateTime). `UTCDateTime` adds UTC processing around that storage: + +* **PostgreSQL** uses `TIMESTAMP WITH TIME ZONE`. It stores an instant in time and returns it using the session timezone, without retaining the original timezone name or offset. `UTCDateTime` converts returned values to UTC. +* **SQLite** stores values without timezone information when using SQLAlchemy's default datetime format. `UTCDateTime` first converts the input to UTC, then restores UTC on the naive value read from the database. +* **MySQL and MariaDB** use `DATETIME`, which ignores the `timezone` flag. `UTCDateTime` supplies UTC values and restores UTC on naive results. It does not switch the column to MySQL's `TIMESTAMP` type. + +You can read more in the [PostgreSQL datetime documentation](https://www.postgresql.org/docs/current/datatype-datetime.html), [SQLAlchemy SQLite documentation](https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#sqlalchemy.dialects.sqlite.DATETIME), and [SQLAlchemy MySQL documentation](https://docs.sqlalchemy.org/en/20/dialects/mysql.html#sqlalchemy.dialects.mysql.DATETIME). + +### Use UTC Datetimes + +For timestamps that represent an instant in time, it is recommended using **UTC datetimes in your application code**, whichever database you use. Create them with `datetime.now(UTC)`, as in the [example above](#models-with-datetimes). + +Raw SQL, other applications, and defaults generated by the database bypass the custom type's processing of input values. Make sure they also write UTC to columns that store datetimes without timezone information. For example, MySQL's `CURRENT_TIMESTAMP` uses the connection's timezone, so a connection generating these values needs to use UTC. + +## Upgrade Existing Applications + +/// warning + +Changing the default from `DateTime(timezone=False)` to `UTCDateTime` is a **breaking change**. The new default requires aware database parameters and returns aware UTC values when reading. + +Upgrading SQLModel changes your model metadata. It does **not** alter existing database columns or convert existing data. `SQLModel.metadata.create_all()` does not update existing columns either. + +/// + +Review each datetime field and choose whether to keep naive storage or adopt UTC storage. Existing explicit `sa_type` or `sa_column` overrides retain their behavior, including an explicit `DateTime(timezone=True)`. + +If you adopt UTC storage, keep `datetime` or use `AwareDatetime` to require aware input during validation. Follow the [UTC recommendation above](#use-utc-datetimes) for new timestamps. Update comparisons and query parameters to use aware values, and check API responses for the added UTC timezone information. + +Before adopting UTC storage, determine what timezone existing naive values represent. This information cannot be recovered from the column type. Reading an old local time as UTC would change its meaning. Resolve ambiguous or nonexistent times around daylight-saving transitions and mixed or unknown timezones before converting data. + +Back up the database and test schema changes, data conversions, and application behavior against a copy, using the same driver as production. Coordinate the migration with all writers so that old and new applications don't mix timezone conventions. + +### Keep Naive Storage + +Change the field annotation to `NaiveDatetime`: + +{* ./docs_src/advanced/datetime/tutorial005_py310.py ln[1:9] hl[3,9] *} + +This keeps the previous database column type, so no database migration is needed for that field. It also adds the validation constraint described above. + +To retain the previous database type **and** plain `datetime` validation behavior, declare the database type explicitly: + +{* ./docs_src/advanced/datetime/tutorial003_py310.py ln[1:9] hl[3,9] *} + +### Adopt Timezone-Aware Storage on PostgreSQL + +1. Create an Alembic migration with your models imported in `env.py` and `target_metadata=SQLModel.metadata`. Run `alembic revision --autogenerate -m "Use timezone-aware datetimes"`. +2. Review the generated migration. For each affected column, add an explicit conversion using the timezone of the existing values. Autogeneration cannot determine this timezone for you. +3. After testing, apply the migration with `alembic upgrade head` as part of the application deployment. + +For example, if `event.created_at` contains naive **UTC** values, the operations in the generated revision can be: + +{* ./docs_src/advanced/datetime/tutorial004_py310.py hl[9:11,19:21] *} + +This migration uses `sa.DateTime(timezone=True)`, the underlying database type of `UTCDateTime`. + +By default, Alembic renders this type as `sqlmodel.sql.sqltypes.UTCDateTime()`. Add `import sqlmodel.sql.sqltypes` at the top of the migration to use the generated code. + +If the existing values represent another timezone, replace `UTC` in both `upgrade()` and `downgrade()` with that timezone. The downgrade converts instants back to naive values in the original timezone. + +Without an explicit conversion, PostgreSQL interprets the old values using the database session's `TimeZone`, which can change the intended instants. Review server defaults and dependent indexes or constraints as part of the migration, too. + +See [Alembic autogeneration](https://alembic.sqlalchemy.org/en/latest/autogenerate.html) and [`alter_column()`'s `postgresql_using` option](https://alembic.sqlalchemy.org/en/latest/ops.html#alembic.operations.Operations.alter_column). + +### Adopt UTC Storage on MySQL, MariaDB, and SQLite + +These databases keep the same `DATETIME` column definition, so adopting `UTCDateTime` does **not** require a schema change by itself. + +If existing values are already naive **UTC**, no data conversion is needed. + +If they represent another timezone, create a **data migration** that converts them to UTC before the new application reads them. + +Alembic's schema autogeneration cannot detect or generate this data conversion. An empty autogenerated migration does not mean the existing timestamps are ready for the new type. + +### Other Databases + +For other databases, inspect the generated column type and test writes and reads with the driver you use. Follow that database's schema and data migration procedures. Native timezone support and accepted input values vary by dialect and driver. Some require an explicit dialect-specific timestamp type. diff --git a/docs/advanced/decimal.md b/docs/advanced/decimal.md index ce971b201b..80330a59ab 100644 --- a/docs/advanced/decimal.md +++ b/docs/advanced/decimal.md @@ -11,7 +11,7 @@ As an example, if you open Python and sum `1.1` + `2.2` you would expect to see 3.3000000000000003 ``` -This is because of the way numbers are stored in "ones and zeros" (binary). But Python has a module and some types to have strict decimal values. You can read more about it in the official Python docs for Decimal. +This is because of the way numbers are stored in "ones and zeros" (binary). But Python has a module and some types to have strict decimal values. You can read more about it in the official [Python docs for Decimal](https://docs.python.org/3/library/decimal.html). Because databases store data in the same ways as computers (in binary), they would have the same types of issues. And because of that, they also have a special **decimal** type. @@ -19,19 +19,19 @@ In most cases this would probably not be a problem, for example measuring views ## Decimal Types -Pydantic has special support for `Decimal` types. +Pydantic has special support for [`Decimal` types](https://docs.pydantic.dev/latest/api/standard_library_types/#decimaldecimal). When you use `Decimal` you can specify the number of digits and decimal places to support in the `Field()` function. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns. -/// info +/// note -For the database, **SQLModel** will use SQLAlchemy's `DECIMAL` type. +For the database, **SQLModel** will use [SQLAlchemy's `DECIMAL` type](https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DECIMAL). /// ## Decimals in SQLModel -Let's say that each hero in the database will have an amount of money. We could make that field a `Decimal` type using the `condecimal()` function: +Let's say that each hero in the database will have an amount of money. We can define that field as a `Decimal` and configure the maximum number of digits and decimal places using the parameters of `Field()`: {* ./docs_src/advanced/decimal/tutorial001_py310.py ln[1:11] hl[11] *} @@ -82,7 +82,7 @@ Now if you run this, instead of printing the unexpected number `3.30000000000000
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -102,7 +102,7 @@ Total money: 3.300 /// warning -Although Decimal types are supported and used in the Python side, not all databases support it. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports. +Although Decimal types are supported and used on the Python side, not all databases support them. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports. But decimals are supported by most of the other SQL databases. 🎉 diff --git a/docs/advanced/uuid.md b/docs/advanced/uuid.md index abfcfeda2b..c244f4403d 100644 --- a/docs/advanced/uuid.md +++ b/docs/advanced/uuid.md @@ -12,7 +12,7 @@ You might have seen **UUIDs**, for example in URLs. They look something like thi UUIDs can be particularly useful as an alternative to auto-incrementing integers for **primary keys**. -/// info +/// note Official support for UUIDs was added in SQLModel version `0.0.20`. @@ -28,7 +28,7 @@ There are several versions of UUID, some versions include the current time in th ### Distributed UUIDs -You could generate one UUID in one computer, and someone else could generate another UUID in another computer, and it would be almost **impossible** for both UUIDs to be the **same**. +You could generate one UUID on one computer, and someone else could generate another UUID on another computer, and it would be almost **impossible** for both UUIDs to be the **same**. This means that you don't have to wait for the DB to generate the ID for you, you can **generate it in code before sending it to the database**, because you can be quite certain it will be unique. @@ -82,9 +82,9 @@ This means that the UUID will be generated in the Python code, **before sending {* ./docs_src/advanced/uuid/tutorial001_py310.py ln[1:10] hl[1,7] *} -Pydantic has support for `UUID` types. +Pydantic has support for [`UUID` types](https://docs.pydantic.dev/latest/api/standard_library_types/#uuid). -For the database, **SQLModel** internally uses SQLAlchemy's `Uuid` type. +For the database, **SQLModel** internally uses [SQLAlchemy's `Uuid` type](https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.Uuid). ### Create a Record with a UUID @@ -127,7 +127,7 @@ If you run the program, you will see the **UUID** generated in the Python code,
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -174,5 +174,5 @@ Selected hero ID: You can learn more about **UUIDs** in: -* The official Python docs for UUID. -* The Wikipedia for UUID. +* The official [Python docs for UUID](https://docs.python.org/3/library/uuid.html). +* The [Wikipedia for UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier). diff --git a/docs/contributing.md b/docs/contributing.md index 5da90a9f57..cb3181947f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,196 +1,15 @@ # Contributing -First, you might want to see the basic ways to [help SQLModel and get help](help.md){.internal-link target=_blank}. +First, you might want to see the basic ways to [help SQLModel and get help](help.md). ## Developing -If you already cloned the sqlmodel repository and you want to deep dive in the code, here are some guidelines to set up your environment. +To contribute code to the project, please follow the guidelines in [tiangolo.com - Contributing](https://tiangolo.com/open-source/contributing/). -### Install Requirements Using `uv` +## Database tests -Create a virtual environment and install the required packages in one command: - -
- -```console -$ uv sync - ----> 100% -``` - -
- -It will install all the dependencies and your local SQLModel in your local environment. - -### Using your Local SQLModel - -If you create a Python file that imports and uses SQLModel, and run it with the Python from your local environment, it will use your cloned local SQLModel source code. - -And if you update that local SQLModel source code when you run that Python file again, it will use the fresh version of SQLModel you just edited. - -That way, you don't have to "install" your local version to be able to test every change. - -/// note | "Technical Details" - -This only happens when you install using this included `requirements.txt` instead of running `pip install sqlmodel` directly. - -That is because inside the `requirements.txt` file, the local version of SQLModel is marked to be installed in "editable" mode, with the `-e` option. - -/// - -### Format - -There is a script that you can run that will format and clean all your code: - -
+Start local PostgreSQL, MySQL, and MariaDB databases for tests with Docker Compose: ```console -$ bash scripts/format.sh +docker compose up --wait postgres mysql mariadb ``` - -
- -It will also auto-sort all your imports. - -## Tests - -There is a script that you can run locally to test all the code and generate coverage reports in HTML: - -
- -```console -$ bash scripts/test.sh -``` - -
- -This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. - -## Docs - -First, make sure you set up your environment as described above, that will install all the requirements. - -### Docs Live - -During local development, there is a script that builds the site and checks for any changes, live-reloading: - -
- -```console -$ python ./scripts/docs.py live - -[INFO] Serving on http://127.0.0.1:8008 -[INFO] Start watching changes -[INFO] Start detecting changes -``` - -
- -It will serve the documentation on `http://127.0.0.1:8008`. - -That way, you can edit the documentation/source files and see the changes live. - -/// tip - -Alternatively, you can perform the same steps that the script does manually. - -Go into the docs directory at `docs/`: - -```console -$ cd docs/ -``` - -Then run `mkdocs` in that directory: - -```console -$ mkdocs serve --dev-addr 8008 -``` - -/// - -#### Typer CLI (Optional) - -The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. - -But you can also use Typer CLI, and you will get autocompletion in your terminal for the commands after installing completion. - -If you install Typer CLI, you can install completion with: - -
- -```console -$ typer --install-completion - -zsh completion installed in /home/user/.bashrc. -Completion will take effect once you restart the terminal. -``` - -
- -### Docs Structure - -The documentation uses MkDocs. - -And there are extra tools/scripts in place in `./scripts/docs.py`. - -/// tip - -You don't need to see the code in `./scripts/docs.py`, you just use it in the command line. - -/// - -All the documentation is in Markdown format in the directory `./docs`. - -Many of the tutorials have blocks of code. - -In most of the cases, these blocks of code are actual complete applications that can be run as is. - -In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory. - -And those Python files are included/injected in the documentation when generating the site. - -### Docs for Tests - -Most of the tests actually run against the example source files in the documentation. - -This helps to make sure that: - -* The documentation is up-to-date. -* The documentation examples can be run as is. -* Most of the features are covered by the documentation, ensured by test coverage. - -## Automated Code and AI - -You are encouraged to use all the tools you want to do your work and contribute as efficiently as possible, this includes AI (LLM) tools, etc. Nevertheless, contributions should have meaningful human intervention, judgement, context, etc. - -If the **human effort** put in a PR, e.g. writing LLM prompts, is **less** than the **effort we would need to put** to **review it**, please **don't** submit the PR. - -Think of it this way: we can already write LLM prompts or run automated tools ourselves, and that would be faster than reviewing external PRs. - -### Closing Automated and AI PRs - -If we see PRs that seem AI generated or automated in similar ways, we'll flag them and close them. - -The same applies to comments and descriptions, please don't copy paste the content generated by an LLM. - -### Human Effort Denial of Service - -Using automated tools and AI to submit PRs or comments that we have to carefully review and handle would be the equivalent of a Denial-of-service attack on our human effort. - -It would be very little effort from the person submitting the PR (an LLM prompt) that generates a large amount of effort on our side (carefully reviewing code). - -Please don't do that. - -We'll need to block accounts that spam us with repeated automated PRs or comments. - -### Use Tools Wisely - -As Uncle Ben said: - -
-With great power tools comes great responsibility. -
- -Avoid inadvertently doing harm. - -You have amazing tools at hand, use them wisely to help effectively. diff --git a/docs/css/termynal.css b/docs/css/termynal.css index a2564e2860..daa6038a0a 100644 --- a/docs/css/termynal.css +++ b/docs/css/termynal.css @@ -55,10 +55,19 @@ text-align: center; } -a[data-terminal-control] { +button[data-terminal-control] { + cursor: pointer; text-align: right; display: block; + width: 100%; color: #aebbff; + border: 0; + background: transparent; + font: inherit; +} + +button[data-terminal-control]:hover { + color: var(--color-text); } [data-ty] { diff --git a/docs/databases.md b/docs/databases.md index de25ebb4cb..18646e5d9a 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -1,6 +1,6 @@ # Intro to Databases -/// info +/// note Are you a seasoned developer and already know everything about databases? 🤓 @@ -50,7 +50,7 @@ In most of the cases, the objective of your program is to do something with data * It could be just moving **files** from one place to the other. * Or it could be taking data from the user in the **terminal** and showing it differently. -* Or a **web API** that takes some data and process it in some way, etc. +* Or a **web API** that takes some data and processes it in some way, etc. In most cases, the data *comes from outside* the program or *ends outside the program* (for example, shown on the screen, in a file, etc). @@ -108,9 +108,9 @@ Having distributed systems also creates additional challenges, so there's a high We already talked about the different ways to interact with a database and how they handle files, etc. That applies to most or all of the databases. -But there's another way to categorize databases that is very important. As you can imagine, there are many types of databases and many databases in each group. But in general, they can be separated in two big groups: "SQL Databases" and "NoSQL Databases". +But there's another way to categorize databases that is very important. As you can imagine, there are many types of databases and many databases in each group. But in general, they can be separated into two big groups: "SQL Databases" and "NoSQL Databases". -We will get to why the name "SQL" in a bit, but first, let's see what is it all about. +We will get to why the name "SQL" in a bit, but first, let's see what it is all about. ### SQLModel for SQL Databases @@ -137,7 +137,7 @@ If we worked with a single table to store our heroes, it could be like this: idnamesecret_nameageteamheadquarters -1DeadpondDive WilsonnullZ-FactorSister Margaret's Bar +1DeadpondDive WilsonnullZ-ForceSister Margaret's Bar 2Spider-BoyPedro ParqueadornullPreventersSharp Tower @@ -279,7 +279,7 @@ And because of this technical term, these **SQL Databases** are also called **Re After developing these ideas of how to store data in multiple tables they also created a **language** that could be used to interact with them. -The language is called **SQL**, the name comes from for **Structured Query Language**. +The language is called **SQL**, the name comes from **Structured Query Language**. Nevertheless, the language is not only used to *query* for data. It is also used to create records/rows, to update them, to delete them. And to manipulate the database, create tables, etc. @@ -317,9 +317,9 @@ Next, it receives the data and puts it in Python objects that you can continue t I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections. -/// info | Technical Details +/// note | Technical Details -SQLModel is built on top of SQLAlchemy. It is, in fact, just SQLAlchemy and Pydantic mixed together with some sugar on top. +SQLModel is built on top of SQLAlchemy. It is, in fact, just [SQLAlchemy](https://www.sqlalchemy.org/) and [Pydantic](https://pydantic-docs.helpmanual.io/) mixed together with some sugar on top. /// diff --git a/docs/db-to-code.md b/docs/db-to-code.md index fbba54c162..24d09f8e6e 100644 --- a/docs/db-to-code.md +++ b/docs/db-to-code.md @@ -111,7 +111,7 @@ DROP TABLE hero; That is how you tell the database in SQL to delete the entire table `hero`. -Nooooo! We lost all the data in the `hero` table! 💥😱 +[Nooooo!](https://theuselessweb.site/nooooooooooooooo/) We lost all the data in the `hero` table! 💥😱 ### SQL Sanitization @@ -119,7 +119,7 @@ The process of making sure that whatever the external user sends is safe to use It comes by default in **SQLModel** (thanks to SQLAlchemy). And many other similar tools would also provide that functionality among many other features. -Now you are ready for a joke from xkcd: +Now you are ready for [a joke from xkcd](https://xkcd.com/327/): ![Exploits of a Mom](https://imgs.xkcd.com/comics/exploits_of_a_mom.png) @@ -190,7 +190,7 @@ Then your code will continue to execute and calmly tell the user that it couldn' But we never deleted the `hero` table. 🎉 -/// info +/// note Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default. @@ -244,7 +244,7 @@ These types of libraries like **SQLModel** (and of course, SQLAlchemy) that tran **ORM** means **Object-Relational Mapper**. -This is a very common term, but it also comes from quite technical and **academical** concepts 👩‍🎓: +This is a very common term, but it also comes from quite technical and **academic** concepts 👩‍🎓: * **Object**: refers to code with classes and instances, normally called "Object Oriented Programming", that's why the "**Object**" part. @@ -292,11 +292,11 @@ It's actually a simple idea with a very academic and mathematical name. 😅 So, an **ORM** is a library that translates from SQL to code, and from code to SQL. All using classes and objects. -There are many ORMs available apart from **SQLModel**, you can read more about some of them in [Alternatives, Inspiration and Comparisons](alternatives.md){.internal-link target=_blank} +There are many ORMs available apart from **SQLModel**, you can read more about some of them in [Alternatives, Inspiration and Comparisons](alternatives.md) ## SQL Table Names -/// info | Technical Background +/// note | Technical Background This is a bit of boring background for SQL purists. Feel free to skip this section. 😉 diff --git a/docs/environment-variables.md b/docs/environment-variables.md index bd8b2a06d0..d64f41a872 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -1,300 +1,9 @@ # Environment Variables -Before we jump into code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**. +An **environment variable** (also known as an **env var**) is a value that lives outside of your Python code, in the operating system, and can be read by your application and other programs. -/// tip +Applications commonly use environment variables for configuration such as database URLs, credentials, and secret keys. -If you already know what "environment variables" are and how to use them, feel free to skip this. +## Learn More { #learn-more } -/// - -An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well). - -Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc. - -## Create and Use Env Vars - -You can **create** and use environment variables in the **shell (terminal)**, without needing Python: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -// You could create an env var MY_NAME with -$ export MY_NAME="Wade Wilson" - -// Then you could use it with other programs, like -$ echo "Hello $MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -// Create an env var MY_NAME -$ $Env:MY_NAME = "Wade Wilson" - -// Use it with other programs, like -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -## Read env vars in Python - -You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**. - -For example you could have a file `main.py` with: - -```Python hl_lines="3" -import os - -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` - -/// tip - -The second argument to `os.getenv()` is the default value to return. - -If not provided, it's `None` by default, here we provide `"World"` as the default value to use. - -/// - -Then you could call that Python program: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -// Here we don't set the env var yet -$ python main.py - -// As we didn't set the env var, we get the default value - -Hello World from Python - -// But if we create an environment variable first -$ export MY_NAME="Wade Wilson" - -// And then call the program again -$ python main.py - -// Now it can read the environment variable - -Hello Wade Wilson from Python -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -// Here we don't set the env var yet -$ python main.py - -// As we didn't set the env var, we get the default value - -Hello World from Python - -// But if we create an environment variable first -$ $Env:MY_NAME = "Wade Wilson" - -// And then call the program again -$ python main.py - -// Now it can read the environment variable - -Hello Wade Wilson from Python -``` - -
- -//// - -As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**. - -You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration. - -To do that, create it right before the program itself, on the same line: - -
- -```console -// Create an env var MY_NAME in line for this program call -$ MY_NAME="Wade Wilson" python main.py - -// Now it can read the environment variable - -Hello Wade Wilson from Python - -// The env var no longer exists afterwards -$ python main.py - -Hello World from Python -``` - -
- -/// tip - -You can read more about it at The Twelve-Factor App: Config. - -/// - -## Types and Validation - -These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). - -That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code. - -## `PATH` Environment Variable - -There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run. - -The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows. - -For example, the `PATH` environment variable could look like this: - -//// tab | Linux, macOS - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -``` - -This means that the system should look for programs in the directories: - -* `/usr/local/bin` -* `/usr/bin` -* `/bin` -* `/usr/sbin` -* `/sbin` - -//// - -//// tab | Windows - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 -``` - -This means that the system should look for programs in the directories: - -* `C:\Program Files\Python312\Scripts` -* `C:\Program Files\Python312` -* `C:\Windows\System32` - -//// - -When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable. - -For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list. - -If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**. - -### Installing Python and Updating the `PATH` - -When you install Python, you might be asked if you want to update the `PATH` environment variable. - -//// tab | Linux, macOS - -Let's say you install Python and it ends up in a directory `/opt/custompython/bin`. - -If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable. - -It could look like this: - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin -``` - -This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. - -//// - -//// tab | Windows - -Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`. - -If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable. - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin -``` - -This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one. - -//// - -This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. - -So, if you type: - -
- -```console -$ python -``` - -
- -//// tab | Linux, macOS - -The system will **find** the `python` program in `/opt/custompython/bin` and run it. - -It would be roughly equivalent to typing: - -
- -```console -$ /opt/custompython/bin/python -``` - -
- -//// - -//// tab | Windows - -The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it. - -It would be roughly equivalent to typing: - -
- -```console -$ C:\opt\custompython\bin\python -``` - -
- -//// - -This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}. - -## Conclusion - -With this you should have a basic understanding of what **environment variables** are and how to use them in Python. - -You can also read more about them in the Wikipedia for Environment Variable. - -In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them. - -For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md). +Read the [Environment Variables guide](https://tiangolo.com/guides/environment-variables/) for a detailed, cross-platform explanation, including how to create and read environment variables and how the `PATH` environment variable works. diff --git a/docs/features.md b/docs/features.md index 2f2e873105..20f4392878 100644 --- a/docs/features.md +++ b/docs/features.md @@ -2,9 +2,9 @@ ## Designed for **FastAPI** -**SQLModel** was created by the same author of FastAPI. +**SQLModel** was created by the same [author](https://tiangolo.com/) of FastAPI. - + It follows the same design and ideas, and it was created to be the most intuitive way to interact with SQL databases in FastAPI applications. @@ -14,9 +14,9 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used It's all based on standard modern **Python** type annotations. No new syntax to learn. Just standard modern Python. -If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: Python types intro. +If you need a 2 minute refresher on how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: [Python types intro](https://fastapi.tiangolo.com/python-types/). -You will also see a 20 seconds refresher on the section [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank}. +You will also see a 20 seconds refresher on the section [Tutorial - User Guide: First Steps](tutorial/index.md). ## Editor support @@ -24,11 +24,11 @@ You will also see a 20 seconds refresher on the section [Tutorial - User Guide: Here's how your editor might help you: -* in Visual Studio Code: +* in [Visual Studio Code](https://code.visualstudio.com/): -* in PyCharm: +* in [PyCharm](https://www.jetbrains.com/pycharm/): @@ -36,7 +36,7 @@ You will get completion for everything while writing the **minimum** amount of c You won't need to keep guessing the types of different attributes in your models, if they could be `None`, etc. Your editor will be able to help you with everything because **SQLModel** is based on **standard Python type annotations**. -**SQLModel** adopts PEP 681 for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances. +**SQLModel** adopts [PEP 681](https://peps.python.org/pep-0681/) for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances. @@ -56,7 +56,7 @@ And later, you can fine-tune everything with all the power of SQLAlchemy and Pyd Underneath, ✨ a **SQLModel** model is also a **Pydantic** model. ✨ -There was a lot of research and effort dedicated to make it that way. +There was a lot of research and effort dedicated to making it that way. That means you get all of **Pydantic's features**, including automatic data **validation**, **serialization**, and **documentation**. You can use SQLModel in the same way you can use Pydantic. @@ -74,9 +74,9 @@ You will learn more about combining different models later in the tutorial. Underneath, ✨ a **SQLModel** model is also a **SQLAlchemy** model. ✨ -There was **a lot** of research and effort dedicated to make it that way. In particular, there was a lot of effort and experimentation in making a single model be **both a SQLAlchemy model and a Pydantic** model at the same time. +There was **a lot** of research and effort dedicated to making it that way. In particular, there was a lot of effort and experimentation in making a single model be **both a SQLAlchemy model and a Pydantic** model at the same time. -That means that you get all the power, robustness, and certainty of SQLAlchemy, the most widely used database library in Python. +That means that you get all the power, robustness, and certainty of SQLAlchemy, the [most widely used database library in Python](https://lp.jetbrains.com/python-developers-survey-2024/#orms). **SQLModel** provides its own utilities to improve the developer experience, but underneath, it uses all of SQLAlchemy. diff --git a/docs/help.md b/docs/help.md index 6e5fe581f7..f76d9882f1 100644 --- a/docs/help.md +++ b/docs/help.md @@ -1,18 +1,12 @@ -# Help SQLModel - Get Help +# Help -Do you like **SQLModel**? +Would you like to help SQLModel or get help about SQLModel? -Would you like to help SQLModel, other users, and the author? +There are very simple ways to help and get help. -Or would you like to get help with **SQLModel**? +## Subscribe to the newsletter -There are very simple ways to help (several involve just one or two clicks). - -And there are several ways to get help too. - -## Subscribe to the FastAPI and Friends newsletter - -You can subscribe to the (infrequent) **FastAPI and friends** newsletter to stay updated about: +You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](https://fastapi.tiangolo.com/newsletter/) to stay updated about: * News about FastAPI and friends, including SQLModel 🚀 * Guides 📝 @@ -20,231 +14,60 @@ You can subscribe to the (infrequent) https://github.com/fastapi/sqlmodel. ⭐️ +You can "star" SQLModel in GitHub (clicking the star button at the top right): [https://github.com/fastapi/sqlmodel](https://github.com/fastapi/sqlmodel). ⭐️ -By adding a star, other users will be able to find it more easily and see that it has been already useful for others. +By adding a star, other users will be able to find it more easily and see that it has already been useful for others. ## Watch the GitHub repository for releases -You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/sqlmodel. 👀 +You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): [https://github.com/fastapi/sqlmodel](https://github.com/fastapi/sqlmodel). 👀 There you can select "Releases only". By doing it, you will receive notifications (in your email) whenever there's a new release (a new version) of **SQLModel** with bug fixes and new features. -## Connect with the author - -You can connect with me (Sebastián Ramírez / `tiangolo`), the author. - -You can: - -* Follow me on **GitHub**. - * See other Open Source projects I have created that could help you. - * Follow me to see when I create a new Open Source project. -* Follow me on **Twitter**. - * Tell me how you use SQLModel (I love to hear that). - * Hear when I make announcements or release new tools. -* Connect with me on **Linkedin**. - * Hear when I make announcements or release new tools (although I use Twitter more often 🤷‍♂). -* Read what I write (or follow me) on **Dev.to** or **Medium**. - * Read other ideas, articles, and read about tools I have created. - * Follow me to read when I publish something new. - -## Tweet about **SQLModel** - -Tweet about **SQLModel** and let me and others know why you like it. 🎉 - -I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc. - -## Help others with questions in GitHub - -You can try and help others with their questions in: - -* GitHub Discussions -* GitHub Issues - -In many cases you might already know the answer for those questions. 🤓 - -Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗 - -The idea is for the **SQLModel** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other. - ---- - -Here's how to help others with questions (in discussions or issues): - -### Understand the question - -* Check if you can understand what is the **purpose** and use case of the person asking. +## Follow the author -* Then check if the question (the vast majority are questions) is **clear**. +You can follow [me (Sebastián Ramírez / `tiangolo`)](https://tiangolo.com), the author in a few places, to hear when I have news to share about FastAPI and friends: -* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**. - -* If you can't understand the question, ask for more **details**. - -### Reproduce the problem - -For most of the cases and most of the questions there's something related to the person's **original code**. - -In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**. - -* You can ask them to provide a minimal, reproducible, example, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better. - -* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first. - -### Suggest solutions - -* After being able to understand the question, you can give them a possible **answer**. - -* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do. - -### Ask to close - -If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸 - -* Now, if that solved their problem, you can ask them to: - - * In GitHub Discussions: mark the comment as the **answer**. - * In GitHub Issues: **close** the issue**. - -## Watch the GitHub repository - -You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): https://github.com/fastapi/sqlmodel. 👀 - -If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc. - -Then you can try and help them solve those questions. +* [@tiangolo on **GitHub**](https://github.com/tiangolo). +* [@tiangolo on **X (Twitter)**](https://x.com/tiangolo) +* [@tiangolo.com on **Bluesky**](https://bsky.app/profile/tiangolo.com) +* [@tiangolo on **LinkedIn**](https://www.linkedin.com/in/tiangolo/). ## Ask Questions -You can create a new question in the GitHub repository, for example to: +You can [create a new question](https://github.com/fastapi/sqlmodel/discussions/new?category=questions) in the GitHub repository, for example to: * Ask a **question** or ask about a **problem**. * Suggest a new **feature**. -**Note**: if you do it, then I'm going to ask you to also help others. 😉 - -## Review Pull Requests - -You can help me review pull requests from others. - -Again, please try your best to be kind. 🤗 - ---- - -Here's what to have in mind and how to review a pull request: +## Join the Chat -### Understand the problem - -* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue. - -* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that. - -### Don't worry about style - -* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually. - -* Also don't worry about style rules, there are already automatized tools checking that. - -And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes. - -### Check the code - -* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem. - -* Then **comment** saying that you did that, that's how I will know you really checked it. - -/// info - -Unfortunately, I can't simply trust PRs that just have several approvals. - -Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅 - -So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓 - -/// - -* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things. - -### Tests - -* Help me check that the PR has **tests**. - -* Check that the tests **fail** before the PR. 🚨 - -* Then check that the tests **pass** after the PR. ✅ - -* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that. - -* Then also comment what you tried, that way I'll know that you checked it. 🤓 - -## Create a Pull Request - -You can [contribute](contributing.md){.internal-link target=_blank} to the source code with Pull Requests, for example: - -* To fix a typo you found on the documentation. -* To propose new documentation sections. -* To fix an existing issue/bug. - * Make sure to add tests. -* To add a new feature. - * Make sure to add tests. - * Make sure to add documentation if it's relevant. - -## Help Maintain SQLModel - -Help me maintain **SQLModel**! 🤓 - -There's a lot of work to do, and for most of it, **YOU** can do it. - -The main tasks that you can do right now are: - -* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above). -* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above). - -Those two tasks are what **consume time the most**. That's the main work of maintaining SQLModel. - -If you can help me with that, **you are helping me maintain SQLModel** and making sure it keeps **advancing faster and better**. 🚀 - -## Join the chat - -Join the 👥 FastAPI and Friends Discord chat server 👥 and hang out with others in the community. There's a `#sqlmodel` channel. +Join the 👥 [FastAPI and Friends Discord chat server](https://discord.gg/VQjSZaeJmf) 👥 and hang out with others in the community. There's a `#sqlmodel` channel. /// tip -For questions, ask them in GitHub Discussions, there's a much better chance you will receive help there. +For questions, ask them in [GitHub Discussions](https://github.com/fastapi/sqlmodel/discussions/new?category=questions), there's a much better chance you will receive help there. Use the chat only for other general conversations. /// -### Don't use the chat for questions - -Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers. - -In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. 😅 - -Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation. - -On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. 😄 - -## Sponsor the author - -You can also financially support the author (me) through GitHub sponsors. - -There you could buy me a coffee ☕️ to say thanks. 😄 - -## Sponsor the tools that power SQLModel - -As you have seen in the documentation, SQLModel stands on the shoulders of giants, Pydantic and SQLAlchemy. - -You can also sponsor: +### Don't use the Chat for Questions -* Samuel Colvin (Pydantic) -* SQLAlchemy +Keep in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers. ---- +In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. -Thanks! 🚀 +Conversations in the chat systems are also not as easily searchable as in GitHub, they get lost. diff --git a/docs/index.md b/docs/index.md index cd48b91385..3114f5bc2f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,10 @@ +--- +include_yaml: + sponsors: data/sponsors.yml +--- + +# + @@ -12,30 +19,27 @@ SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.

- + Test - - Publish - - + Coverage - + Package version

--- -**Documentation**: https://sqlmodel.tiangolo.com +**Documentation**: [https://sqlmodel.tiangolo.com](https://sqlmodel.tiangolo.com) -**Source Code**: https://github.com/fastapi/sqlmodel +**Source Code**: [https://github.com/fastapi/sqlmodel](https://github.com/fastapi/sqlmodel) --- SQLModel is a library for interacting with SQL databases from Python code, with Python objects. It is designed to be intuitive, easy to use, highly compatible, and robust. -**SQLModel** is based on Python type annotations, and powered by Pydantic and SQLAlchemy. +**SQLModel** is based on Python type annotations, and powered by [Pydantic](https://pydantic-docs.helpmanual.io/) and [SQLAlchemy](https://sqlalchemy.org/). The key features are: @@ -51,10 +55,10 @@ The key features are: {% if sponsors %} {% for sponsor in sponsors.gold -%} - + {% endfor -%} {%- for sponsor in sponsors.silver -%} - + {% endfor %} {% endif %} @@ -62,9 +66,9 @@ The key features are: ## SQL Databases in FastAPI - + -**SQLModel** is designed to simplify interacting with SQL databases in FastAPI applications, it was created by the same author. 😁 +**SQLModel** is designed to simplify interacting with SQL databases in [FastAPI](https://fastapi.tiangolo.com) applications, it was created by the same [author](https://tiangolo.com/). 😁 It combines SQLAlchemy and Pydantic and tries to simplify the code you write as much as possible, allowing you to reduce the **code duplication to a minimum**, but while getting the **best developer experience** possible. @@ -72,27 +76,28 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python. +A recent and currently supported [version of Python](https://www.python.org/downloads/). As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. ## Installation -Make sure you create a virtual environment, activate it, and then install SQLModel, for example with: +First, [install `uv`](https://docs.astral.sh/uv/getting-started/installation/), and then add SQLModel to your project:
```console -$ pip install sqlmodel +$ uv add sqlmodel ---> 100% -Successfully installed sqlmodel ```
+If you prefer to use `pip`, install `sqlmodel` inside a virtual environment. See the [installation guide](install.md) for the alternative steps. + ## Example -For an introduction to databases, SQL, and everything else, see the SQLModel documentation. +For an introduction to databases, SQL, and everything else, see the [SQLModel documentation](https://sqlmodel.tiangolo.com/databases/). Here's a quick example. ✨ diff --git a/docs/install.md b/docs/install.md index 129df3512b..e8bf5d23f3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,39 +1,86 @@ # Install **SQLModel** -Create a project directory, create a [virtual environment](virtual-environments.md){.internal-link target=_blank}, activate it, and then install **SQLModel**, for example with: +The first step is to set up your project and add **SQLModel**. + +Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/), then create a project and add SQLModel:
```console -$ pip install sqlmodel +$ uv init awesome-project --bare +$ cd awesome-project +$ uv add sqlmodel ---> 100% -Successfully installed sqlmodel pydantic sqlalchemy ```
-As **SQLModel** is built on top of SQLAlchemy and Pydantic, when you install `sqlmodel` they will also be automatically installed. +`uv add` creates the project's virtual environment in `.venv`, adds SQLModel to `pyproject.toml`, and creates `uv.lock` so the same package versions can be installed later. + +/// details | What these commands do + +* `uv init`: create a new Python project. +* `awesome-project`: create the project in a new directory with this name. +* `--bare`: create only the minimal `pyproject.toml` file, without generating a sample `main.py`, `README.md`, or other files. You will create the application files yourself in the next steps of this tutorial. + +Then `cd awesome-project` enters the new project directory before adding SQLModel. + +`uv` will use a compatible Python version already installed on your system, or download one if needed. + +When you run `uv add`, it selects compatible versions of SQLModel and all the packages SQLModel depends on. It records the exact versions in `uv.lock`, making it possible to install the same package versions later on another computer or when deploying the application. + +Creating or updating this file is called [**locking** the project dependencies](https://docs.astral.sh/uv/concepts/projects/sync/). `uv` does this automatically when you add a package. + +/// + +As **SQLModel** is built on top of [SQLAlchemy](https://www.sqlalchemy.org/) and [Pydantic](https://pydantic-docs.helpmanual.io/), when you install `sqlmodel` they will also be automatically installed. + +/// details | Using `pip` instead + +If you prefer to manage a virtual environment and packages manually, create and activate a virtual environment and then install SQLModel with `pip install sqlmodel`. + +Read the [Virtual Environments guide](https://tiangolo.com/guides/virtual-environments/) for the detailed steps. + +/// + +## AI Agent Skills + +SQLModel includes an official skill for AI coding agents. It is bundled with the package, so its guidance stays aligned with the version of SQLModel installed in your project and updates when you update SQLModel. + +After installing SQLModel in your project, you can install the skill with Library Skills: + +```bash +uvx library-skills +``` + +/// note + +`uvx` is an alias for `uv tool run`. It runs Library Skills in a temporary, isolated environment while Library Skills scans the packages installed in your project. + +/// + +The skill is compatible with Codex, Claude Code, Cursor, GitHub Copilot, Gemini CLI, Pi, OpenCode, and most other coding agents. For Claude Code, select `.claude/skills` when asked where to install the skill. ## Install DB Browser for SQLite -Remember that [SQLite is a simple database in a single file](databases.md#a-single-file-database){.internal-link target=_blank}? +Remember that [SQLite is a simple database in a single file](databases.md#a-single-file-database)? For most of the tutorial I'll use SQLite for the examples. -Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning. +Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](databases.md#a-server-database), so it will be perfect for learning. -In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like PostgreSQL (which is also free). But for now we'll stick to SQLite. +In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like [PostgreSQL](https://www.postgresql.org/) (which is also free). But for now we'll stick to SQLite. Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data. -To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use DB Browser for SQLite. +To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use [DB Browser for SQLite](https://sqlitebrowser.org/). It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface. -Go ahead and Install DB Browser for SQLite, it's free. +Go ahead and [Install DB Browser for SQLite](https://sqlitebrowser.org/), it's free. ## Next Steps -Okay, let's get going! On the next section we'll start the [Tutorial - User Guide](tutorial/index.md). 🚀 +Okay, let's get going! In the next section we'll start the [Tutorial - User Guide](tutorial/index.md). 🚀 diff --git a/docs/js/termynal.js b/docs/js/termynal.js index 45bb371c83..de4d8d1442 100644 --- a/docs/js/termynal.js +++ b/docs/js/termynal.js @@ -127,27 +127,29 @@ class Termynal { } generateRestart() { - const restart = document.createElement('a') - restart.onclick = (e) => { - e.preventDefault() + const restart = document.createElement('button') + restart.type = 'button' + + restart.onclick = () => { this.container.innerHTML = '' this.init() } - restart.href = '#' + restart.setAttribute('data-terminal-control', '') restart.innerHTML = "restart ↻" return restart } generateFinish() { - const finish = document.createElement('a') - finish.onclick = (e) => { - e.preventDefault() + const finish = document.createElement('button') + finish.type = 'button' + + finish.onclick = () => { this.lineDelay = 0 this.typeDelay = 0 this.startDelay = 0 } - finish.href = '#' + finish.setAttribute('data-terminal-control', '') finish.innerHTML = "fast →" this.finishElement = finish diff --git a/docs/management-tasks.md b/docs/management-tasks.md deleted file mode 100644 index a1eb0b1863..0000000000 --- a/docs/management-tasks.md +++ /dev/null @@ -1,117 +0,0 @@ -# Repository Management Tasks - -These are the tasks that can be performed to manage the SQLModel repository by [team members](./management.md#team){.internal-link target=_blank}. - -/// tip - -This section is useful only to a handful of people, team members with permissions to manage the repository. You can probably skip it. 😉 - -/// - -...so, you are a [team member of SQLModel](./management.md#team){.internal-link target=_blank}? Wow, you are so cool! 😎 - -You can help with everything on [Help SQLModel - Get Help](./help.md){.internal-link target=_blank} the same ways as external contributors. But additionally, there are some tasks that only you (as part of the team) can perform. - -Here are the general instructions for the tasks you can perform. - -Thanks a lot for your help. 🙇 - -## Be Nice - -First of all, be nice. 😊 - -You probably are super nice if you were added to the team, but it's worth mentioning it. 🤓 - -### When Things are Difficult - -When things are great, everything is easier, so that doesn't need much instructions. But when things are difficult, here are some guidelines. - -Try to find the good side. In general, if people are not being unfriendly, try to thank their effort and interest, even if you disagree with the main subject (discussion, PR), just thank them for being interested in the project, or for having dedicated some time to try to do something. - -It's difficult to convey emotion in text, use emojis to help. 😅 - -In discussions and PRs, in many cases, people bring their frustration and show it without filter, in many cases exaggerating, complaining, being entitled, etc. That's really not nice, and when it happens, it lowers our priority to solve their problems. But still, try to breath, and be gentle with your answers. - -Try to avoid using bitter sarcasm or potentially passive-aggressive comments. If something is wrong, it's better to be direct (try to be gentle) than sarcastic. - -Try to be as specific and objective as possible, avoid generalizations. - -For conversations that are more difficult, for example to reject a PR, you can ask me (@tiangolo) to handle it directly. - -## Edit PR Titles - -* Edit the PR title to start with an emoji from gitmoji. - * Use the emoji character, not the GitHub code. So, use `🐛` instead of `:bug:`. This is so that it shows up correctly outside of GitHub, for example in the release notes. -* Start the title with a verb. For example `Add`, `Refactor`, `Fix`, etc. This way the title will say the action that the PR does. Like `Add support for teleporting`, instead of `Teleporting wasn't working, so this PR fixes it`. -* Edit the text of the PR title to start in "imperative", like giving an order. So, instead of `Adding support for teleporting` use `Add support for teleporting`. -* Try to make the title descriptive about what it achieves. If it's a feature, try to describe it, for example `Add support for teleporting` instead of `Create TeleportAdapter class`. -* Do not finish the title with a period (`.`). - -Once the PR is merged, a GitHub Action (latest-changes) will use the PR title to update the latest changes automatically. - -So, having a nice PR title will not only look nice in GitHub, but also in the release notes. 📝 - -## Add Labels to PRs - -The same GitHub Action latest-changes uses one label in the PR to decide the section in the release notes to put this PR in. - -Make sure you use a supported label from the latest-changes list of labels: - -* `breaking`: Breaking Changes - * Existing code will break if they update the version without changing their code. This rarely happens, so this label is not frequently used. -* `security`: Security Fixes - * This is for security fixes, like vulnerabilities. It would almost never be used. -* `feature`: Features - * New features, adding support for things that didn't exist before. -* `bug`: Fixes - * Something that was supported didn't work, and this fixes it. There are many PRs that claim to be bug fixes because the user is doing something in an unexpected way that is not supported, but they considered it what should be supported by default. Many of these are actually features or refactors. But in some cases there's an actual bug. -* `refactor`: Refactors - * This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc. -* `upgrade`: Upgrades - * This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies or GitHub Action versions should be marked as `internal`, not `upgrade`. -* `docs`: Docs - * Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations. - * You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`. -* `internal`: Internal - * Use this for changes that only affect how the repo is managed. For example upgrades to internal dependencies, changes in GitHub Actions or scripts, etc. - -/// tip - -Some tools like Dependabot, will add some labels, like `dependencies`, but have in mind that this label is not used by the `latest-changes` GitHub Action, so it won't be used in the release notes. Please make sure one of the labels above is added. - -/// - -## Review PRs - -* If a PR doesn't explain what it does or why, if it seems like it could be useful, ask for more information. Otherwise, feel free to close it. - -* If a PR seems to be spam, meaningless, only to change statistics (to appear as "contributor") or similar, you can simply mark it as `invalid`, and it will be automatically closed. - -* If a PR seems to be AI generated, and seems like reviewing it would take more time from you than the time it took to write the prompt, mark it as `maybe-ai`, and it will be automatically closed. - -* A PR should have a specific use case that it is solving. - -* If the PR is for a feature, it should have docs. - * Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use. -* The docs should include a source example file, not write Python directly in Markdown. -* If the source example(s) file can have different syntax for different Python versions, there should be different versions of the file, and they should be shown in tabs in the docs. -* There should be tests testing the source example. -* Before the PR is applied, the new tests should fail. -* After applying the PR, the new tests should pass. -* Coverage should stay at 100%. -* If you see the PR makes sense, or we discussed it and considered it should be accepted, you can add commits on top of the PR to tweak it, to add docs, tests, format, refactor, remove extra files, etc. -* Feel free to comment in the PR to ask for more information, to suggest changes, etc. -* Once you think the PR is ready, move it in the internal GitHub project for me to review it. - -## Dependabot PRs - -Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others. - -* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml` in the main dependencies, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed. -* If the PR updates one of the internal dependencies, for example the group `dev` in `pyproject.toml`, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎 - -## Mark GitHub Discussions Answers - -When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer". - -You can filter discussions by `Questions` that are `Unanswered`. diff --git a/docs/management.md b/docs/management.md index 0078e364c3..4a4e512474 100644 --- a/docs/management.md +++ b/docs/management.md @@ -4,42 +4,12 @@ Here's a short description of how the SQLModel repository is managed and maintai ## Owner -I, @tiangolo, am the creator and owner of the SQLModel repository. 🤓 +I, [@tiangolo](https://github.com/tiangolo), am the creator and owner of the SQLModel repository. 🤓 -I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the BDFL. 😅 +I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life). 😅 ## Team There's a team of people that help manage and maintain the project. 😎 -They have different levels of permissions and [specific instructions](./management-tasks.md){.internal-link target=_blank}. - -Some of the tasks they can perform include: - -* Adding labels to PRs. -* Editing PR titles. -* Adding commits on top of PRs to tweak them. -* Mark answers in GitHub Discussions questions, etc. -* Merge some specific types of PRs. - -Joining the team is by invitation only, and I could update or remove permissions, instructions, or membership. - -### Team Members - -This is the current list of team members. 😎 - -
-{% for user in members["members"] %} - - -{% endfor %} - -
- -Additional to them, there's a large community of people helping each other and getting involved in the projects in different ways. - -## External Contributions - -External contributions are very welcome and appreciated, including answering questions, submitting PRs, etc. 🙇‍♂️ - -There are many ways to [help maintain SQLModel](./help.md){.internal-link target=_blank}. +Learn more about it in [tiangolo.com - GitHub FastAPI](https://tiangolo.com/github-fastapi/). diff --git a/docs/release-notes.md b/docs/release-notes.md index 8d534858e7..4ee1dd77bd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,8 +2,161 @@ ## Latest Changes +## 0.0.47 (2026-09-23) + +### Features + +* ✨ Add direct support for PostgreSQL, MySQL, and MariaDB, include in CI tests. PR [#2103](https://github.com/fastapi/sqlmodel/pull/2103) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.46 (2026-09-22) + +### Fixes + +* 🐛 Fix timedelta arithmetic with UTC datetime fields. PR [#2101](https://github.com/fastapi/sqlmodel/pull/2101) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.45 (2026-09-21) + +### Breaking Changes + +* ✨ Use UTC datetimes by default. PR [#2099](https://github.com/fastapi/sqlmodel/pull/2099) by [@tiangolo](https://github.com/tiangolo). + +`datetime` fields now use `UTCDateTime`, a custom type based on `DateTime(timezone=True)`. It requires aware datetime parameters, normalizes them to UTC, and returns aware UTC values from database reads, including on SQLite and MySQL/MariaDB. Pydantic's `AwareDatetime` uses the same type, while `NaiveDatetime` explicitly selects `DateTime(timezone=False)`. Plain `datetime` validation still accepts both aware and naive values, but naive database parameters now raise an error. + +Existing databases are not changed automatically. To retain naive storage, use `NaiveDatetime` or an explicit `Field(sa_type=DateTime(timezone=False))`. Explicit SQLAlchemy types retain their existing behavior. PostgreSQL needs a column migration that explicitly specifies the timezone of existing values. SQLite and MySQL/MariaDB keep their column definitions, but existing non-UTC values need a data migration before adopting the new type. Update datetime producers, query parameters, comparisons, and any database defaults to follow the new UTC convention. + +Read [Datetimes and Timezones: Upgrade Existing Applications](https://sqlmodel.tiangolo.com/advanced/datetime/#upgrade-existing-applications) before upgrading. + +## 0.0.44 (2026-09-21) + +### Refactors + +* ♻️ Replace the dataclass_transform shim with typing_extensions. PR [#2097](https://github.com/fastapi/sqlmodel/pull/2097) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.43 (2026-09-21) + +### Fixes + +* 🐛 Fix SQLAlchemy type handling in `Field`. PR [#2095](https://github.com/fastapi/sqlmodel/pull/2095) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* ⬆ Bump sqlalchemy from 2.0.51 to 2.0.52. PR [#2086](https://github.com/fastapi/sqlmodel/pull/2086) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the python-packages group with 9 updates. PR [#2085](https://github.com/fastapi/sqlmodel/pull/2085) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the github-actions group with 4 updates. PR [#2084](https://github.com/fastapi/sqlmodel/pull/2084) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pre-commit hooks. PR [#2083](https://github.com/fastapi/sqlmodel/pull/2083) by [@pr-submit[bot]](https://github.com/apps/pr-submit). + +## 0.0.42 (2026-08-28) + +### Features + +* 🏷️ Allow `Discriminator` type for `discriminator` parameter in `Field`. PR [#1729](https://github.com/fastapi/sqlmodel/pull/1729) by [@YuriiMotov](https://github.com/YuriiMotov). + +## 0.0.41 (2026-08-28) + +### Refactors + +* 🗑️ Deprecate `min_items` and `max_items` parameters of `Field`. PR [#1731](https://github.com/fastapi/sqlmodel/pull/1731) by [@YuriiMotov](https://github.com/YuriiMotov). + +## 0.0.40 (2026-08-28) + +### Docs + +* ✏️ Fix grammar and typos in docs. PR [#2058](https://github.com/fastapi/sqlmodel/pull/2058) by [@YuriiMotov](https://github.com/YuriiMotov). +* 📝 Fix "Decimals in SQLModel" to reference `Field()` instead of `condecimal()`. PR [#2035](https://github.com/fastapi/sqlmodel/pull/2035) by [@anxkhn](https://github.com/anxkhn). +* 🐛 Use buttons for Termynal controls. PR [#2063](https://github.com/fastapi/sqlmodel/pull/2063) by [@tiangolo](https://github.com/tiangolo). +* 📝 Add Library Skills documentation. PR [#2052](https://github.com/fastapi/sqlmodel/pull/2052) by [@tiangolo](https://github.com/tiangolo). +* 📝 Recommend uv projects by default in docs. PR [#2050](https://github.com/fastapi/sqlmodel/pull/2050) by [@tiangolo](https://github.com/tiangolo). +* 🐛 Prevent Termynal controls from scrolling. PR [#2051](https://github.com/fastapi/sqlmodel/pull/2051) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update help docs. PR [#2049](https://github.com/fastapi/sqlmodel/pull/2049) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* ⬆️ Bump Typer min version to `0.26.1`. PR [#2078](https://github.com/fastapi/sqlmodel/pull/2078) by [@YuriiMotov](https://github.com/YuriiMotov). +* ⬆️ Bump `setup-uv` action to `10.0.1`. PR [#2077](https://github.com/fastapi/sqlmodel/pull/2077) by [@YuriiMotov](https://github.com/YuriiMotov). +* 👷 Migrate automatic labels to Latest Changes. PR [#2074](https://github.com/fastapi/sqlmodel/pull/2074) by [@tiangolo](https://github.com/tiangolo). +* 👷 Remove legacy label check. PR [#2072](https://github.com/fastapi/sqlmodel/pull/2072) by [@tiangolo](https://github.com/tiangolo). +* 🔐 Use PR Submit for pull requests. PR [#2071](https://github.com/fastapi/sqlmodel/pull/2071) by [@tiangolo](https://github.com/tiangolo). +* 👷 Use GitHub CLI for Git authentication. PR [#2070](https://github.com/fastapi/sqlmodel/pull/2070) by [@tiangolo](https://github.com/tiangolo). +* 👷 Use PR Push commit identity. PR [#2069](https://github.com/fastapi/sqlmodel/pull/2069) by [@tiangolo](https://github.com/tiangolo). +* 🔒 Replace pre-commit PAT with PR Push. PR [#2067](https://github.com/fastapi/sqlmodel/pull/2067) by [@tiangolo](https://github.com/tiangolo). +* 👷 Remove Zensical docs cache. PR [#2065](https://github.com/fastapi/sqlmodel/pull/2065) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove the old Latest Changes workflow. PR [#2064](https://github.com/fastapi/sqlmodel/pull/2064) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump the python-packages group with 7 updates. PR [#2060](https://github.com/fastapi/sqlmodel/pull/2060) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the github-actions group with 5 updates. PR [#2059](https://github.com/fastapi/sqlmodel/pull/2059) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆️ Upgrade latest-changes to 0.7.1. PR [#2054](https://github.com/fastapi/sqlmodel/pull/2054) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump the python-packages group across 1 directory with 6 updates. PR [#2042](https://github.com/fastapi/sqlmodel/pull/2042) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the github-actions group across 1 directory with 4 updates. PR [#2045](https://github.com/fastapi/sqlmodel/pull/2045) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Add GH workflow to bump pre-commit hook versions. PR [#2026](https://github.com/fastapi/sqlmodel/pull/2026) by [@YuriiMotov](https://github.com/YuriiMotov). +* 🔧 Set Dependabot schedule interval to "monthly". PR [#2025](https://github.com/fastapi/sqlmodel/pull/2025) by [@YuriiMotov](https://github.com/YuriiMotov). +* ⬆ Bump the python-packages group with 4 updates. PR [#2039](https://github.com/fastapi/sqlmodel/pull/2039) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump https://github.com/crate-ci/typos from v1.47.2 to 1.48.0 in the pre-commit group. PR [#2038](https://github.com/fastapi/sqlmodel/pull/2038) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Fix latest-changes checkout target. PR [#2031](https://github.com/fastapi/sqlmodel/pull/2031) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Update issue-manager to 0.8.1. PR [#2030](https://github.com/fastapi/sqlmodel/pull/2030) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Update latest-changes to 0.6.1. PR [#2029](https://github.com/fastapi/sqlmodel/pull/2029) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump the python-packages group across 1 directory with 8 updates. PR [#2028](https://github.com/fastapi/sqlmodel/pull/2028) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the github-actions group with 4 updates. PR [#2027](https://github.com/fastapi/sqlmodel/pull/2027) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump sqlalchemy from 2.0.50 to 2.0.51. PR [#2013](https://github.com/fastapi/sqlmodel/pull/2013) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Update release workflows, fix gitmoji emoji used. PR [#2019](https://github.com/fastapi/sqlmodel/pull/2019) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.39 (2026-06-25) + +### Features + +* ✨ Add SQLModel Agent Library Skill, install with `uvx library-skills`. PR [#2017](https://github.com/fastapi/sqlmodel/pull/2017) by [@tiangolo](https://github.com/tiangolo). + +### Fixes + +* 🐛 Fix type of `__sqlmodel_relationships__`. PR [#1996](https://github.com/fastapi/sqlmodel/pull/1996) by [@svlandeg](https://github.com/svlandeg). + +### Docs + +* 📝 Update docs badges: remove Publish badge, it doesn't give extra information. PR [#2009](https://github.com/fastapi/sqlmodel/pull/2009) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update and simplify docs about help and management. PR [#1978](https://github.com/fastapi/sqlmodel/pull/1978) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update docs about contributing, reference central docs. PR [#1975](https://github.com/fastapi/sqlmodel/pull/1975) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update security policy. PR [#1973](https://github.com/fastapi/sqlmodel/pull/1973) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update link syntax to minimal Markdown. PR [#1800](https://github.com/fastapi/sqlmodel/pull/1800) by [@tiangolo](https://github.com/tiangolo). +* 📝 Update admonitions, simplify and use defaults. PR [#1915](https://github.com/fastapi/sqlmodel/pull/1915) by [@tiangolo](https://github.com/tiangolo). +* 🔒️ Only allow team members to modify dependencies. PR [#1914](https://github.com/fastapi/sqlmodel/pull/1914) by [@svlandeg](https://github.com/svlandeg). + ### Internal +* 👷 Simplify pull request workflow triggers. PR [#2016](https://github.com/fastapi/sqlmodel/pull/2016) by [@tiangolo](https://github.com/tiangolo). +* 👷 Update issue-manager to 0.7.1. PR [#2015](https://github.com/fastapi/sqlmodel/pull/2015) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Update issue-manager to 0.7.0. PR [#2014](https://github.com/fastapi/sqlmodel/pull/2014) by [@tiangolo](https://github.com/tiangolo). +* 🔒️ Update zizmor workflow security checks. PR [#2010](https://github.com/fastapi/sqlmodel/pull/2010) by [@tiangolo](https://github.com/tiangolo). +* 👷 Fix alls-green test dependency. PR [#2008](https://github.com/fastapi/sqlmodel/pull/2008) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump the python-packages group with 5 updates. PR [#2006](https://github.com/fastapi/sqlmodel/pull/2006) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump https://github.com/crate-ci/typos from v1.47.1 to v1.47.2 in the pre-commit group. PR [#2005](https://github.com/fastapi/sqlmodel/pull/2005) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump astral-sh/setup-uv from 8.1.0 to 8.2.0 in the github-actions group. PR [#2004](https://github.com/fastapi/sqlmodel/pull/2004) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the python-packages group with 3 updates. PR [#2000](https://github.com/fastapi/sqlmodel/pull/2000) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump https://github.com/crate-ci/typos from v1.46.0 to v1.47.1 in the pre-commit group. PR [#1999](https://github.com/fastapi/sqlmodel/pull/1999) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the github-actions group with 2 updates. PR [#1998](https://github.com/fastapi/sqlmodel/pull/1998) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump sqlalchemy from 2.0.49 to 2.0.50. PR [#1992](https://github.com/fastapi/sqlmodel/pull/1992) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump the python-packages group across 1 directory with 9 updates. PR [#1995](https://github.com/fastapi/sqlmodel/pull/1995) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump eps1lon/actions-label-merge-conflict from 3.0.3 to 3.1.0 in the github-actions group. PR [#1993](https://github.com/fastapi/sqlmodel/pull/1993) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆️ Bump Pydantic to 2.13.4. PR [#1985](https://github.com/fastapi/sqlmodel/pull/1985) by [@YuriiMotov](https://github.com/YuriiMotov). +* 👷 Automate release preparation. PR [#1988](https://github.com/fastapi/sqlmodel/pull/1988) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove old package stub slim, not used nor needed. PR [#1987](https://github.com/fastapi/sqlmodel/pull/1987) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump the github-actions group with 2 updates. PR [#1983](https://github.com/fastapi/sqlmodel/pull/1983) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Configure Dependabot to group updates and update weekly. PR [#1972](https://github.com/fastapi/sqlmodel/pull/1972) by [@YuriiMotov](https://github.com/YuriiMotov). +* 🔥 Remove config files now in central GitHub repo. PR [#1979](https://github.com/fastapi/sqlmodel/pull/1979) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump zizmorcore/zizmor-action from 0.5.3 to 0.5.5. PR [#1974](https://github.com/fastapi/sqlmodel/pull/1974) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Fix branch name in `guard-dependencies.yml`. PR [#1916](https://github.com/fastapi/sqlmodel/pull/1916) by [@svlandeg](https://github.com/svlandeg). +* ♻️ Migrate docs to Zensical. PR [#1913](https://github.com/fastapi/sqlmodel/pull/1913) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump ruff from 0.15.12 to 0.15.13. PR [#1910](https://github.com/fastapi/sqlmodel/pull/1910) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 🔒️ Add zizmor and fix audit findings. PR [#1871](https://github.com/fastapi/sqlmodel/pull/1871) by [@YuriiMotov](https://github.com/YuriiMotov). +* ⬆ Bump ty from 0.0.34 to 0.0.35. PR [#1904](https://github.com/fastapi/sqlmodel/pull/1904) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump actions/add-to-project from 1.0.2 to 2.0.0. PR [#1900](https://github.com/fastapi/sqlmodel/pull/1900) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump actions/labeler from 6.0.1 to 6.1.0. PR [#1902](https://github.com/fastapi/sqlmodel/pull/1902) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pydantic-settings from 2.14.0 to 2.14.1. PR [#1903](https://github.com/fastapi/sqlmodel/pull/1903) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump prek from 0.3.11 to 0.3.13. PR [#1901](https://github.com/fastapi/sqlmodel/pull/1901) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 👷 Add pre-commit for typos. PR [#1899](https://github.com/fastapi/sqlmodel/pull/1899) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Bump ty from 0.0.32 to 0.0.34. PR [#1898](https://github.com/fastapi/sqlmodel/pull/1898) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump typer from 0.25.0 to 0.25.1. PR [#1896](https://github.com/fastapi/sqlmodel/pull/1896) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump prek from 0.3.10 to 0.3.11. PR [#1892](https://github.com/fastapi/sqlmodel/pull/1892) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump typer from 0.24.2 to 0.25.0. PR [#1893](https://github.com/fastapi/sqlmodel/pull/1893) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump ruff from 0.15.11 to 0.15.12. PR [#1888](https://github.com/fastapi/sqlmodel/pull/1888) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump fastapi from 0.136.0 to 0.136.1. PR [#1887](https://github.com/fastapi/sqlmodel/pull/1887) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump typer from 0.24.1 to 0.24.2. PR [#1883](https://github.com/fastapi/sqlmodel/pull/1883) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump prek from 0.3.9 to 0.3.10. PR [#1881](https://github.com/fastapi/sqlmodel/pull/1881) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump ty from 0.0.31 to 0.0.32. PR [#1876](https://github.com/fastapi/sqlmodel/pull/1876) by [@dependabot[bot]](https://github.com/apps/dependabot). diff --git a/docs/tutorial/automatic-id-none-refresh.md b/docs/tutorial/automatic-id-none-refresh.md index c55951f4e4..11029f2d8e 100644 --- a/docs/tutorial/automatic-id-none-refresh.md +++ b/docs/tutorial/automatic-id-none-refresh.md @@ -20,7 +20,7 @@ When we create a new `Hero` instance, we don't set the `id`: ### How `int | None` Helps -Because we don't set the `id`, it takes the Python's default value of `None` that we set in `Field(default=None)`. +Because we don't set the `id`, it takes Python's default value of `None` that we set in `Field(default=None)`. This is the only reason why we define it with `int | None` and with a default value of `None`. @@ -51,7 +51,7 @@ That will output:
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -82,7 +82,7 @@ This will, again, output the `id`s of the objects as `None`:
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -107,7 +107,7 @@ And now, something unexpected happens, look at the output, it seems as if the `H
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -179,7 +179,7 @@ Let's see how it works:
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -249,12 +249,12 @@ session.refresh(hero_1) ...the **session** goes and makes the **engine** communicate with the database to get the recent data for this object `hero_1`, and then the **session** puts the data in the `hero_1` object and marks it as "fresh" or "not expired". -Here's how the output would look like: +Here's what the output would look like:
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -304,7 +304,7 @@ And the output shows again the same data:
```console -$ python app.py +$ uv run python app.py // Output above omitted 👆 @@ -347,7 +347,7 @@ And here's all the output generated by running this program, all together:
```console -$ python app.py +$ uv run python app.py INFO Engine BEGIN (implicit) INFO Engine PRAGMA main.table_info("hero") @@ -442,4 +442,4 @@ If you understood all that, now you know a lot about **SQLModel**, SQLAlchemy, a If you didn't get all that, it's fine, you can always come back later to `refresh` the concepts. -I think this might be one of the main types of bugs that cause problems and makes you scratch your head. So, good job studying it! 💪 +I think this might be one of the main types of bugs that cause problems and make you scratch your head. So, good job studying it! 💪 diff --git a/docs/tutorial/code-structure.md b/docs/tutorial/code-structure.md index 2c0cba526e..6db6217dd8 100644 --- a/docs/tutorial/code-structure.md +++ b/docs/tutorial/code-structure.md @@ -61,7 +61,7 @@ from .models import Hero, Team from .database import engine ``` -We can use these relative imports because, for example, in the file `app.py` (the `app` module) Python knows that it is **part of our Python package** because it is in the same directory as the file `__init__.py`. And all the Python files on the same directory are part of the same Python package too. +We can use these relative imports because, for example, in the file `app.py` (the `app` module) Python knows that it is **part of our Python package** because it is in the same directory as the file `__init__.py`. And all the Python files in the same directory are part of the same Python package too. ### Models File @@ -91,7 +91,7 @@ Here we import the models, the engine, and the function to create all the tables ### Order Matters -Remember that [Order Matters](create-db-and-table.md#sqlmodel-metadata-order-matters){.internal-link target=_blank} when calling `SQLModel.metadata.create_all()`? +Remember that [Order Matters](create-db-and-table.md#sqlmodel-metadata-order-matters) when calling `SQLModel.metadata.create_all()`? The point of that section in the docs is that you have to import the module that has the models **before** calling `SQLModel.metadata.create_all()`. @@ -102,13 +102,13 @@ We are doing that here, we import the models in `app.py` and **after** that we c Because now this is a larger project with a **Python package** and not a single Python file, we **cannot** call it just passing a single file name as we did before with: ```console -$ python app.py +$ uv run python app.py ``` Now we have to tell Python that we want it to execute a *module* that is part of a package: ```console -$ python -m project.app +$ uv run python -m project.app ``` The `-m` is to tell Python to call a *module*. And the next thing we pass is a string with `project.app`, that is the same format we would use in an **import**: @@ -129,7 +129,7 @@ So, the output would be:
```console -$ python -m project.app +$ uv run python -m project.app Created hero: id=1 secret_name='Dive Wilson' team_id=1 name='Deadpond' age=None Hero's team: name='Z-Force' headquarters='Sister Margaret's Bar' id=1 @@ -203,7 +203,7 @@ Using that trick of `TYPE_CHECKING` we can "import" the `Team` in `hero_model.py {* ./docs_src/tutorial/code_structure/tutorial002_py310/hero_model.py hl[1,5:6,16] *} -Have in mind that now we *have* to put the annotation of `Team` as a string: `"Team"`, so that Python doesn't have errors at runtime. +Keep in mind that now we *have* to put the annotation of `Team` as a string: `"Team"`, so that Python doesn't have errors at runtime. ### Team Model File @@ -221,14 +221,14 @@ Now, just for completeness, the `app.py` file would import the models from both And of course, all the tricks with `TYPE_CHECKING` and type annotations in strings are **only needed in the files with circular imports**. -As there are no circular imports with `app.py`, we can just use normal imports and use the classes as normally here. +As there are no circular imports with `app.py`, we can just use normal imports and use the classes normally here. And running that achieves the same result as before:
```console -$ python -m project.app +$ uv run python -m project.app Created hero: id=1 age=None name='Deadpond' secret_name='Dive Wilson' team_id=1 Hero's team: id=1 name='Z-Force' headquarters='Sister Margaret's Bar' diff --git a/docs/tutorial/connect/create-connected-rows.md b/docs/tutorial/connect/create-connected-rows.md index 2f952bf706..ffcfd0a784 100644 --- a/docs/tutorial/connect/create-connected-rows.md +++ b/docs/tutorial/connect/create-connected-rows.md @@ -37,7 +37,7 @@ Each row in the table `hero` will point to a row in the table `team`: table relationships -/// info +/// note We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet. @@ -82,7 +82,7 @@ If we run that code we have up to now, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 @@ -168,7 +168,7 @@ If we execute that in the command line, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 diff --git a/docs/tutorial/connect/create-connected-tables.md b/docs/tutorial/connect/create-connected-tables.md index 2b1a4f8758..c18367180d 100644 --- a/docs/tutorial/connect/create-connected-tables.md +++ b/docs/tutorial/connect/create-connected-tables.md @@ -100,7 +100,7 @@ This is the name of the **table** in the database, so it is `"team"`, not the na If you had a custom table name, you would use that custom table name. -/// info +/// note You can learn about setting a custom table name for a model in the Advanced User Guide. @@ -129,7 +129,7 @@ If we run the code we have up to now, it will go and create the database file `d
```console -$ python app.py +$ uv run python app.py // Automatically start a new transaction INFO Engine BEGIN (implicit) @@ -204,7 +204,7 @@ CREATE TABLE hero ( ) ``` -The only new is the `FOREIGN KEY` line, and as you can see, it tells the database what column in this table is a foreign key (`team_id`), which other (foreign) table it references (`team`) and which column in that table is the key to define which row to connect (`id`). +The only new part is the `FOREIGN KEY` line, and as you can see, it tells the database what column in this table is a foreign key (`team_id`), which other (foreign) table it references (`team`) and which column in that table is the key to define which row to connect (`id`). Feel free to experiment with it in **DB Browser for SQLite**. diff --git a/docs/tutorial/connect/read-connected-data.md b/docs/tutorial/connect/read-connected-data.md index 2868c69dd5..a8bafa2c5a 100644 --- a/docs/tutorial/connect/read-connected-data.md +++ b/docs/tutorial/connect/read-connected-data.md @@ -55,7 +55,7 @@ FROM hero, team WHERE hero.team_id = team.id ``` -/// info +/// note Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to. @@ -133,7 +133,7 @@ For each iteration in the `for` loop we get a tuple with an instance of the clas And in this `for` loop we assign them to the variable `hero` and the variable `team`. -/// info +/// note There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience. @@ -154,7 +154,7 @@ Now we can run the program and see how it shows us each hero with their correspo
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 @@ -249,7 +249,7 @@ This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if h The same way there's a `.where()` available when using `select()`, there's also a `.join()`. -And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we already declared what is the `foreign_key` when creating the models, we don't have to pass an `ON` part, it is inferred automatically: +And in SQLModel (actually SQLAlchemy), we don't have to pass an `ON` part when using `.join()`. It is inferred automatically because we already declared the `foreign_key` in the model definition: {* ./docs_src/tutorial/connect/select/tutorial002_py310.py ln[61:66] hl[63] *} @@ -262,7 +262,7 @@ And if we run it in the command line, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 @@ -385,7 +385,7 @@ And if we run it, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 @@ -413,7 +413,7 @@ And then why we didn't include `Hero` in the `.join()`. 🤔 In SQLModel (actually in SQLAlchemy), all these functions and tools try to **replicate** how it would be to work with the **SQL** language. -Remember that [`SELECT` defines the columns to get and `WHERE` how to filter them?](../where.md#select-and-where){.internal-link target=_blank}. +Remember that [`SELECT` defines the columns to get and `WHERE` how to filter them?](../where.md#select-and-where). This also applies here, but with `JOIN` and `ON`. @@ -436,7 +436,7 @@ If we run that, it would output:
```console -$ python app.py +$ uv run python app.py // Select only the hero data INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id @@ -463,7 +463,7 @@ And if we run that, it will output:
```console -$ python app.py +$ uv run python app.py // Select the hero and the team data INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters diff --git a/docs/tutorial/connect/remove-data-connections.md b/docs/tutorial/connect/remove-data-connections.md index d0a0267b1f..d133b19cb4 100644 --- a/docs/tutorial/connect/remove-data-connections.md +++ b/docs/tutorial/connect/remove-data-connections.md @@ -56,7 +56,7 @@ Running that in the command line will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 diff --git a/docs/tutorial/connect/update-data-connections.md b/docs/tutorial/connect/update-data-connections.md index 147c4e599d..bf168843a5 100644 --- a/docs/tutorial/connect/update-data-connections.md +++ b/docs/tutorial/connect/update-data-connections.md @@ -33,7 +33,7 @@ And a `hero` table: Some of these heroes are part of a team. -Now we'll see how to **update** those connections between rows tables. +Now we'll see how to **update** those connections between rows in tables. We will continue with the code we used to create some heroes, and we'll update them. @@ -56,7 +56,7 @@ Running that in the command line will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 😉 diff --git a/docs/tutorial/create-db-and-table-with-db-browser.md b/docs/tutorial/create-db-and-table-with-db-browser.md index 4c535df11c..90d4659b3c 100644 --- a/docs/tutorial/create-db-and-table-with-db-browser.md +++ b/docs/tutorial/create-db-and-table-with-db-browser.md @@ -40,7 +40,7 @@ Click the button New Database. -A dialog should show up. Go to the [project directory you created](../virtual-environments.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`. +A dialog should show up. Go to your project directory and save the file with a name of `database.db`. /// tip @@ -117,7 +117,7 @@ You will see again the same table we created. ## Create the Table again, with SQL -Now, to see how is it that SQL works, let's create the table again, but with SQL. +Now, to see how SQL works, let's create the table again, but with SQL. Click the Close Database button again. @@ -151,7 +151,7 @@ You will see the "execution finished successfully" message. -And if you go back to the Database Structure tab, you will see that you effectively created again the same table. +And if you go back to the Database Structure tab, you will see that you effectively created the same table again. @@ -159,16 +159,16 @@ And if you go back to the Database Structure tab, you will see that y I will keep showing you small bits of SQL through this tutorial. And you don't have to be a SQL expert to use **SQLModel**. -But if you are curious and want to get a quick overview of SQL, I recommend the visual documentation from SQLite, on SQL As Understood By SQLite. +But if you are curious and want to get a quick overview of SQL, I recommend the visual documentation from SQLite, on [SQL As Understood By SQLite](https://www.sqlite.org/lang.html). -You can start with `CREATE TABLE`. +You can start with [`CREATE TABLE`](https://www.sqlite.org/lang_createtable.html). -Of course, you can also go and take a full SQL course or read a book about SQL, but you don't need more than what I'll explain here on the tutorial to start being productive with **SQLModel**. 🤓 +Of course, you can also go and take a full SQL course or read a book about SQL, but you don't need more than what I'll explain here in the tutorial to start being productive with **SQLModel**. 🤓 ## Recap We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface. -We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correctly, to debug, etc. +We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database looks correct, to debug, etc. In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍 diff --git a/docs/tutorial/create-db-and-table.md b/docs/tutorial/create-db-and-table.md index bbd5532bae..2900ad7367 100644 --- a/docs/tutorial/create-db-and-table.md +++ b/docs/tutorial/create-db-and-table.md @@ -2,7 +2,7 @@ Now let's get to the code. 👩‍💻 -Make sure you are inside of your project directory and with your virtual environment activated as explained in [Virtual Environments](../virtual-environments.md#create-a-project){.internal-link target=_blank}. +Make sure you are inside of your project directory. We will: @@ -47,7 +47,7 @@ This class `Hero` **represents the table** for our heroes. And each instance we We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table. -/// info +/// note It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**. @@ -65,7 +65,7 @@ And the type of each of them will also be the type of table column: {* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,5:8] *} -Let's now see with more detail these field/column declarations. +Let's now see these field/column declarations in more detail. ### `None` Fields, Nullable Columns @@ -152,7 +152,7 @@ Creating the **engine** is very simple, just call `create_engine()` with a URL f {* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[1,14] *} -You should normally have a single **engine** object for your whole application and re-use it everywhere. +You should normally have a single **engine** object for your whole application and reuse it everywhere. /// tip @@ -176,7 +176,7 @@ SQLite supports a special database that lives all *in memory*. Hence, it's very {* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[11:12,14] *} -You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the SQLAlchemy documentation. +You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the [SQLAlchemy documentation](https://docs.sqlalchemy.org/en/14/core/engines.html). ### Engine Echo @@ -202,7 +202,7 @@ If you didn't know about SQLAlchemy before and are just learning **SQLModel**, y /// -You can read a lot more about the engine in the SQLAlchemy documentation. +You can read a lot more about the engine in the [SQLAlchemy documentation](https://docs.sqlalchemy.org/en/14/tutorial/engine.html). **SQLModel** defines its own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0). @@ -327,7 +327,7 @@ Put the code in a file `app.py` if you haven't already. /// tip -Remember to [activate the virtual environment](../virtual-environments.md#create-a-virtual-environment){.internal-link target=_blank} before running it. +Run it inside the project environment with `uv run`. /// @@ -337,7 +337,7 @@ Now run the program with Python: ```console // We set echo=True, so this will show the SQL code -$ python app.py +$ uv run python app.py // First, some boilerplate SQL that we are not that interested in @@ -366,7 +366,7 @@ INFO Engine COMMIT
-/// info +/// note I simplified the output above a bit to make it easier to read. @@ -390,7 +390,7 @@ In the example in the previous chapter we created the table using `TEXT` for som But in this output SQLAlchemy is using `VARCHAR` instead. Let's see what's going on. -Remember that [each SQL Database has some different variations in what they support?](../databases.md#sql-the-language){.internal-link target=_blank} +Remember that [each SQL Database has some different variations in what they support?](../databases.md#sql-the-language) This is one of the differences. Each database supports some particular **data types**, like `INTEGER` and `TEXT`. @@ -402,7 +402,7 @@ The same way, there are several possible types for storing strings. SQLite uses SQLAlchemy generates the SQL statements to create tables using `VARCHAR`, and then SQLite receives them, and internally converts them to `TEXT`s. -Additional to the difference between those two data types, some databases like MySQL require setting a maximum length for the `VARCHAR` types, for example `VARCHAR(255)` sets the maximum number of characters to 255. +In addition to the difference between those two data types, some databases like MySQL require setting a maximum length for the `VARCHAR` types, for example `VARCHAR(255)` sets the maximum number of characters to 255. To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort. @@ -467,7 +467,7 @@ The main purpose of the `__name__ == "__main__"` is to have some code that is ex
```console -$ python app.py +$ uv run python app.py // Something happens here ✨ ``` @@ -484,7 +484,7 @@ from app import Hero That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**". -The official name (in the Python docs) is "**Top-level script environment**". +The official name (in the [Python docs](https://docs.python.org/3/library/__main__.html)) is "**Top-level script environment**". /// @@ -497,7 +497,7 @@ If you run it with:
```console -$ python myapp.py +$ uv run python myapp.py // This will call create_db_and_tables() ``` @@ -538,9 +538,9 @@ if __name__ == "__main__": ...will **not** be executed. -/// info +/// note -For more information, check the official Python docs. +For more information, check [the official Python docs](https://docs.python.org/3/library/__main__.html). /// @@ -570,7 +570,7 @@ Review what each line does by clicking each number bubble in the code. 👆 ## Recap -We learnt how to use **SQLModel** to define how a table in the database should look like, and we created a database and a table using **SQLModel**. +We learnt how to use **SQLModel** to define what a table in the database should look like, and we created a database and a table using **SQLModel**. We also refactored the code to make it easier to reuse, share, and test later. diff --git a/docs/tutorial/delete.md b/docs/tutorial/delete.md index f6807fddab..4ec9e1f6d4 100644 --- a/docs/tutorial/delete.md +++ b/docs/tutorial/delete.md @@ -44,7 +44,7 @@ You can try that in **DB Browser for SQLite**: -Have in mind that `DELETE` is to delete entire **rows**, not single values in a row. +Keep in mind that `DELETE` is to delete entire **rows**, not single values in a row. If you want to "delete" a single value in a column while **keeping the row**, you would instead **update** the row as explained in the previous chapter, setting the specific value of the column in that row to `NULL` (to `None` in Python). @@ -67,7 +67,7 @@ That will print the same existing hero **Spider-Youngster**:
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -105,7 +105,7 @@ This commit after deleting the hero will generate this output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -138,7 +138,7 @@ This will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -167,7 +167,7 @@ This will execute some SQL in the database and output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -198,7 +198,7 @@ This will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 diff --git a/docs/tutorial/fastapi/index.md b/docs/tutorial/fastapi/index.md index 0d57ab9bd8..f837bb22b5 100644 --- a/docs/tutorial/fastapi/index.md +++ b/docs/tutorial/fastapi/index.md @@ -2,7 +2,7 @@ One of the use cases where **SQLModel** shines the most, and the main one why it was built, was to be combined with **FastAPI**. ✨ -FastAPI is a Python web framework for building web APIs created by the same author of SQLModel. FastAPI is also built on top of **Pydantic**. +[FastAPI](https://fastapi.tiangolo.com/) is a Python web framework for building web APIs created by the same [author](https://twitter.com/tiangolo) of SQLModel. FastAPI is also built on top of **Pydantic**. In this group of chapters we will see how to combine SQLModel **table models** representing tables in the SQL database as all the ones we have seen up to now, with **data models** that only represent data (which are actually just Pydantic models behind the scenes). @@ -14,4 +14,4 @@ By the end we will have a **simple** but **complete** web **API** to interact wi If you have never used FastAPI, maybe a good idea would be to go and study it a bit before continuing. -Just reading and trying the examples on the FastAPI main page should be enough, and it shouldn't take you more than **10 minutes**. +Just reading and trying the examples on the [FastAPI main page](https://fastapi.tiangolo.com/) should be enough, and it shouldn't take you more than **10 minutes**. diff --git a/docs/tutorial/fastapi/limit-and-offset.md b/docs/tutorial/fastapi/limit-and-offset.md index 1a464a664f..a10d86d6b0 100644 --- a/docs/tutorial/fastapi/limit-and-offset.md +++ b/docs/tutorial/fastapi/limit-and-offset.md @@ -8,7 +8,7 @@ So, we probably want to limit it. Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API. -/// info +/// note In many cases, this is also called **pagination**. @@ -32,13 +32,13 @@ So, to prevent it, we add additional validation to the `limit` query parameter, This way, a client can decide to take fewer heroes if they want, but not more. -/// info +/// note If you need to refresh how query parameters and their validation work, check out the docs in FastAPI: -* Query Parameters -* Query Parameters and String Validations -* Path Parameters and Numeric Validations +* [Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/) +* [Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/) +* [Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/) /// diff --git a/docs/tutorial/fastapi/multiple-models.md b/docs/tutorial/fastapi/multiple-models.md index 1bc045612b..d302d4028e 100644 --- a/docs/tutorial/fastapi/multiple-models.md +++ b/docs/tutorial/fastapi/multiple-models.md @@ -98,7 +98,7 @@ But we also want to have a `HeroCreate` for the data we want to receive when **c * `secret_name`, required * `age`, optional -And we want to have a `HeroPublic` with the `id` field, but this time with a type of `id: int`, instead of `id: int | None`, to make it clear that it will always have an `int` in responses **read** from the clients: +And we want to have a `HeroPublic` with the `id` field, but this time with a type of `id: int`, instead of `id: int | None`, to make it clear that it will always have an `int` in responses **read** by the clients: * `id`, required * `name`, required @@ -129,7 +129,7 @@ We will improve this code to avoid duplicating the fields, but for now we can co Let's now see how to use these new models in the FastAPI application. -Let's first check how is the process to create a hero now: +Let's first check how we create a hero now: {* ./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py ln[44:51] hl[44:45,47] *} @@ -167,7 +167,7 @@ This will validate that all the data that we promised is there and will remove a This filtering could be very important and could be a very good security feature, for example, to make sure you filter private data, hashed passwords, etc. -You can read more about it in the FastAPI docs about Response Model. +You can read more about it in the [FastAPI docs about Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). /// @@ -235,7 +235,7 @@ And those inherited fields will also be in the **autocompletion** and **inline e ### Columns and Inheritance with Multiple Models -Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`. +Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`. {* ./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py ln[5:12] hl[6,8,11] *} diff --git a/docs/tutorial/fastapi/read-one.md b/docs/tutorial/fastapi/read-one.md index 03a65a0a2d..fec0f711f0 100644 --- a/docs/tutorial/fastapi/read-one.md +++ b/docs/tutorial/fastapi/read-one.md @@ -8,9 +8,9 @@ Let's add a new *path operation* to read one single hero. We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`. -/// info +/// note -If you need to refresh how *path parameters* work, including their data validation, check the FastAPI docs about Path Parameters. +If you need to refresh how *path parameters* work, including their data validation, check the [FastAPI docs about Path Parameters](https://fastapi.tiangolo.com/tutorial/path-params/). /// @@ -24,7 +24,7 @@ For example, to get the hero with ID `2` we would send a `GET` request to: ## Handling Errors -Then, because FastAPI already takes care of making sure that the `hero_id` is an actual integer, we can use it directly with `Hero.get()` to try and get one hero by that ID. +Then, because FastAPI already takes care of making sure that the `hero_id` is an actual integer, we can use it directly with `session.get()` to try and get one hero by that ID. But if the integer is not the ID of any hero in the database, it will not find anything, and the variable `hero` will be `None`. diff --git a/docs/tutorial/fastapi/relationships.md b/docs/tutorial/fastapi/relationships.md index 8c69b51776..697011de10 100644 --- a/docs/tutorial/fastapi/relationships.md +++ b/docs/tutorial/fastapi/relationships.md @@ -46,7 +46,7 @@ And the same way, we declared the `TeamPublic` with only the same base fields of {* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[5:7,20:21,29:34,43:44] hl[5:7,20:21,29:34,43:44] *} -Now, remember that FastAPI uses the `response_model` to validate and **filter** the response data? +Now, remember that [FastAPI uses the `response_model` to validate and **filter** the response data](https://fastapi.tiangolo.com/tutorial/response-model/)? In this case, we used `response_model=TeamPublic` and `response_model=HeroPublic`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**: diff --git a/docs/tutorial/fastapi/response-model.md b/docs/tutorial/fastapi/response-model.md index f9214332c6..029c371ad8 100644 --- a/docs/tutorial/fastapi/response-model.md +++ b/docs/tutorial/fastapi/response-model.md @@ -8,7 +8,7 @@ Up to now, with the code we have used, the API docs know the data the clients ha Interactive API docs UI -This interactive docs UI is powered by Swagger UI, and what Swagger UI does is to read a big JSON content that defines the API with all the data schemas (data shapes) using the standard OpenAPI, and showing it in that nice UI. +This interactive docs UI is powered by [Swagger UI](https://github.com/swagger-api/swagger-ui), and what Swagger UI does is to read a big JSON content that defines the API with all the data schemas (data shapes) using the standard [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md), and showing it in that nice UI. FastAPI automatically **generates that OpenAPI** for Swagger UI to read it. @@ -18,7 +18,7 @@ And it generates it **based on the code you write**, using the Pydantic models ( But up to now, the API docs UI doesn't know the schema of the *responses* our app sends back. -You can see that there's a possible "Successful Response" with a code `200`, but we have no idea how the response data would look like. +You can see that there's a possible "Successful Response" with a code `200`, but we have no idea what the response data would look like. API docs UI without response data schemas @@ -48,7 +48,7 @@ FastAPI will do data validation and filtering of the response with this `respons So this works like a contract between our application and the client. -You can read more about it in the FastAPI docs about `response_model`. +You can read more about it in the [FastAPI docs about `response_model`](https://fastapi.tiangolo.com/tutorial/response-model/). ## New API Docs UI @@ -62,17 +62,17 @@ The clients will know what data they should expect. The most visible advantage of using the `response_model` is that it shows up in the API docs UI. -But there are other advantages, like that FastAPI will do automatic data validation and filtering of the response data using this model. +But there are other advantages, like that FastAPI will do automatic [data validation and filtering](https://fastapi.tiangolo.com/tutorial/response-model/) of the response data using this model. -Additionally, because the schemas are defined in using a standard, there are many tools that can take advantage of this. +Additionally, because the schemas are defined using a standard, there are many tools that can take advantage of this. For example, client generators, that can automatically create the code necessary to talk to your API in many languages. -/// info +/// note If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema. -You can read about all that in the FastAPI docs - First Steps. +You can read about all that in the [FastAPI docs - First Steps](https://fastapi.tiangolo.com/tutorial/first-steps/#openapi). /// diff --git a/docs/tutorial/fastapi/session-with-dependency.md b/docs/tutorial/fastapi/session-with-dependency.md index e81e9e6745..11c6a4c3a4 100644 --- a/docs/tutorial/fastapi/session-with-dependency.md +++ b/docs/tutorial/fastapi/session-with-dependency.md @@ -8,7 +8,7 @@ Up to now, we have been creating a session in each *path operation*, in a `with` {* ./docs_src/tutorial/fastapi/delete/tutorial001_py310.py ln[48:55] hl[50] *} -That's perfectly fine, but in many use cases we would want to use FastAPI Dependencies, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*. +That's perfectly fine, but in many use cases we would want to use [FastAPI Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/), for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*. These dependencies are also very useful during **testing**, because we can **easily replace them**, and then, for example, use a new database for our tests, or put some data before the tests, etc. @@ -38,7 +38,7 @@ Here we are passing the parameter `session` that has a "default value" of `Depen Python would normally complain about that, but we can use the initial "parameter" `*,` to mark all the rest of the parameters as "keyword only", which solves the problem. -You can read more about it in the FastAPI documentation Path Parameters and Numeric Validations - Order the parameters as you need, tricks +You can read more about it in the FastAPI documentation [Path Parameters and Numeric Validations - Order the parameters as you need, tricks](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks) /// @@ -86,4 +86,4 @@ You just learned how to use **FastAPI dependencies** to handle the database sess And you will see how much these dependencies can help the more you work with FastAPI, to handle **permissions**, **authentication**, resources like database **sessions**, etc. 🚀 -If you want to learn more about dependencies, checkout the FastAPI docs about Dependencies. +If you want to learn more about dependencies, check out the [FastAPI docs about Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/). diff --git a/docs/tutorial/fastapi/simple-hero-api.md b/docs/tutorial/fastapi/simple-hero-api.md index 79cf075e1b..07417aa79c 100644 --- a/docs/tutorial/fastapi/simple-hero-api.md +++ b/docs/tutorial/fastapi/simple-hero-api.md @@ -8,12 +8,12 @@ The first step is to install FastAPI. FastAPI is the framework to create the **web API**. -Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with: +Add them to your project:
```console -$ pip install fastapi "uvicorn[standard]" +$ uv add fastapi "uvicorn[standard]" ---> 100% ``` @@ -40,9 +40,9 @@ But here we will make sure we don't share the same **session** in more than one And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads. -/// info +/// note -That's enough information for now, you can read more about it in the FastAPI docs for `async` and `await`. +That's enough information for now, you can read more about it in the [FastAPI docs for `async` and `await`](https://fastapi.tiangolo.com/async/). The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe. @@ -68,9 +68,9 @@ This should be called only once at startup, not before every request, so we put ## Create Heroes *Path Operation* -/// info +/// note -If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the FastAPI First Steps docs. +If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the [FastAPI First Steps docs](https://fastapi.tiangolo.com/tutorial/first-steps/). /// @@ -80,19 +80,19 @@ It will be called when a user sends a request with a `POST` **operation** to the {* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:37] hl[31:32] *} -/// info +/// note -If you need a refresher on some of those concepts, checkout the FastAPI documentation: +If you need a refresher on some of those concepts, check out the FastAPI documentation: -* First Steps -* Path Parameters - Data Validation and Data Conversion -* Request Body +* [First Steps](https://fastapi.tiangolo.com/tutorial/first-steps/) +* [Path Parameters - Data Validation and Data Conversion](https://fastapi.tiangolo.com/tutorial/path-params/) +* [Request Body](https://fastapi.tiangolo.com/tutorial/body/) /// ## The **SQLModel** Advantage -Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨ +Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shines. ✨ Here we use the **same** class model to define the **request body** that will be received by our API. @@ -120,7 +120,7 @@ When a client sends a request to the **path** `/heroes/` with a `GET` HTTP **ope ## One Session per Request -Remember that we should use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session? +Remember that we should use a SQLModel **session** per group of operations and if we need other unrelated operations we should use a different session? Here it is much more obvious. @@ -132,7 +132,7 @@ But we would **never want to *share* the same session** among different requests In this simple example, we just create the new sessions manually in the **path operation functions**. -In future examples later we will use a FastAPI Dependency to get the **session**, being able to share it with other dependencies and being able to replace it during testing. 🤓 +In future examples later we will use a [FastAPI Dependency](https://fastapi.tiangolo.com/tutorial/dependencies/) to get the **session**, being able to share it with other dependencies and being able to replace it during testing. 🤓 ## Run the **FastAPI** Server in Development Mode @@ -145,16 +145,16 @@ Then run it with the `fastapi` CLI,
```console -$ fastapi dev main.py +$ uv run fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-/// info +/// note -The `fastapi` command uses Uvicorn underneath. +The `fastapi` command uses [Uvicorn](https://www.uvicorn.org/) underneath. /// @@ -162,14 +162,14 @@ When you use `fastapi dev` it starts Uvicorn with the option to reload automatic ## Run the **FastAPI** Server in Production Mode -The development mode should not be used in production, as it includes automatic reload by default it consumes much more resources than necessary, and it would be more error prone, etc. +The development mode should not be used in production: it includes automatic reload by default, which consumes far more resources than necessary and is more error-prone. For production, use `fastapi run` instead of `fastapi dev`:
```console -$ fastapi run main.py +$ uv run fastapi run main.py INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) ``` @@ -180,7 +180,7 @@ $ fastapi run main.py Now you can go to that URL in your browser `http://127.0.0.1:8000`. We didn't create a *path operation* for the root path `/`, so that URL alone will only show a "Not Found" error... that "Not Found" error is produced by your FastAPI application. -But you can go to the **automatically generated interactive API documentation** at the path `/docs`: http://127.0.0.1:8000/docs. ✨ +But you can go to the **automatically generated interactive API documentation** at the path `/docs`: [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). ✨ You will see that this **automatic API docs UI** has the *paths* that we defined above with their *operations*, and that it already knows the shape of the data that the **path operations** will receive: diff --git a/docs/tutorial/fastapi/teams.md b/docs/tutorial/fastapi/teams.md index 4f07fb1981..eafe4659a6 100644 --- a/docs/tutorial/fastapi/teams.md +++ b/docs/tutorial/fastapi/teams.md @@ -48,7 +48,7 @@ These are equivalent and very similar to the **path operations** for the **heroe {* ./docs_src/tutorial/fastapi/teams/tutorial001_py310.py ln[135:188] hl[135:141,144:152,155:160,163:178,181:188] *} -## Using Relationships Attributes +## Using Relationship Attributes Up to this point, we are actually not using the **relationship attributes**, but we could access them in our code. diff --git a/docs/tutorial/fastapi/tests.md b/docs/tutorial/fastapi/tests.md index f7fd92c9cb..7ec9bd9450 100644 --- a/docs/tutorial/fastapi/tests.md +++ b/docs/tutorial/fastapi/tests.md @@ -18,7 +18,7 @@ Now we will see how useful it is to have this session dependency. ✨ ## File Structure -Now we will have a Python project with multiple files, one file `main.py` with all the application, and one file `test_main.py` with the tests, with the same ideas from [Code Structure and Multiple Files](../code-structure.md){.internal-link target=_blank}. +Now we will have a Python project with multiple files, one file `main.py` with all the application, and one file `test_main.py` with the tests, with the same ideas from [Code Structure and Multiple Files](../code-structure.md). The file structure is: @@ -32,16 +32,16 @@ The file structure is: ## Testing FastAPI Applications -If you haven't done testing in FastAPI applications, first check the FastAPI docs about Testing. +If you haven't done testing in FastAPI applications, first check the [FastAPI docs about Testing](https://fastapi.tiangolo.com/tutorial/testing/). Then, we can continue here, the first step is to install the dependencies, `requests` and `pytest`. -Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with: +Add them to your project:
```console -$ pip install requests pytest +$ uv add requests pytest ---> 100% ``` @@ -50,7 +50,7 @@ $ pip install requests pytest ## Basic Tests Code -Let's start with a simple test, with just the basic test code we need the check that the **FastAPI** application is creating a new hero correctly. +Let's start with a simple test, with just the basic test code we need to check that the **FastAPI** application is creating a new hero correctly. ```{ .python .annotate } {!./docs_src/tutorial/fastapi/app_testing/tutorial001_py310/test_main_001.py[ln:1-7]!} @@ -144,9 +144,9 @@ Here we create all the tables in the testing database with: SQLModel.metadata.create_all(engine) ``` -But remember that [Order Matters](../create-db-and-table.md#sqlmodel-metadata-order-matters){.internal-link target=_blank} and we need to make sure all the **SQLModel** models are already defined and **imported** before calling `.create_all()`. +But remember that [Order Matters](../create-db-and-table.md#sqlmodel-metadata-order-matters) and we need to make sure all the **SQLModel** models are already defined and **imported** before calling `.create_all()`. -In this case, it all works for a little subtlety that deserves some attention. +In this case, it all works thanks to a little subtlety that deserves some attention. Because we import something, *anything*, from `.main`, the code in `.main` will be executed, including the definition of the **table models**, and that will automatically register them in `SQLModel.metadata`. @@ -156,13 +156,13 @@ That way, when we call `.create_all()` all the **table models** are correctly re Now we are not using the production database. Instead, we use a **new testing database** with the `testing.db` file, which is great. -But SQLite also supports having an **in memory** database. This means that all the database is only in memory, and it is never saved in a file on disk. +But SQLite also supports having an **in memory** database. This means that the whole database is only in memory, and it is never saved in a file on disk. After the program terminates, **the in-memory database is deleted**, so it wouldn't help much for a production database. But **it works great for testing**, because it can be quickly created before each test, and quickly removed after each test. ✅ -And also, because it never has to write anything to a file and it's all just in memory, it will be even faster than normally. 🏎 +And also, because it never has to write anything to a file and it's all just in memory, it will be even faster than a disk-based database. 🏎 /// details | Other alternatives and ideas 👀 @@ -214,7 +214,7 @@ Do we really have to duplicate all that for **each test**? No, we can do better! We are using **pytest** to run the tests. And pytest also has a very similar concept to the **dependencies in FastAPI**. -/// info +/// note In fact, pytest was one of the things that inspired the design of the dependencies in FastAPI. @@ -222,15 +222,15 @@ In fact, pytest was one of the things that inspired the design of the dependenci It's a way for us to declare some **code that should be run before** each test and **provide a value** for the test function (that's pretty much the same as FastAPI dependencies). -In fact, it also has the same trick of allowing to use `yield` instead of `return` to provide the value, and then **pytest** makes sure that the code after `yield` is executed *after* the function with the test is done. +In fact, it also has the same trick of allowing you to use `yield` instead of `return` to provide the value, and then **pytest** makes sure that the code after `yield` is executed *after* the function with the test is done. In pytest, these things are called **fixtures** instead of *dependencies*. -Let's use these **fixtures** to improve our code and reduce de duplicated boilerplate for the next tests. +Let's use these **fixtures** to improve our code and reduce the duplicated boilerplate for the next tests. ## Pytest Fixtures -You can read more about them in the pytest docs for fixtures, but I'll give you a short example for what we need here. +You can read more about them in the [pytest docs for fixtures](https://docs.pytest.org/en/6.2.x/fixture.html), but I'll give you a short example for what we need here. Let's see the first code example with a fixture: @@ -253,7 +253,7 @@ Check out the number bubbles to see what is done by each line of code. But apart from the way we declare them and how we tell the framework that we want to have them in the function, they **work in a very similar way**. -Now we create lot's of tests and re-use that same fixture in all of them, saving us that **boilerplate code**. +Now we create lots of tests and reuse that same fixture in all of them, saving us that **boilerplate code**. **pytest** will make sure to run them right before (and finish them right after) each test function. So, each test function will actually have its own database, engine, and session. @@ -302,13 +302,13 @@ Let's add some more tests: /// tip -It's always **good idea** to not only test the normal case, but also that **invalid data**, **errors**, and **corner cases** are handled correctly. +It's always a **good idea** to not only test the normal case, but also that **invalid data**, **errors**, and **corner cases** are handled correctly. That's why we add these two extra tests here. /// -Now, any additional test functions can be as **simple** as the first one, they just have to **declare the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎 +Now, any additional test functions can be as **simple** as the first one, they just have to **declare the `client` parameter** to get the `TestClient` **fixture** with all the database stuff set up. Nice! 😎 ## Why Two Fixtures @@ -353,7 +353,7 @@ Now we can run the tests with `pytest` and see the results:
```console -$ pytest +$ uv run pytest ============= test session starts ============== platform linux -- Python 3.10.0, pytest-7.4.4, pluggy-1.5.0 diff --git a/docs/tutorial/fastapi/update-extra-data.md b/docs/tutorial/fastapi/update-extra-data.md index 95843144ae..bff57784f6 100644 --- a/docs/tutorial/fastapi/update-extra-data.md +++ b/docs/tutorial/fastapi/update-extra-data.md @@ -10,7 +10,7 @@ This is particularly useful when you need to **generate some data** in your code Let's imagine that each hero in our system also has a **password**. -We should never store the password in plain text in the database, we should only stored a **hashed version** of it. +We should never store the password in plain text in the database, we should only store a **hashed version** of it. "**Hashing**" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish. @@ -26,7 +26,7 @@ So, the thief won't be able to try to use that password in another system (as ma /// tip -You could use passlib to hash passwords. +You could use [passlib](https://passlib.readthedocs.io/en/stable/) to hash passwords. In this example we will use a fake hashing function to focus on the data changes. 🤡 diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index dd61e8597e..281f99081c 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -4,9 +4,9 @@ In this tutorial you will learn how to use **SQLModel**. ## Type hints -If you need a refresher about how to use Python type hints (type annotations), check FastAPI's Python types intro. +If you need a refresher about how to use Python type hints (type annotations), check [FastAPI's Python types intro](https://fastapi.tiangolo.com/python-types/). -You can also check the mypy cheat sheet. +You can also check the [mypy cheat sheet](https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html). **SQLModel** uses type annotations for everything, this way you can use a familiar Python syntax and get all the editor support possible, with autocompletion and in-editor error checking. diff --git a/docs/tutorial/indexes.md b/docs/tutorial/indexes.md index 459ee8ce34..2a411671a6 100644 --- a/docs/tutorial/indexes.md +++ b/docs/tutorial/indexes.md @@ -52,7 +52,7 @@ You know that `d` from `database` comes before `f`. So it has to be **before** t -Now you have a **small section of dictionary** to search (only a **quarter** of dictionary can have your word). You take that **quarter** of the pages at the start of the dictionary that can contain your word, and open it in the middle of that section. Maybe you arrive at the letter `c`. +Now you have a **small section of dictionary** to search (only a **quarter** of the dictionary can have your word). You take that **quarter** of the pages at the start of the dictionary that can contain your word, and open it in the middle of that section. Maybe you arrive at the letter `c`. @@ -90,7 +90,7 @@ Now let's imagine you are reading a technical book. For example, with several to This book might have a **book index**: a section in the book that has some **names of topics covered** and the **page numbers** in the book where you can read about them. And the topic names are **sorted** in alphabetic order, pretty much like a dictionary (a book with words, as in the previous example). -In this case, you can open that book in the end (or in the beginning) to find the **book index** section, it would have only a few pages. And then, you can do the same process as with the **dictionary** example above. +In this case, you can open that book at the end (or at the beginning) to find the **book index** section, it would have only a few pages. And then, you can do the same process as with the **dictionary** example above. Open the index, and after **5 or 10 steps**, quickly find the topic "**database**" with the page numbers where that is covered, for example "page 253 in Chapter 5". Now you used the dictionary technique to find the **topic**, and that topic gave you a **page number**. @@ -267,7 +267,7 @@ We use the same `Field()` again as we did before, and set `index=True`. That's i Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field. -/// info +/// note SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you. @@ -294,7 +294,7 @@ If you run the program now, you will see an output like this:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 diff --git a/docs/tutorial/insert.md b/docs/tutorial/insert.md index e2d9a4637f..4f71d7873c 100644 --- a/docs/tutorial/insert.md +++ b/docs/tutorial/insert.md @@ -2,7 +2,7 @@ Now that we have a database and a table, we can start adding data. -Here's a reminder of how the table would look like, this is the data we want to add: +Here's a reminder of what the table would look like, this is the data we want to add: @@ -39,7 +39,7 @@ This is the code we had to create the database and table, nothing new here: //// -Now that we can create the database and the table, we will continue from this point and add more code on the same file to create the data. +Now that we can create the database and the table, we will continue from this point and add more code in the same file to create the data. ## Create Data with SQL @@ -68,9 +68,9 @@ It means, more or less: > * `"Deadpond"` > * `"Dive Wilson"` -### Try it in DB Explorer for SQLite +### Try it in DB Browser for SQLite -You can try that SQL statement in **DB Explorer for SQLite**. +You can try that SQL statement in **DB Browser for SQLite**. Make sure to open the same database we already created by clicking Open Database and selecting the same `database.db` file. @@ -159,7 +159,7 @@ In fact, the **session** needs and uses an **engine**. For example, if we have a web application, we would normally have a single **session** per request. -We would re-use the same **engine** in all the code, everywhere in the application (shared by all the requests). But for each request, we would create and use a new **session**. And once the request is done, we would close the session. +We would reuse the same **engine** in all the code, everywhere in the application (shared by all the requests). But for each request, we would create and use a new **session**. And once the request is done, we would close the session. The first step is to import the `Session` class: @@ -193,7 +193,7 @@ And once we are ready, we can **commit** those changes, and then the **session** This makes the interactions with the database more efficient (plus some extra benefits). -/// info | Technical Details +/// note | Technical Details The session will create a new transaction and execute all the SQL code in that transaction. @@ -251,7 +251,7 @@ Because we created the **engine** with `echo=True`, it will print out all the SQ
```console -$ python app.py +$ uv run python app.py // Some boilerplate, checking that the hero table already exists INFO Engine BEGIN (implicit) INFO Engine PRAGMA main.table_info("hero") @@ -335,7 +335,7 @@ Review what each line does by clicking each number bubble in the code. 👆 /// -You can now put it in a `app.py` file and run it with Python. And you will see an output like the one shown above. +You can now put it in an `app.py` file and run it with Python. And you will see an output like the one shown above. After that, if you open the database with **DB Browser for SQLite**, you will see the data you just created in the Browse Data tab: diff --git a/docs/tutorial/limit-and-offset.md b/docs/tutorial/limit-and-offset.md index 90bb167f26..f00e416f48 100644 --- a/docs/tutorial/limit-and-offset.md +++ b/docs/tutorial/limit-and-offset.md @@ -43,7 +43,7 @@ If we run it on the command line, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -95,7 +95,7 @@ We can use `.offset()`: The way this works is that the special **select** object we get from `select()` has methods like `.where()`, `.offset()` and `.limit()`. -Each of those methods applies the change in the internal special select statement object, and also **return the same object**, this way, we can continue using more methods on it, like in the example above that we use both `.offset()` and `.limit()`. +Each of those methods applies the change in the internal special select statement object, and also **returns the same object**, this way, we can continue using more methods on it, like in the example above that we use both `.offset()` and `.limit()`. **Offset** means "skip this many rows", and as we want to skip the ones we already saw, the first three, we use `.offset(3)`. @@ -106,7 +106,7 @@ Now we can run the program on the command line, and it will output:
```console -$python app.py +$ python app.py // Previous output omitted 🙈 @@ -147,7 +147,7 @@ And if we run it in the command line, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -196,7 +196,7 @@ But we are starting to include after an offset of 1 (so we don't count the first
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 diff --git a/docs/tutorial/many-to-many/create-data.md b/docs/tutorial/many-to-many/create-data.md index 3c8c0cfa6a..c275ba217d 100644 --- a/docs/tutorial/many-to-many/create-data.md +++ b/docs/tutorial/many-to-many/create-data.md @@ -1,6 +1,6 @@ # Create Data with Many-to-Many Relationships -Let's continue from where we left and create some data. +Let's continue from where we left off and create some data. We'll create data for this same **many-to-many** relationship with a link table: @@ -43,7 +43,7 @@ If we run the program from the command line, it would output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -61,7 +61,7 @@ INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?) INFO Engine [generated in 0.00037s] ('Z-Force', 'Sister Margaret's Bar') INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?) INFO Engine [cached since 0.001239s ago] ('Preventers', 'Sharp Tower') -// Insert the link data last, to be able to re-use the created IDs +// Insert the link data last, to be able to reuse the created IDs INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?) INFO Engine [generated in 0.00026s] ((2, 3), (1, 1), (2, 1), (2, 2)) // Commit and save the data in the database @@ -119,7 +119,7 @@ INFO Engine [cached since 0.002739s ago] (3,) // Print Spider-Boy's teams, just one, but still a list Spider-Boy Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')] -// Automatic roll back any previous automatic transaction, at the end of the with block +// Automatically roll back any previous automatic transaction, at the end of the with block INFO Engine ROLLBACK ``` diff --git a/docs/tutorial/many-to-many/create-models-with-link.md b/docs/tutorial/many-to-many/create-models-with-link.md index af6563e1ab..34fbd3b2f9 100644 --- a/docs/tutorial/many-to-many/create-models-with-link.md +++ b/docs/tutorial/many-to-many/create-models-with-link.md @@ -24,7 +24,7 @@ And **both fields are primary keys**. We hadn't used this before. 🤓 ## Team Model -Let's see the `Team` model, it's almost identical as before, but with a little change: +Let's see the `Team` model, it's almost identical to the previous version, but with a little change: {* ./docs_src/tutorial/many_to_many/tutorial001_py310.py ln[9:14] hl[14] *} @@ -71,7 +71,7 @@ If you run the code in the command line, it would output:
```console -$ python app.py +$ uv run python app.py // Boilerplate omitted 😉 @@ -98,7 +98,7 @@ CREATE TABLE hero ( INFO Engine [no key 0.00016s] () INFO Engine -// Our shinny new link table ✨ +// Our shiny new link table ✨ CREATE TABLE heroteamlink ( team_id INTEGER, hero_id INTEGER, diff --git a/docs/tutorial/many-to-many/index.md b/docs/tutorial/many-to-many/index.md index 5cb3067e4a..908e028001 100644 --- a/docs/tutorial/many-to-many/index.md +++ b/docs/tutorial/many-to-many/index.md @@ -109,7 +109,7 @@ Specifically, the new link table `heroteamlink` would be:
-/// info +/// note Other names used for this **link table** are: @@ -128,11 +128,11 @@ I'm using the term "link table" because it's short, doesn't collide with other t ## Link Primary Key -Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key){.internal-link target=_blank} that **uniquely identifies** the row in that table? +Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key) that **uniquely identifies** the row in that table? Now, what is the **primary key** in this table? -How to we identify each unique row? +How do we identify each unique row? Should we add another column just to be the **primary key** of this link table? Nope! We don't have to do that. 👌 diff --git a/docs/tutorial/many-to-many/link-with-extra-fields.md b/docs/tutorial/many-to-many/link-with-extra-fields.md index 7c7756bcd2..b44dbb5ebe 100644 --- a/docs/tutorial/many-to-many/link-with-extra-fields.md +++ b/docs/tutorial/many-to-many/link-with-extra-fields.md @@ -39,7 +39,7 @@ The new **relationship attributes** have their own `back_populates` pointing to * `team`: has `back_populates="hero_links"`, because in the `Team` model, the attribute will contain the links to the **team's heroes**. * `hero`: has `back_populates="team_links"`, because in the `Hero` model, the attribute will contain the links to the **hero's teams**. -/// info +/// note In SQLAlchemy this is called an Association Object or Association Model. @@ -59,7 +59,7 @@ We no longer have the `heroes` relationship attribute, and instead we have the n The same with the `Hero` model. -We change the `teams` relationship attribute for `team_links`: +We change the `teams` relationship attribute to `team_links`: {* ./docs_src/tutorial/many_to_many/tutorial003_py310.py ln[21:27] hl[27] *} @@ -71,7 +71,7 @@ But now we create the **explicit link models** manually, pointing to their hero {* ./docs_src/tutorial/many_to_many/tutorial003_py310.py ln[40:79] hl[58:67,69:72] *} -We are just adding the link model instances to the session, because the link model instances are connected to the heroes and teams, they will be also automatically included in the session when we commit. +We are just adding the link model instances to the session, because the link model instances are connected to the heroes and teams, they will also be automatically included in the session when we commit. ## Run the Program @@ -80,7 +80,7 @@ Now, if we run the program, it will show almost the same output as before, becau
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -176,7 +176,7 @@ If we run that program, we will see the output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -257,7 +257,7 @@ And if we run the program now, it will output:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 diff --git a/docs/tutorial/many-to-many/update-remove-relationships.md b/docs/tutorial/many-to-many/update-remove-relationships.md index ebc9ba3a85..a2a15aa645 100644 --- a/docs/tutorial/many-to-many/update-remove-relationships.md +++ b/docs/tutorial/many-to-many/update-remove-relationships.md @@ -12,7 +12,7 @@ Let's now create a function `update_heroes()`. We'll get **Spider-Boy** and the **Z-Force** team. -As you already know how these goes, I'll use the **short version** and get the data in a single Python statement. +As you already know how this goes, I'll use the **short version** and get the data in a single Python statement. And because we are now using `select()`, we also have to import it. @@ -38,7 +38,7 @@ So we don't have to call `session.refresh()`. /// -We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm. +We then commit the change, refresh, and print the updated **Spider-Boy**'s teams to confirm. Notice that we only `add` **Z-Force** to the session, then we commit. @@ -53,7 +53,7 @@ You can confirm it's all working by running the program in the command line:
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -81,7 +81,7 @@ Updated Spider-Boy's Teams: [ Team(id=1, name='Z-Force', headquarters='Sister Margaret's Bar') ] -// Automatically refresh the data while accessing the attribute .heores +// Automatically refresh the data while accessing the attribute .heroes INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age FROM hero, heroteamlink WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id @@ -115,7 +115,7 @@ In this case, we use the method `.remove()`, that takes an item and removes it f {* ./docs_src/tutorial/many_to_many/tutorial002_py310.py ln[72:91] hl[86:88,90:91] *} -And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even add `hero_spider_boy`. +And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even adding `hero_spider_boy`. This still works because by updating the teams in `hero_spider_boy`, because they are synchronized with `back_populates`, the changes are also reflected in `team_z_force`, so it also has changes to be saved in the DB (that **Spider-Boy** was removed). @@ -130,7 +130,7 @@ To confirm that this last part worked, you can run the program again, it will ou
```console -$ python app.py +$ uv run python app.py // Previous output omitted 🙈 @@ -184,4 +184,4 @@ INFO Engine ROLLBACK Updating and removing many-to-many relationships is quite straightforward after setting up the **link model** and the relationship attributes. -You can just use common list operation. 🚀 +You can just use common list operations. 🚀 diff --git a/docs/tutorial/one.md b/docs/tutorial/one.md index 63e6a9e448..ad181247f0 100644 --- a/docs/tutorial/one.md +++ b/docs/tutorial/one.md @@ -47,7 +47,7 @@ If we run it in the command line it would output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -78,7 +78,7 @@ When we run it in the command line it will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -88,7 +88,7 @@ FROM hero WHERE hero.age <= ? INFO Engine [no key 0.00021s] (35,) -// Now rows found, first is None +// No rows found, first is None Hero: None ``` @@ -111,7 +111,7 @@ If we run it once, it will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -134,7 +134,7 @@ So, running it again, without first deleting the file `database.db` will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -175,7 +175,7 @@ This is what we would get if we run it in the command line:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -219,14 +219,14 @@ As selecting a single row by its Id column with the **primary key** is a common {* ./docs_src/tutorial/one/tutorial008_py310.py ln[42:45] hl[44] *} -`session.get(Hero, 1)` is an equivalent to creating a `select()`, then filtering by Id using `.where()`, and then getting the first item with `.first()`. +`session.get(Hero, 1)` is equivalent to creating a `select()`, then filtering by Id using `.where()`, and then getting the first item with `.first()`. If you run it, it will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -244,7 +244,7 @@ Hero: secret_name='Dive Wilson' age=None id=1 name='Deadpond' ## Select by Id with `.get()` with No Data -`.get()` behaves similar to `.first()`, if there's no data it will simply return `None` (instead of raising an error): +`.get()` behaves similarly to `.first()`, if there's no data it will simply return `None` (instead of raising an error): {* ./docs_src/tutorial/one/tutorial009_py310.py ln[42:45] hl[44] *} @@ -253,7 +253,7 @@ Running that will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 diff --git a/docs/tutorial/relationship-attributes/back-populates.md b/docs/tutorial/relationship-attributes/back-populates.md index 9bdcbf0c28..63082c6132 100644 --- a/docs/tutorial/relationship-attributes/back-populates.md +++ b/docs/tutorial/relationship-attributes/back-populates.md @@ -26,7 +26,7 @@ Let's see how that works by writing an **incomplete** version first, without `ba Now, we will get the **Spider-Boy** hero and, *independently*, the **Preventers** team using two `select`s. -As you already know how this works, I won't separate that in a select `statement`, `results`, etc. Let's use the shorter form in a single call: +As you already know how this works, I won't separate that into a select `statement`, `results`, etc. Let's use the shorter form in a single call: {* ./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py ln[103:111] hl[105:107,109:111] *} @@ -66,7 +66,7 @@ Now let's update **Spider-Boy**, removing him from the team by setting `hero_spi The first important thing is, we *haven't committed* the hero yet, so accessing the list of heroes would not trigger an automatic refresh. -But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥 +But in our code, at this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥 /// tip diff --git a/docs/tutorial/relationship-attributes/cascade-delete-relationships.md b/docs/tutorial/relationship-attributes/cascade-delete-relationships.md index 3b6fe9156c..c87c137a57 100644 --- a/docs/tutorial/relationship-attributes/cascade-delete-relationships.md +++ b/docs/tutorial/relationship-attributes/cascade-delete-relationships.md @@ -8,7 +8,7 @@ Should their `team_id` instead be set to `NULL` in the database? Let's see how to configure that with **SQLModel**. -/// info +/// note This feature, including `cascade_delete`, `ondelete`, and `passive_deletes`, is available since SQLModel version `0.0.21`. @@ -30,7 +30,7 @@ Let's say that we have these **teams** and **heroes**. | id | name | secret_name | age | team_id | | ---- | --------------- | ---------------- | ---- | ------- | -| 1 | Deadpond | Dive WIlson | | 1 | +| 1 | Deadpond | Dive Wilson | | 1 | | 2 | Rusty-Man | Tommy Sharp | 48 | 2 | | 3 | Spider-Boy | Pedro Parqueador | | 2 | | 4 | Black Lion | Trevor Challa | 35 | 3 | @@ -129,7 +129,7 @@ The `cascade_delete` parameter is set in the `Relationship()`, on the model that Setting `cascade_delete=True` in the `Relationship()` will configure SQLAlchemy to use `cascade="all, delete-orphan"`, which is the most common and useful configuration when wanting to cascade deletes. -You can read more about it in the SQLAlchemy docs. +You can read more about it in the [SQLAlchemy docs](https://docs.sqlalchemy.org/en/20/orm/cascades.html). /// @@ -236,7 +236,7 @@ We can confirm everything is working by running the program.
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -378,7 +378,7 @@ Let's confirm it all works by running the program now:
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -438,11 +438,11 @@ If you know your database would be able to correctly handle the deletes or updat To be able to test this out with SQLite, we first need to enable foreign key support. -{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py ln[30:33] hl[33] *} +{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py ln[30:34] hl[32,34] *} -/// info +/// note -You can learn more about SQLite, foreign keys, and this SQL command on the SQLAlchemy docs. +You can learn more about SQLite, foreign keys, and this SQL command on the [SQLAlchemy docs](https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#foreign-key-support). /// @@ -461,7 +461,7 @@ Now, if we run the program, we will see that SQLModel (SQLAlchemy) is no longer
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -516,7 +516,7 @@ For SQLite, this also needs enabling foreign key support. As `ondelete="RESTRICT"` is mainly a database-level constraint, let's enable foreign key support in SQLite first to be able to test it. -{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py ln[30:33] hl[33] *} +{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py ln[30:34] hl[32,34] *} ### Use `ondelete="RESTRICT"` @@ -539,7 +539,7 @@ Now, if we run the program and try to delete a team with heroes, we will see an
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -593,7 +593,7 @@ Calling `team.heroes.clear()` is very similar to what SQLModel (actually SQLAlch /// -{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py ln[80:88] hl[84] *} +{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py ln[81:89] hl[85] *} ### Run the Program Deleting Heroes First @@ -602,7 +602,7 @@ Now, if we run the program and delete the heroes first, we will be able to delet
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -648,4 +648,4 @@ In some cases, when you want to **cascade** the delete of a record to its relate That will **cover most of the use cases**. 🚀 -And if you need something else, you can refer the additional options described above. 🤓 +And if you need something else, you can refer to the additional options described above. 🤓 diff --git a/docs/tutorial/relationship-attributes/define-relationships-attributes.md b/docs/tutorial/relationship-attributes/define-relationships-attributes.md index c5307d3663..5368a82297 100644 --- a/docs/tutorial/relationship-attributes/define-relationships-attributes.md +++ b/docs/tutorial/relationship-attributes/define-relationships-attributes.md @@ -1,4 +1,4 @@ -# Define Relationships Attributes +# Define Relationship Attributes Now we are finally in one of the most exciting parts of **SQLModel**. diff --git a/docs/tutorial/relationship-attributes/index.md b/docs/tutorial/relationship-attributes/index.md index f63b2669e3..a1427fb1a0 100644 --- a/docs/tutorial/relationship-attributes/index.md +++ b/docs/tutorial/relationship-attributes/index.md @@ -6,11 +6,11 @@ And then we read the data together with `select()` and using `.where()` or `.joi Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy), to work with the data in the database in a much more familiar way, and closer to normal Python code. -/// info +/// note When I say "**relationship**" I mean the standard dictionary term, of data related to other data. -I'm not using the term "**relation**" that is the technical, academical, SQL term for a single table. +I'm not using the term "**relation**" that is the technical, academic, SQL term for a single table. /// diff --git a/docs/tutorial/relationship-attributes/read-relationships.md b/docs/tutorial/relationship-attributes/read-relationships.md index 528c0c983a..cfa77d92e9 100644 --- a/docs/tutorial/relationship-attributes/read-relationships.md +++ b/docs/tutorial/relationship-attributes/read-relationships.md @@ -1,6 +1,6 @@ # Read Relationships -Now that we know how to connect data using **relationship Attributes**, let's see how to get and read the objects from a relationship. +Now that we know how to connect data using **relationship attributes**, let's see how to get and read the objects from a relationship. ## Select a Hero @@ -43,7 +43,7 @@ That would print a list with all the heroes in the Preventers team:
```console -$ python app.py +$ uv run python app.py // Automatically fetch the heroes INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id diff --git a/docs/tutorial/relationship-attributes/type-annotation-strings.md b/docs/tutorial/relationship-attributes/type-annotation-strings.md index 74b97f08d1..ca81141683 100644 --- a/docs/tutorial/relationship-attributes/type-annotation-strings.md +++ b/docs/tutorial/relationship-attributes/type-annotation-strings.md @@ -16,7 +16,7 @@ And of course, **SQLModel** can also understand it in the string correctly. ✨ That is actually part of Python, it's the current official solution to handle it. -/// info +/// note There's a lot of work going on in Python itself to make that simpler and more intuitive, and find ways to make it possible to not wrap the class in a string. diff --git a/docs/tutorial/select.md b/docs/tutorial/select.md index f31722d445..89a900626e 100644 --- a/docs/tutorial/select.md +++ b/docs/tutorial/select.md @@ -31,7 +31,7 @@ We will need the `Hero` model and the **engine**, but we will create a new sessi ## Read Data with SQL -Before writing Python code let's do a quick review of how querying data with SQL looks like: +Before writing Python code let's do a quick review of what querying data with SQL looks like: ```SQL SELECT id, name, secret_name, age @@ -259,7 +259,7 @@ Now include a call to `select_heroes()` in the `main()` function so that it is e ## Review The Code -Great, you're now being able to read the data from the database! 🎉 +Great, you're now able to read the data from the database! 🎉 Let's review the code up to this point: @@ -289,11 +289,11 @@ And the second section reading data from the database could be in another functi So, both sections could be in **different places** and would need their own sessions. -/// info +/// note To be fair, in this example all that code could actually share the same **session**, there's actually no need to have two here. -But it allows me to show you how they could be separated and to reinforce the idea that you should have **one engine** per application, and **multiple sessions**, one per each group of operations. +But it allows me to show you how they could be separated and to reinforce the idea that you should have **one engine** per application, and **multiple sessions**, one per group of operations. /// @@ -319,7 +319,7 @@ After printing it, we would see something like: ] ``` -/// info +/// note It would actually look more compact, I'm formatting it a bit for you to see that it is actually a list with all the data. @@ -329,7 +329,7 @@ It would actually look more compact, I'm formatting it a bit for you to see that I have been creating several variables to be able to explain to you what each thing is doing. -But knowing what is each object and what it is all doing, we can simplify it a bit and put it in a more compact form: +But knowing what each object is and what it is all doing, we can simplify it a bit and put it in a more compact form: {* ./docs_src/tutorial/select/tutorial004_py310.py ln[34:37] hl[36] *} @@ -349,11 +349,11 @@ In this chapter we are touching some of them. When importing from `sqlmodel` the `select()` function, you are using **SQLModel**'s version of `select`. -SQLAchemy also has its own `select`, and SQLModel's `select` uses SQLAlchemy's `select` internally. +SQLAlchemy also has its own `select`, and SQLModel's `select` uses SQLAlchemy's `select` internally. But SQLModel's version does a lot of **tricks** with type annotations to make sure you get the best **editor support** possible, no matter if you use **VS Code**, **PyCharm**, or something else. ✨ -/// info +/// note There was a lot of work and research, with different versions of the internal code, to improve this as much as possible. 🤓 @@ -369,7 +369,7 @@ If you see SQLAlchemy tutorials, they will always use `session.execute()`. **SQLModel**'s own `Session` inherits directly from SQLAlchemy's `Session`, and adds this additional method `session.exec()`. Underneath, it uses the same `session.execute()`. -But `session.exec()` does several **tricks** combined with the tricks in `session()` to give you the **best editor support**, with **autocompletion** and **inline errors** everywhere, even after getting data from a select. ✨ +But `session.exec()` does several **tricks** combined with the tricks in `select()` to give you the **best editor support**, with **autocompletion** and **inline errors** everywhere, even after getting data from a select. ✨ For example, in SQLAlchemy you would need to add a `.scalars()` here: diff --git a/docs/tutorial/update.md b/docs/tutorial/update.md index 4066379218..af7a8ec13a 100644 --- a/docs/tutorial/update.md +++ b/docs/tutorial/update.md @@ -34,7 +34,7 @@ And the second part, with the `WHERE`, defines to which rows it should apply tha In this case, as we only have one hero with the name `"Spider-Boy"`, it will only apply the update in that row. -/// info +/// note Notice that in the `UPDATE` the single equals sign (`=`) means **assignment**, setting a column to some value. @@ -98,7 +98,7 @@ Up to that point, running that in the command line will output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate and previous output omitted 😉 @@ -153,7 +153,7 @@ This commit will generate this output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -182,7 +182,7 @@ This refresh will trigger the same SQL query that would be automatically trigger
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -210,7 +210,7 @@ So, printing it will show the new `age`:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 diff --git a/docs/tutorial/where.md b/docs/tutorial/where.md index 120874a235..296751c018 100644 --- a/docs/tutorial/where.md +++ b/docs/tutorial/where.md @@ -90,7 +90,7 @@ You can try that out in **DB Browser for SQLite**: These additional keywords with some sections like `WHERE` and `FROM` that go after `SELECT` (or others) have a technical name, they are called **clauses**. -There are others clauses too, with their own SQL keywords. +There are other clauses too, with their own SQL keywords. I won't use the term **clause** too much here, but it's good for you to know it as it will probably show up in other tutorials you could study later. 🤓 @@ -101,7 +101,7 @@ Here's a quick tip that helps me think about it. * **`SELECT`** is used to tell the SQL database what **columns** to return. * **`WHERE`** is used to tell the SQL database what **rows** to return. -The size of the table in the two dimensions depend mostly on those two keywords. +The size of the table in the two dimensions depends mostly on those two keywords. ### `SELECT` Land @@ -181,7 +181,7 @@ Starting with some table: Let's review some of the code we used to read data with **SQLModel**. -We care specially about the **select** statement: +We care especially about the **select** statement: {* ./docs_src/tutorial/select/tutorial001_py310.py ln[34:39] hl[36] *} @@ -191,7 +191,7 @@ Now, the same way that we add `WHERE` to a SQL statement to filter rows, we can {* ./docs_src/tutorial/where/tutorial001_py310.py ln[34:39] hl[36] *} -It's a very small change, but it's packed of details. Let's explore them. +It's a very small change, but it's packed with details. Let's explore them. ## `select()` Objects @@ -220,7 +220,7 @@ Notice that we didn't call it with a single equal (`=`) sign, and with something select(Hero).where(name="Deadpond") ``` -That would have been shorter, of course, but it would have been much more error prone and limited. I'll show you why in a bit. +That would have been shorter, of course, but it would have been much more error-prone and limited. I'll show you why in a bit. Instead, we used two `==`: @@ -444,7 +444,7 @@ And in this case the results will be just one:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -474,7 +474,7 @@ We'll see other ways to get the data later. ## Other Comparisons -Here's another great advantage of these special **expressions** passed to `.where()`. +Here's another great advantage of these special **expressions** passed to `.where()`. Above, we have been using an "equality" comparison (using `==`), only checking if two things are the same value. @@ -538,7 +538,7 @@ age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios' /// tip -This time we got `Black Lion` too because although the age is not *strictly* greater than `35`it is *equal* to `35`. +This time we got `Black Lion` too because although the age is not *strictly* greater than `35` it is *equal* to `35`. /// @@ -548,7 +548,7 @@ Similarly, we can use `<` to get the rows where a column is **less than** a valu {* ./docs_src/tutorial/where/tutorial005_py310.py ln[42:47] hl[44] *} -And we get the younger one with an age in the database: +And we get the youngest one with an age in the database: ``` age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on' @@ -585,13 +585,13 @@ Finally, we can use `in_` to get the rows where a column is a member of a collec {* ./docs_src/tutorial/where/tutorial006b_py310.py ln[42:47] hl[44] *} -In this case, we match `Deadpond` since it's part of the collections of names. +In this case, we match `Deadpond` since it's part of the collection of names. We don't have any hero called `Ratman`, so it does not match any hero. /// tip You need to wrap your attribute with `col()` to use `in_`. -You can read more about it in the [Type annotations and errors](#type-annotations-and-errors){.internal-link target=_blank} section. +You can read more about it in the [Type annotations and errors](#type-annotations-and-errors) section. /// @@ -624,7 +624,7 @@ We can then run it to see the output from the program:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -676,7 +676,7 @@ When we run it, this generates the output:
```console -$ python app.py +$ uv run python app.py // Some boilerplate output omitted 😉 @@ -708,13 +708,13 @@ It would be an error telling you that This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `int | None`. -By using this simple and standard Python type annotations we get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨ +By using these simple and standard Python type annotations we get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨ And when we use these special **class attributes** in a `.where()`, during execution of the program, the special class attribute will know that the comparison only applies for the values that are not `NULL` in the database, and it will work correctly. -But the editor doesn't know that it's a special **class attribute**, so it tries to help us preventing an error (that in this case is a false alarm). +But the editor doesn't know that it's a special **class attribute**, so it tries to help us prevent an error (that in this case is a false alarm). -Nevertheless, we can easily fix. 🎉 +Nevertheless, we can easily fix it. 🎉 We can tell the editor that this class attribute is actually a special **SQLModel** column (instead of an instance attribute with a normal value). @@ -742,7 +742,7 @@ And with that the editor knows this code is actually fine, because this is a spe /// tip -That `col()` will come handy later, giving autocompletion to several other things we can do with these special **class attributes** for columns. +That `col()` will come in handy later, giving autocompletion to several other things we can do with these special **class attributes** for columns. But we'll get there later. diff --git a/docs/virtual-environments.md b/docs/virtual-environments.md index 131cd53d76..b12bf74387 100644 --- a/docs/virtual-environments.md +++ b/docs/virtual-environments.md @@ -1,844 +1,35 @@ # Virtual Environments -When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project. +When you work with Python projects, you should use a **virtual environment** to isolate the packages installed for each project. -/// info +For SQLModel projects, I recommend using [uv](https://docs.astral.sh/uv/) to manage the project, its dependencies, and its virtual environment. -If you already know about virtual environments, how to create them and use them, you might want to skip this section. 🤓 +## Create a Project { #create-a-project } -/// - -/// tip - -A **virtual environment** is different than an **environment variable**. - -An **environment variable** is a variable in the system that can be used by programs. - -A **virtual environment** is a directory with some files in it. - -/// - -/// info - -This page will teach you how to use **virtual environments** and how they work. - -If you are ready to adopt a **tool that manages everything** for you (including installing Python), try uv. - -/// - -## Create a Project - -First, create a directory for your project. - -What I normally do is that I create a directory named `code` inside my home/user directory. - -And inside of that I create one directory per project. +Install `uv` using the [official installation guide](https://docs.astral.sh/uv/getting-started/installation/), and then create a project:
```console -// Go to the home directory -$ cd -// Create a directory for all your code projects -$ mkdir code -// Enter into that code directory -$ cd code -// Create a directory for this project -$ mkdir awesome-project -// Enter into that project directory +$ uv init awesome-project --bare $ cd awesome-project +$ uv add sqlmodel ```
-## Create a Virtual Environment - -When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. - -/// tip - -You only need to do this **once per project**, not every time you work. - -/// - -//// tab | `venv` - -To create a virtual environment, you can use the `venv` module that comes with Python. - -
- -```console -$ python -m venv .venv -``` - -
- -/// details | What that command means - -* `python`: use the program called `python` -* `-m`: call a module as a script, we'll tell it which module next -* `venv`: use the module called `venv` that normally comes installed with Python -* `.venv`: create the virtual environment in the new directory `.venv` - -/// - -//// - -//// tab | `uv` - -If you have `uv` installed, you can use it to create a virtual environment. - -
- -```console -$ uv venv -``` - -
- -/// tip - -By default, `uv` will create a virtual environment in a directory called `.venv`. - -But you could customize it passing an additional argument with the directory name. - -/// - -//// - -That command creates a new virtual environment in a directory called `.venv`. - -/// details | `.venv` or other name - -You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`. - -/// - -## Activate the Virtual Environment - -Activate the new virtual environment so that any Python command you run or package you install uses it. - -/// tip - -Do this **every time** you start a **new terminal session** to work on the project. - -/// - -//// tab | Linux, macOS - -
- -```console -$ source .venv/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .venv\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Or if you use Bash for Windows (e.g. Git Bash): - -
- -```console -$ source .venv/Scripts/activate -``` - -
- -//// - -/// tip - -Every time you install a **new package** in that environment, **activate** the environment again. - -This makes sure that if you use a **terminal (CLI) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need. - -/// - -## Check the Virtual Environment is Active - -Check that the virtual environment is active (the previous command worked). - -/// tip - -This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended. - -/// - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which python - -/home/user/code/awesome-project/.venv/bin/python -``` - -
- -If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. 🎉 - -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command python - -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -
- -If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. 🎉 - -//// - -## Upgrade `pip` - -/// tip - -If you use `uv` you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. 😎 - -/// - -If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version. - -Many exotic errors while installing a package are solved by just upgrading `pip` first. - -/// tip - -You would normally do this **once**, right after you create the virtual environment. - -/// - -Make sure the virtual environment is active (with the command above) and then run: - -
- -```console -$ python -m pip install --upgrade pip - ----> 100% -``` - -
- -## Add `.gitignore` - -If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git. - -/// tip - -If you used `uv` to create the virtual environment, it already did this for you, you can skip this step. 😎 - -/// - -/// tip - -Do this **once**, right after you create the virtual environment. - -/// - -
- -```console -$ echo "*" > .venv/.gitignore -``` - -
- -/// details | What that command means - -* `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit) -* `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>` -* `.gitignore`: the name of the file where the text should be written - -And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory. - -That command will create a file `.gitignore` with the content: - -```gitignore -* -``` - -/// - -## Install Packages - -After activating the environment, you can install packages in it. - -/// tip - -Do this **once** when installing or upgrading the packages your project needs. - -If you need to upgrade a version or add a new package you would **do this again**. - -/// - -### Install Packages Directly - -If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly. - -/// tip - -It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`). - -/// - -//// tab | `pip` - -
- -```console -$ pip install sqlmodel - ----> 100% -``` - -
- -//// - -//// tab | `uv` - -If you have `uv`: - -
- -```console -$ uv pip install sqlmodel ----> 100% -``` - -
- -//// - -### Install from `requirements.txt` - -If you have a `requirements.txt`, you can now use it to install its packages. - -//// tab | `pip` - -
- -```console -$ pip install -r requirements.txt ----> 100% -``` - -
- -//// - -//// tab | `uv` - -If you have `uv`: - -
- -```console -$ uv pip install -r requirements.txt ----> 100% -``` - -
- -//// - -/// details | `requirements.txt` - -A `requirements.txt` with some packages could look like: - -```requirements.txt -sqlmodel==0.13.0 -rich==13.7.1 -``` - -/// - -## Run Your Program - -After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there. - -
- -```console -$ python main.py - -Hello World -``` - -
- -## Configure Your Editor - -You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors. - -For example: - -* VS Code -* PyCharm - -/// tip - -You normally have to do this only **once**, when you create the virtual environment. - -/// - -## Deactivate the Virtual Environment - -Once you are done working on your project you can **deactivate** the virtual environment. - -
- -```console -$ deactivate -``` - -
- -This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there. - -## Ready to Work - -Now you're ready to start working on your project. - - - -/// tip - -Do you want to understand what's all that above? - -Continue reading. 👇🤓 - -/// - -## Why Virtual Environments - -To work with SQLModel you need to install Python. - -After that, you would need to **install** SQLModel and any other **packages** you want to use. - -To install packages you would normally use the `pip` command that comes with Python (or similar alternatives). - -Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python). - -### The Problem - -So, what's the problem with installing packages in the global Python environment? - -At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. 😱 - -For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`. - -```mermaid -flowchart LR - stone(philosophers-stone) -->|requires| harry-1[harry v1] -``` - -Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**. - -```mermaid -flowchart LR - azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] -``` - -But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install. - -If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with: - -
- -```console -$ pip install "harry==1" -``` - -
- -And then you would end up with `harry` version `1` installed in your global Python environment. - -```mermaid -flowchart LR - subgraph global[global env] - harry-1[harry v1] - end - subgraph stone-project[philosophers-stone project] - stone(philosophers-stone) -->|requires| harry-1 - end -``` - -But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`). - -
- -```console -$ pip install "harry==3" -``` - -
- -And then you would end up with `harry` version `3` installed in your global Python environment. - -And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`. - -```mermaid -flowchart LR - subgraph global[global env] - harry-1[harry v1] - style harry-1 fill:#ccc,stroke-dasharray: 5 5 - harry-3[harry v3] - end - subgraph stone-project[philosophers-stone project] - stone(philosophers-stone) -.-x|⛔️| harry-1 - end - subgraph azkaban-project[prisoner-of-azkaban project] - azkaban(prisoner-of-azkaban) --> |requires| harry-3 - end -``` - -/// tip - -It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly. - -/// - -Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working. - -Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system. - -## Where are Packages Installed - -When you install Python, it creates some directories with some files in your computer. - -Some of these directories are the ones in charge of having all the packages you install. - -When you run: - -
- -```console -// Don't run this now, it's just an example 🤓 -$ pip install sqlmodel ----> 100% -``` - -
- -That will download a compressed file with the SQLModel code, normally from PyPI. - -It will also **download** files for other packages that SQLModel depends on. - -Then it will **extract** all those files and put them in a directory in your computer. - -By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**. - -## What are Virtual Environments - -The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on. - -A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project. - -This way, each project will have its own virtual environment (`.venv` directory) with its own packages. - -```mermaid -flowchart TB - subgraph stone-project[philosophers-stone project] - stone(philosophers-stone) --->|requires| harry-1 - subgraph venv1[.venv] - harry-1[harry v1] - end - end - subgraph azkaban-project[prisoner-of-azkaban project] - azkaban(prisoner-of-azkaban) --->|requires| harry-3 - subgraph venv2[.venv] - harry-3[harry v3] - end - end - stone-project ~~~ azkaban-project -``` - -## What Does Activating a Virtual Environment Mean - -When you activate a virtual environment, for example with: - -//// tab | Linux, macOS - -
- -```console -$ source .venv/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .venv\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Or if you use Bash for Windows (e.g. Git Bash): - -
- -```console -$ source .venv/Scripts/activate -``` - -
- -//// - -That command will create or modify some [environment variables](environment-variables.md){.internal-link target=_blank} that will be available for the next commands. - -One of those variables is the `PATH` variable. - -/// tip - -You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. - -/// - -Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable. - -Let's say that before activating the environment, the `PATH` variable looked like this: - -//// tab | Linux, macOS - -```plaintext -/usr/bin:/bin:/usr/sbin:/sbin -``` - -That means that the system would look for programs in: - -* `/usr/bin` -* `/bin` -* `/usr/sbin` -* `/sbin` - -//// - -//// tab | Windows - -```plaintext -C:\Windows\System32 -``` - -That means that the system would look for programs in: - -* `C:\Windows\System32` - -//// - -After activating the virtual environment, the `PATH` variable would look something like this: - -//// tab | Linux, macOS - -```plaintext -/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin -``` - -That means that the system will now start looking first for programs in: - -```plaintext -/home/user/code/awesome-project/.venv/bin -``` - -before looking in the other directories. - -So, when you type `python` in the terminal, the system will find the Python program in - -```plaintext -/home/user/code/awesome-project/.venv/bin/python -``` - -and use that one. - -//// - -//// tab | Windows - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 -``` - -That means that the system will now start looking first look for programs in: - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts -``` - -before looking in the other directories. - -So, when you type `python` in the terminal, the system will find the Python program in - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -and use that one. - -//// - -An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment). - -Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does. - -## Checking a Virtual Environment - -When you check if a virtual environment is active, for example with: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which python - -/home/user/code/awesome-project/.venv/bin/python -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command python - -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -
- -//// - -That means that the `python` program that will be used is the one **in the virtual environment**. - -you use `which` in Linux and macOS and `Get-Command` in Windows PowerShell. - -The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program. - -The most important part is that when you call `python`, that is the exact "`python`" that will be executed. - -So, you can confirm if you are in the correct virtual environment. - -/// tip - -It's easy to activate one virtual environment, get one Python, and then **go to another project**. - -And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project. - -It's useful being able to check what `python` is being used. 🤓 - -/// - -## Why Deactivate a Virtual Environment - -For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment. - -And then you want to work on **another project** `prisoner-of-azkaban`. - -You go to that project: - -
- -```console -$ cd ~/code/prisoner-of-azkaban -``` - -
- -If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`. - -
- -```console -$ cd ~/code/prisoner-of-azkaban - -$ python main.py - -// Error importing sirius, it's not installed 😱 -Traceback (most recent call last): - File "main.py", line 1, in - import sirius -``` - -
+`uv` creates a virtual environment for the project automatically. You don't need to create or activate one yourself. -But if you deactivate the virtual environment and activate the new one for `prisoner-of-askaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`. +Run commands inside the project environment with `uv run`, for example:
```console -$ cd ~/code/prisoner-of-azkaban - -// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 -$ deactivate - -// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 -$ source .venv/bin/activate - -// Now when you run python, it will find the package sirius installed in this virtual environment ✨ -$ python main.py - -I solemnly swear 🐺 +$ uv run python app.py ```
-## Alternatives - -This is a simple guide to get you started and teach you how everything works **underneath**. - -There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects. - -Once you are ready and want to use a tool to **manage the entire project**, packages dependencies, virtual environments, etc. I would suggest you try uv. - -`uv` can do a lot of things, it can: - -* **Install Python** for you, including different versions -* Manage the **virtual environment** for your projects -* Install **packages** -* Manage package **dependencies and versions** for your project -* Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking** -* And many other things - -## Conclusion - -If you read and understood all this, now **you know much more** about virtual environments than many developers out there. 🤓 +## Learn More { #learn-more } -Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. 😎 +Read the [Virtual Environments guide](https://tiangolo.com/guides/virtual-environments/) to learn how virtual environments work underneath, including activation and the alternative `python -m venv` and `pip` workflow. diff --git a/docs_src/advanced/datetime/__init__.py b/docs_src/advanced/datetime/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/advanced/datetime/tutorial001_py310.py b/docs_src/advanced/datetime/tutorial001_py310.py new file mode 100644 index 0000000000..d8ae8e37ab --- /dev/null +++ b/docs_src/advanced/datetime/tutorial001_py310.py @@ -0,0 +1,30 @@ +from datetime import datetime, timezone + +from sqlmodel import Field, Session, SQLModel, create_engine + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +engine = create_engine(sqlite_url, echo=True) + + +def main(): + event = Event() + print("Event:", event) + + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + session.add(event) + session.commit() + session.refresh(event) + print("Event from database:", event) + + +if __name__ == "__main__": + main() diff --git a/docs_src/advanced/datetime/tutorial001_py311.py b/docs_src/advanced/datetime/tutorial001_py311.py new file mode 100644 index 0000000000..82613cd575 --- /dev/null +++ b/docs_src/advanced/datetime/tutorial001_py311.py @@ -0,0 +1,30 @@ +from datetime import UTC, datetime + +from sqlmodel import Field, Session, SQLModel, create_engine + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +engine = create_engine(sqlite_url, echo=True) + + +def main(): + event = Event() + print("Event:", event) + + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + session.add(event) + session.commit() + session.refresh(event) + print("Event from database:", event) + + +if __name__ == "__main__": + main() diff --git a/docs_src/advanced/datetime/tutorial002_py310.py b/docs_src/advanced/datetime/tutorial002_py310.py new file mode 100644 index 0000000000..76159ca325 --- /dev/null +++ b/docs_src/advanced/datetime/tutorial002_py310.py @@ -0,0 +1,21 @@ +from pydantic import AwareDatetime, NaiveDatetime +from sqlmodel import Field, SQLModel + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + starts_at: AwareDatetime + local_time: NaiveDatetime + + +def main(): + data = { + "starts_at": "2026-01-01T12:00:00Z", + "local_time": "2026-01-01T12:00:00", + } + event = Event.model_validate(data) + print("Event:", event) + + +if __name__ == "__main__": + main() diff --git a/docs_src/advanced/datetime/tutorial003_py310.py b/docs_src/advanced/datetime/tutorial003_py310.py new file mode 100644 index 0000000000..b87f7db166 --- /dev/null +++ b/docs_src/advanced/datetime/tutorial003_py310.py @@ -0,0 +1,18 @@ +from datetime import datetime + +from sqlalchemy import DateTime +from sqlmodel import Field, SQLModel + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + created_at: datetime = Field(sa_type=DateTime(timezone=False)) + + +def main(): + event = Event(created_at=datetime(2026, 1, 1, 12)) + print("Event:", event) + + +if __name__ == "__main__": + main() diff --git a/docs_src/advanced/datetime/tutorial004_py310.py b/docs_src/advanced/datetime/tutorial004_py310.py new file mode 100644 index 0000000000..689cc754b2 --- /dev/null +++ b/docs_src/advanced/datetime/tutorial004_py310.py @@ -0,0 +1,22 @@ +import sqlalchemy as sa +from alembic import op + + +def upgrade(): + op.alter_column( + "event", + "created_at", + existing_type=sa.DateTime(timezone=False), + type_=sa.DateTime(timezone=True), + postgresql_using="created_at AT TIME ZONE 'UTC'", + ) + + +def downgrade(): + op.alter_column( + "event", + "created_at", + existing_type=sa.DateTime(timezone=True), + type_=sa.DateTime(timezone=False), + postgresql_using="created_at AT TIME ZONE 'UTC'", + ) diff --git a/docs_src/advanced/datetime/tutorial005_py310.py b/docs_src/advanced/datetime/tutorial005_py310.py new file mode 100644 index 0000000000..3bd479507e --- /dev/null +++ b/docs_src/advanced/datetime/tutorial005_py310.py @@ -0,0 +1,18 @@ +from datetime import datetime + +from pydantic import NaiveDatetime +from sqlmodel import Field, SQLModel + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + created_at: NaiveDatetime + + +def main(): + event = Event.model_validate({"created_at": datetime(2026, 1, 1, 12)}) + print("Event:", event) + + +if __name__ == "__main__": + main() diff --git a/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md b/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md index fee38368dc..eea540309b 100644 --- a/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md +++ b/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md @@ -307,7 +307,7 @@ 33. Print the `hero_1`. - /// info + /// note Even if the `hero_1` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute. @@ -323,7 +323,7 @@ 34. Print the `hero_2`. - /// info + /// note Even if the `hero_2` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute. @@ -339,7 +339,7 @@ 35. Print the `hero_3`. - /// info + /// note Even if the `hero_3` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute. diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_004.md b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_004.md index de754c5e76..3be0f1f7c1 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_004.md +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_004.md @@ -22,7 +22,7 @@ We tell it that with the `poolclass=StaticPool` parameter. - /// info + /// note You can read more details in the SQLAlchemy documentation about Using a Memory Database in Multiple Threads diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_005.md b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_005.md index 126e1f1790..e361871455 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_005.md +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/annotations/en/test_main_005.md @@ -38,4 +38,4 @@ 6. Now in the dependency override function, we just return the same `session` object that came from outside it. - The `session` object comes from the parameter passed to the test function, and we just re-use it and return it here in the dependency override. + The `session` object comes from the parameter passed to the test function, and we just reuse it and return it here in the dependency override. diff --git a/docs_src/tutorial/insert/annotations/en/tutorial003.md b/docs_src/tutorial/insert/annotations/en/tutorial003.md index 8236078567..6e364d407b 100644 --- a/docs_src/tutorial/insert/annotations/en/tutorial003.md +++ b/docs_src/tutorial/insert/annotations/en/tutorial003.md @@ -6,7 +6,7 @@ 3. Use a `with` block to create a `Session` using the `engine`. - The new **sesion** will be assigned to the variable `session`. + The new **session** will be assigned to the variable `session`. And it will be automatically closed when the `with` block is finished. diff --git a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py index 4623da1ed0..59d2b5b2c3 100644 --- a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py +++ b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py @@ -29,8 +29,9 @@ class Hero(SQLModel, table=True): def create_db_and_tables(): SQLModel.metadata.create_all(engine) - with engine.connect() as connection: - connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only + if engine.dialect.name == "sqlite": + with engine.connect() as connection: + connection.execute(text("PRAGMA foreign_keys=ON")) def create_heroes(): diff --git a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py index 051c14db34..8f8ae5d68b 100644 --- a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py +++ b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004_py310.py @@ -29,8 +29,9 @@ class Hero(SQLModel, table=True): def create_db_and_tables(): SQLModel.metadata.create_all(engine) - with engine.connect() as connection: - connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only + if engine.dialect.name == "sqlite": + with engine.connect() as connection: + connection.execute(text("PRAGMA foreign_keys=ON")) def create_heroes(): diff --git a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py index 1d89bcae07..d6a596998e 100644 --- a/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py +++ b/docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial005_py310.py @@ -29,8 +29,9 @@ class Hero(SQLModel, table=True): def create_db_and_tables(): SQLModel.metadata.create_all(engine) - with engine.connect() as connection: - connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only + if engine.dialect.name == "sqlite": + with engine.connect() as connection: + connection.execute(text("PRAGMA foreign_keys=ON")) def create_heroes(): diff --git a/mkdocs.yml b/mkdocs.yml index b89516e024..f693107a89 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,6 +3,7 @@ site_name: SQLModel site_description: SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness. site_url: https://sqlmodel.tiangolo.com/ theme: + variant: classic name: material custom_dir: docs/overrides palette: @@ -48,34 +49,22 @@ theme: - toc.follow icon: - repo: fontawesome/brands/github-alt + repo: octicons/mark-github-24 logo: img/icon-white.svg favicon: img/favicon.png language: en repo_name: fastapi/sqlmodel repo_url: https://github.com/fastapi/sqlmodel -plugins: - # Material for MkDocs - search: - social: - typeset: - # Other plugins - macros: - include_yaml: - - sponsors: data/sponsors.yml - - members: data/members.yml nav: - SQLModel: index.md - features.md - - Learn: + - "": - learn/index.md - databases.md - db-to-code.md - - environment-variables.md - - virtual-environments.md - install.md - - Tutorial - User Guide: + - "": - tutorial/index.md - tutorial/create-db-and-table-with-db-browser.md - tutorial/create-db-and-table.md @@ -88,14 +77,14 @@ nav: - tutorial/limit-and-offset.md - tutorial/update.md - tutorial/delete.md - - Connect Tables - JOIN: + - "": - tutorial/connect/index.md - tutorial/connect/create-connected-tables.md - tutorial/connect/create-connected-rows.md - tutorial/connect/read-connected-data.md - tutorial/connect/update-data-connections.md - tutorial/connect/remove-data-connections.md - - Relationship Attributes: + - "": - tutorial/relationship-attributes/index.md - tutorial/relationship-attributes/define-relationships-attributes.md - tutorial/relationship-attributes/create-and-update-relationships.md @@ -104,14 +93,14 @@ nav: - tutorial/relationship-attributes/back-populates.md - tutorial/relationship-attributes/cascade-delete-relationships.md - tutorial/relationship-attributes/type-annotation-strings.md - - Many to Many: + - "": - tutorial/many-to-many/index.md - tutorial/many-to-many/create-models-with-link.md - tutorial/many-to-many/create-data.md - tutorial/many-to-many/update-remove-relationships.md - tutorial/many-to-many/link-with-extra-fields.md - tutorial/code-structure.md - - FastAPI and Pydantic: + - "": - tutorial/fastapi/index.md - tutorial/fastapi/simple-hero-api.md - tutorial/fastapi/response-model.md @@ -125,16 +114,16 @@ nav: - tutorial/fastapi/teams.md - tutorial/fastapi/relationships.md - tutorial/fastapi/tests.md - - Advanced User Guide: + - "": - advanced/index.md + - advanced/datetime.md - advanced/decimal.md - advanced/uuid.md - - Resources: + - "": - resources/index.md - help.md - contributing.md - - management-tasks.md - - About: + - "": - about/index.md - alternatives.md - management.md @@ -146,6 +135,7 @@ markdown_extensions: targets: include: - "*" + zensical.extensions.macros: # Python Markdown abbr: attr_list: @@ -173,17 +163,6 @@ markdown_extensions: # pymdownx blocks pymdownx.blocks.admonition: - types: - - note - - attention - - caution - - danger - - error - - tip - - hint - - warning - # Custom types - - info pymdownx.blocks.details: pymdownx.blocks.tab: alternate_style: True @@ -194,18 +173,14 @@ markdown_extensions: extra: social: - - icon: fontawesome/brands/github-alt - link: https://github.com/fastapi/sqlmodel - - icon: fontawesome/brands/twitter - link: https://twitter.com/tiangolo - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com + - icon: octicons/mark-github-24 + link: https://github.com/fastapi/sqlmodel + - icon: fontawesome/brands/x-twitter + link: https://x.com/tiangolo + - icon: fontawesome/brands/bluesky + link: https://bsky.app/profile/tiangolo.com + - icon: fontawesome/brands/linkedin + link: https://www.linkedin.com/in/tiangolo extra_css: - css/termynal.css @@ -215,5 +190,5 @@ extra_javascript: - js/termynal.js - js/custom.js -hooks: - - scripts/mkdocs_hooks.py +validation: + unresolved_references: false diff --git a/pyproject.toml b/pyproject.toml index 2add5e2515..07bd6117d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ dev = [ { include-group = "docs" }, { include-group = "tests" }, "prek>=0.2.24,<1.0.0", + "zizmor >=1.23.1", ] docs = [ "black >=24.1.0", @@ -60,13 +61,11 @@ docs = [ "griffe-warnings-deprecated >=1.1.0", "markdown-include-variants >=0.0.8", "mdx-include >=1.4.1", - "mkdocs-macros-plugin >=1.5.0", - "mkdocs-material >=9.7.5", - "mkdocs-redirects >=1.2.1", "mkdocstrings[python] >=1.0.3", "pillow >=12.1.1", "pyyaml >=5.3.1", - "typer >=0.24.1", + "typer >=0.26.1", + "zensical>=0.0.42", ] github-actions = [ "httpx >=0.28.1", @@ -76,15 +75,19 @@ github-actions = [ "smokeshow >=0.5.0", ] tests = [ + "alembic >=1.12.0", "black >=24.1.0", "coverage[toml] >=6.2", "dirty-equals >=0.11", "fastapi >=0.128.0", "httpx >=0.28.1", "jinja2 >=3.1.6", + "psycopg[binary] >=3.2.10", + "pymysql[rsa] >=1.1.1", "pytest >=7.0.1", "ruff >=0.15.6", "ty>=0.0.25", + "typer >=0.26.1", "typing-extensions >=4.15.0", ] @@ -154,3 +157,22 @@ keep-runtime-typing = true [tool.ty.terminal] error-on-warning = true + +[tool.typos.files] +extend-exclude = [ + "coverage/", + "dist/", + "docs/img/", + "docs/release-notes.md", + "htmlcov/", + "site/", + "site_build/", + "uv.lock", +] + +[tool.typos.default.extend-identifiers] +alls = "alls" + +[tool.typos.default.extend-words] +Ines = "Ines" +selectin = "selectin" diff --git a/scripts/docs.py b/scripts/docs.py index ba41c57ca6..ec98067307 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -6,8 +6,8 @@ from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path -import mkdocs.utils import typer +import yaml from jinja2 import Template from ruff.__main__ import find_ruff_bin @@ -45,7 +45,7 @@ def generate_readme_content() -> str: match_start = re.search(r"", content) match_end = re.search(r"", content) sponsors_data_path = en_docs_path / "data" / "sponsors.yml" - sponsors = mkdocs.utils.yaml_load(sponsors_data_path.read_text(encoding="utf-8")) + sponsors = yaml.safe_load(sponsors_data_path.read_text(encoding="utf-8")) if not (match_start and match_end): raise RuntimeError("Couldn't auto-generate sponsors section") if not match_pre: @@ -99,16 +99,10 @@ def verify_readme() -> None: @app.command() def live(dirty: bool = False) -> None: """ - Serve with livereload a docs site for a specific language. - - This only shows the actual translated files, not the placeholders created with - build-all. - - Takes an optional LANG argument with the name of the language to serve, by default - en. + Serve the docs site with livereload, at http://127.0.0.1:8008. """ # Enable line numbers during local development to make it easier to highlight - args = ["mkdocs", "serve", "--dev-addr", "127.0.0.1:8008"] + args = ["zensical", "serve", "--dev-addr", "127.0.0.1:8008"] if dirty: args.append("--dirty") subprocess.run(args, env={**os.environ, "LINENUMS": "true"}, check=True) @@ -120,7 +114,7 @@ def build() -> None: Build the docs. """ print("Building docs") - subprocess.run(["mkdocs", "build"], check=True) + subprocess.run(["zensical", "build"], check=True) typer.secho("Successfully built docs", color=typer.colors.GREEN) @@ -129,7 +123,7 @@ def serve() -> None: """ A quick server to preview a built site. - For development, prefer the command live (or just mkdocs serve). + For development, prefer the command live (or just zensical serve). This is here only to preview the documentation site. diff --git a/scripts/lint.sh b/scripts/lint.sh index 9b2366d226..79c62bf0ec 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -3,7 +3,6 @@ set -e set -x -ty check sqlmodel -ty check tests/test_select_typing.py +ty check sqlmodel tests/test_dataclass_transform.py tests/test_field_sa_type.py tests/test_select_typing.py ruff check sqlmodel tests docs_src scripts ruff format sqlmodel tests docs_src scripts --check diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py deleted file mode 100644 index 783ff23302..0000000000 --- a/scripts/mkdocs_hooks.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Any - -from mkdocs.config.defaults import MkDocsConfig -from mkdocs.structure.files import Files -from mkdocs.structure.nav import Link, Navigation, Section -from mkdocs.structure.pages import Page - - -def generate_renamed_section_items( - items: list[Page | Section | Link], *, config: MkDocsConfig -) -> list[Page | Section | Link]: - new_items: list[Page | Section | Link] = [] - for item in items: - if isinstance(item, Section): - new_title = item.title - new_children = generate_renamed_section_items(item.children, config=config) - first_child = new_children[0] - if isinstance(first_child, Page): - if first_child.file.src_path.endswith("index.md"): - # Read the source so that the title is parsed and available - first_child.read_source(config=config) - new_title = first_child.title or new_title - # Creating a new section makes it render it collapsed by default - # no idea why, so, let's just modify the existing one - # new_section = Section(title=new_title, children=new_children) - item.title = new_title - item.children = new_children - new_items.append(item) - else: - new_items.append(item) - return new_items - - -def on_nav( - nav: Navigation, *, config: MkDocsConfig, files: Files, **kwargs: Any -) -> Navigation: - new_items = generate_renamed_section_items(nav.items, config=config) - return Navigation(items=new_items, pages=nav.pages) diff --git a/scripts/prepare_release.py b/scripts/prepare_release.py new file mode 100644 index 0000000000..73ea40f2ff --- /dev/null +++ b/scripts/prepare_release.py @@ -0,0 +1,209 @@ +"""Prepare a release by updating the package version and release notes.""" + +import re +from datetime import date +from pathlib import Path +from typing import Annotated, Literal + +import typer + +VERSION_PATTERN = re.compile(r'(?m)^__version__ = "(\d+\.\d+\.\d+)"$') +VERSION_HEADING_PATTERN = re.compile(r"(?m)^## (\d+\.\d+\.\d+)(?: \([^)]+\))?$") +RELEASE_NOTES_HEADER = "# Release Notes\n\n" +LATEST_CHANGES_HEADER = "## Latest Changes" +BumpType = Literal["major", "minor", "patch"] + +app = typer.Typer() + + +def parse_version(version: str) -> tuple[int, int, int]: + match = re.fullmatch(r"\d+\.\d+\.\d+", version) + if not match: + raise ValueError(f"Invalid version: {version!r}. Expected format: X.Y.Z") + major, minor, patch = version.split(".") + return int(major), int(minor), int(patch) + + +def get_current_version(content: str, version_file: Path) -> str: + matches = list(VERSION_PATTERN.finditer(content)) + if len(matches) != 1: + raise RuntimeError( + f"Expected exactly one __version__ assignment in {version_file}, " + f"found {len(matches)}" + ) + return matches[0].group(1) + + +def bump_version(version: str, bump: BumpType) -> str: + major, minor, patch = parse_version(version) + if bump == "major": + return f"{major + 1}.0.0" + if bump == "minor": + return f"{major}.{minor + 1}.0" + return f"{major}.{minor}.{patch + 1}" + + +def update_version_file(content: str, version: str, version_file: Path) -> str: + current_version = get_current_version(content, version_file) + if parse_version(version) <= parse_version(current_version): + raise RuntimeError( + f"New version {version} must be greater than current version {current_version}" + ) + return VERSION_PATTERN.sub(f'__version__ = "{version}"', content, count=1) + + +def update_release_notes( + content: str, version: str, release_date: date, release_notes_file: Path +) -> str: + if not content.startswith(RELEASE_NOTES_HEADER): + raise RuntimeError( + f"{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}" + ) + if re.search(rf"^## {re.escape(version)}(?: \([^)]+\))?$", content, re.M): + raise RuntimeError(f"Release notes already contain a section for {version}") + + latest_header = f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n" + if not content.startswith(latest_header): + raise RuntimeError(f"{release_notes_file} must start with {latest_header!r}") + + release_header = f"## {version} ({release_date.isoformat()})" + return content.replace( + latest_header, + f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n\n{release_header}\n", + 1, + ) + + +def get_release_notes_body(content: str, version: str, release_notes_file: Path) -> str: + version_heading = re.compile(rf"(?m)^## {re.escape(version)}(?: \([^)]+\))?$") + match = version_heading.search(content) + if not match: + raise RuntimeError( + f"Could not find release notes section for {version} in {release_notes_file}" + ) + + next_match = VERSION_HEADING_PATTERN.search(content, match.end()) + end = next_match.start() if next_match else len(content) + body = content[match.end() : end].strip() + if not body: + raise RuntimeError( + f"Release notes section for {version} in {release_notes_file} is empty" + ) + return f"{body}\n" + + +@app.command() +def prepare( + bump: Annotated[ + BumpType, + typer.Argument( + envvar="PREPARE_RELEASE_BUMP", + help="The release bump to make: major, minor, or patch.", + ), + ], + version_file: Annotated[ + Path, + typer.Option( + envvar="PREPARE_RELEASE_VERSION_FILE", + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + writable=True, + help="Path to the Python file containing the __version__ assignment.", + ), + ], + release_notes_file: Annotated[ + Path, + typer.Option( + envvar="PREPARE_RELEASE_RELEASE_NOTES_FILE", + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + writable=True, + help="Path to the release notes Markdown file.", + ), + ], + release_date: Annotated[ + str, + typer.Option( + "--date", + envvar="PREPARE_RELEASE_DATE", + help="Release date in YYYY-MM-DD format. Defaults to today.", + ), + ] = date.today().isoformat(), +) -> None: + parsed_release_date = date.fromisoformat(release_date or date.today().isoformat()) + + version_file_content = version_file.read_text() + release_notes_content = release_notes_file.read_text() + version = bump_version( + get_current_version(version_file_content, version_file), bump + ) + + version_file.write_text( + update_version_file(version_file_content, version, version_file) + ) + release_notes_file.write_text( + update_release_notes( + release_notes_content, version, parsed_release_date, release_notes_file + ) + ) + + typer.echo(f"Prepared release {version} ({parsed_release_date.isoformat()})") + + +@app.command() +def current_version( + version_file: Annotated[ + Path, + typer.Option( + envvar="PREPARE_RELEASE_VERSION_FILE", + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + help="Path to the Python file containing the __version__ assignment.", + ), + ], +) -> None: + typer.echo(get_current_version(version_file.read_text(), version_file)) + + +@app.command() +def release_notes( + version_file: Annotated[ + Path, + typer.Option( + envvar="PREPARE_RELEASE_VERSION_FILE", + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + help="Path to the Python file containing the __version__ assignment.", + ), + ], + release_notes_file: Annotated[ + Path, + typer.Option( + envvar="PREPARE_RELEASE_RELEASE_NOTES_FILE", + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + help="Path to the release notes Markdown file.", + ), + ], +) -> None: + version = get_current_version(version_file.read_text(), version_file) + typer.echo( + get_release_notes_body( + release_notes_file.read_text(), version, release_notes_file + ), + nl=False, + ) + + +if __name__ == "__main__": + app() diff --git a/sqlmodel-slim/README.md b/sqlmodel-slim/README.md deleted file mode 100644 index bc4b07b5e8..0000000000 --- a/sqlmodel-slim/README.md +++ /dev/null @@ -1,52 +0,0 @@ -

- SQLModel - -

-

- SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness. -

-

- - Test - - - Publish - - - Coverage - - Package version - -

- ---- - -**Documentation**: https://sqlmodel.tiangolo.com - -**Source Code**: https://github.com/fastapi/sqlmodel - ---- - -SQLModel is a library for interacting with SQL databases from Python code, with Python objects. It is designed to be intuitive, easy to use, highly compatible, and robust. - -**SQLModel** is based on Python type annotations, and powered by Pydantic and SQLAlchemy. - -## `sqlmodel-slim` - -⚠️ Do not install this package. ⚠️ - -This package, `sqlmodel-slim`, does nothing other than depend on `sqlmodel`. - -You **should not** install this package. - -Install instead: - -```bash -pip install sqlmodel -``` - -This package is deprecated and will stop receiving any updates and published versions. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/sqlmodel/.agents/skills/sqlmodel/SKILL.md b/sqlmodel/.agents/skills/sqlmodel/SKILL.md new file mode 100644 index 0000000000..f7faa8345a --- /dev/null +++ b/sqlmodel/.agents/skills/sqlmodel/SKILL.md @@ -0,0 +1,205 @@ +--- +name: sqlmodel +description: Use when writing or reviewing Python code with SQLModel, especially models, sessions, queries, FastAPI integration, relationships, link models, creates, updates, and deletes. +--- + +# SQLModel Patterns + +Use SQLModel's API first. Do not default to raw SQLAlchemy patterns unless the task explicitly needs a SQLAlchemy-only feature. + +## Imports + +Prefer imports from `sqlmodel`: + +```python +from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select +``` + +Do not use SQLAlchemy declarative defaults such as `declarative_base()`, `Mapped[...]`, `mapped_column()`, `relationship()`, or `sessionmaker()` for normal SQLModel code. + +## Models + +Define table models with `SQLModel, table=True` and `Field()`: + +```python +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str = Field(index=True) + team_id: int | None = Field(default=None, foreign_key="team.id") +``` + +Use `Field(default_factory=...)` for generated values: + +```python +id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) +``` + +Use non-table `SQLModel` classes for create/update/public API schemas instead of mixing request/response-only fields into table models. + +## Datetimes + +In SQLModel `0.0.45` and newer, use `datetime` for timestamp fields. SQLModel automatically selects `UTCDateTime`, which normalizes aware values to UTC when writing and returns aware UTC datetimes when reading, including with SQLite and MySQL/MariaDB. + +Do not add `sa_type=DateTime(timezone=True)` or `sa_column=Column(DateTime(timezone=True))` just to enable timezone support. These overrides bypass SQLModel's UTC processing. If an explicit column type is needed, import `UTCDateTime` from `sqlmodel`. + +For generated timestamps on Python 3.11 and newer: + +```python +from datetime import UTC, datetime + +from sqlmodel import Field, SQLModel + + +class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) +``` + +On Python 3.10, import `timezone` instead of `UTC` and use `lambda: datetime.now(timezone.utc)`. + +- Supply aware datetimes for writes, updates, and query filters. Avoid `datetime.now()` without a timezone and `datetime.utcnow()` for UTC timestamps. Naive database parameters are rejected when the statement executes. +- Use Pydantic's `AwareDatetime` when input validation must reject naive values. It uses the same UTC storage. Validate table-model input with `Model.model_validate(data)`. Direct table-model construction currently skips Pydantic validation, and plain `datetime` validation accepts naive values. +- Use Pydantic's `NaiveDatetime` for intentional naive storage. It selects `DateTime(timezone=False)` and requires naive values during Pydantic validation. + +When upgrading existing applications, follow the [datetime migration guide](https://sqlmodel.tiangolo.com/advanced/datetime/#upgrade-existing-applications). Changing the model does not alter existing columns or convert historical data. + +## Sessions and Queries + +Open sessions directly with the engine: + +```python +with Session(engine) as session: + heroes = session.exec(select(Hero)).all() +``` + +Do not create a `sessionmaker()` for typical SQLModel examples. + +Use `session.exec(select(...))`, not `session.execute(...)` and not `session.query(...)`. SQLModel's `exec()` handles scalar results so agents should not add `.scalars()` after selects of models. + +Use `session.get(Model, id)` for primary-key lookups. + +Use normal result methods after `session.exec(...)`: `.all()` for lists, `.first()` for optional first rows, `.one()` when exactly one row must exist, and `.one_or_none()` when zero or one row is valid. + +After creating or mutating objects, commit and refresh when returned data needs DB defaults or generated IDs: + +```python +session.add(hero) +session.commit() +session.refresh(hero) +``` + +## Relationships + +Use SQLModel relationship attributes, not ad-hoc SQLAlchemy tables or `secondary=`. + +One-to-many: + +```python +class Team(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + heroes: list["Hero"] = Relationship(back_populates="team") + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + team_id: int | None = Field(default=None, foreign_key="team.id") + team: Team | None = Relationship(back_populates="heroes") +``` + +Many-to-many: + +```python +class HeroTeamLink(SQLModel, table=True): + team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True) + hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True) + + +class Team(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + heroes: list["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink) + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + teams: list[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink) +``` + +If the link table has extra fields, model it as a full SQLModel table with relationships to both sides, and interact with the link objects directly. + +When starting a new app, prefer keeping related SQLModel table models in one file to simplify relationship annotations and metadata ordering. If models must be split across files, use `TYPE_CHECKING` imports plus string annotations: + +```python +if TYPE_CHECKING: + from .team_model import Team + +team: Optional["Team"] = Relationship(back_populates="heroes") +``` + +For SQLAlchemy relationship options not exposed as first-class SQLModel parameters, prefer `Relationship(sa_relationship_kwargs={...})` or `Relationship(sa_relationship_args=[...])`. Use `sa_relationship=relationship(...)` only as an escape hatch when SQLModel's relationship wrapper cannot express the mapping. + +## Creates and Updates + +Use direct construction for trusted internal values: + +```python +hero = Hero(name="Deadpond", secret_name="Dive Wilson") +``` + +Build table objects from data models with `Model.model_validate(...)`: + +```python +db_hero = Hero.model_validate(hero_create) +``` + +For FastAPI, use a split model pattern: `HeroBase` for shared fields, `Hero(HeroBase, table=True)` for the table, `HeroCreate` for input, `HeroUpdate` with all-optional fields for PATCH, `HeroPublic` for output, and relationship-specific public models such as `HeroPublicWithTeam` only where needed. + +For partial updates, dump only provided fields and update in place: + +```python +hero_data = hero_update.model_dump(exclude_unset=True) +db_hero.sqlmodel_update(hero_data) +session.add(db_hero) +session.commit() +session.refresh(db_hero) +``` + +## Metadata and App Setup + +Call `SQLModel.metadata.create_all(engine)` only after all table model classes have been imported. In FastAPI examples, use a dependency that yields `Session(engine)`. + +For tests and small examples, SQLite is a common option; when using it, create metadata explicitly and use direct sessions: + +```python +engine = create_engine("sqlite:///:memory:") +SQLModel.metadata.create_all(engine) +with Session(engine) as session: + ... +``` + +When using SQLite with FastAPI, include: + +```python +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) +``` + +## Deletes + +Use SQLModel's relationship helpers for cascades: + +```python +heroes: list["Hero"] = Relationship(back_populates="team", cascade_delete=True) +team_id: int | None = Field(default=None, foreign_key="team.id", ondelete="CASCADE") +``` + +For database-level delete behavior such as `SET NULL`, pair `ondelete` with a nullable foreign key and use `passive_deletes` on the relationship when appropriate. + +`ondelete="SET NULL"` requires a nullable foreign key: + +```python +team_id: int | None = Field(default=None, foreign_key="team.id", ondelete="SET NULL") +``` + +## SQLAlchemy Escape Hatches + +Use SQLModel's `Field(sa_type=...)`, `Field(sa_column=...)`, `Field(sa_column_args=...)`, or `Field(sa_column_kwargs=...)` only when normal `Field()` parameters do not cover a column or type requirement. Do not switch the whole model to SQLAlchemy declarative style for one custom column. diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index ba58041629..5ad6961897 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -1,4 +1,8 @@ -__version__ = "0.0.38.post1" +__version__ = "0.0.47.post1" + +# Re-export from Pydantic +from pydantic import Discriminator as Discriminator +from pydantic import Tag as Tag # Re-export from SQLAlchemy from sqlalchemy.engine import create_engine as create_engine @@ -141,3 +145,4 @@ from .sql.expression import type_coerce as type_coerce from .sql.expression import within_group as within_group from .sql.sqltypes import AutoString as AutoString +from .sql.sqltypes import UTCDateTime as UTCDateTime diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index a220b193f1..c4cf7ac5f1 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -12,13 +12,14 @@ TypeAlias, TypeVar, Union, + cast, get_args, get_origin, ) from annotated_types import MaxLen from pydantic import VERSION as P_VERSION -from pydantic import BaseModel +from pydantic import AwareDatetime, BaseModel, NaiveDatetime from pydantic import ConfigDict as ConfigDict from pydantic._internal._fields import PydanticMetadata from pydantic._internal._model_construction import ModelMetaclass as ModelMetaclass @@ -179,12 +180,20 @@ def get_sa_type_from_type_annotation(annotation: Any) -> Any: if origin is None: return annotation elif origin is Annotated: - return get_sa_type_from_type_annotation(get_args(annotation)[0]) + type_, *metadata = get_args(annotation) + type_ = get_sa_type_from_type_annotation(type_) + # Like Pydantic, apply the last timezone constraint in Annotated. + for meta in metadata: + if meta is AwareDatetime or isinstance(meta, cast(type, AwareDatetime)): + type_ = AwareDatetime + elif meta is NaiveDatetime or isinstance(meta, cast(type, NaiveDatetime)): + type_ = NaiveDatetime + return type_ if _is_union_type(origin): bases = get_args(annotation) if len(bases) > 2: raise ValueError("Cannot have a (non-optional) union as a SQLAlchemy field") - # Non optional unions are not allowed + # Non-optional unions are not allowed if bases[0] is not NoneType and bases[1] is not NoneType: raise ValueError("Cannot have a (non-optional) union as a SQLAlchemy field") # Optional unions are allowed @@ -194,7 +203,7 @@ def get_sa_type_from_type_annotation(annotation: Any) -> Any: def get_sa_type_from_field(field: Any) -> Any: - type_: Any = field.annotation + type_: Any = field.rebuild_annotation() return get_sa_type_from_type_annotation(type_) diff --git a/sqlmodel/ext/asyncio/session.py b/sqlmodel/ext/asyncio/session.py index f2b989e2fa..6df870bc61 100644 --- a/sqlmodel/ext/asyncio/session.py +++ b/sqlmodel/ext/asyncio/session.py @@ -105,7 +105,7 @@ async def exec( 🚨 You probably want to use `session.exec()` instead of `session.execute()`. This is the original SQLAlchemy `session.execute()` method that returns objects - of type `Row`, and that you have to call `scalars()` to get the model objects. + of type `Row`, on which you have to call `scalars()` to get the model objects. For example: @@ -114,7 +114,7 @@ async def exec( heroes = result.scalars().all() ``` - instead you could use `exec()`: + Instead, you could use `exec()`: ```Python result = await session.exec(select(Hero)) @@ -136,7 +136,7 @@ async def execute( 🚨 You probably want to use `session.exec()` instead of `session.execute()`. This is the original SQLAlchemy `session.execute()` method that returns objects - of type `Row`, and that you have to call `scalars()` to get the model objects. + of type `Row`, on which you have to call `scalars()` to get the model objects. For example: @@ -145,7 +145,7 @@ async def execute( heroes = result.scalars().all() ``` - instead you could use `exec()`: + Instead, you could use `exec()`: ```Python result = await session.exec(select(Hero)) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 9a1a676775..bf0e14c247 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -3,6 +3,7 @@ import builtins import ipaddress import uuid +import warnings from collections.abc import Callable, Mapping, Sequence, Set from dataclasses import dataclass from datetime import date, datetime, time, timedelta @@ -11,6 +12,7 @@ from pathlib import Path from typing import ( TYPE_CHECKING, + Annotated, Any, ClassVar, Literal, @@ -22,7 +24,7 @@ overload, ) -from pydantic import BaseModel, EmailStr +from pydantic import AwareDatetime, BaseModel, Discriminator, EmailStr, NaiveDatetime from pydantic.fields import FieldInfo as PydanticFieldInfo from sqlalchemy import ( Boolean, @@ -49,7 +51,8 @@ from sqlalchemy.orm.instrumentation import is_instrumented from sqlalchemy.sql.schema import MetaData from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid -from typing_extensions import deprecated +from sqlalchemy.types import TypeEngine +from typing_extensions import dataclass_transform, deprecated from ._compat import ( PYDANTIC_MINOR_VERSION, @@ -71,7 +74,7 @@ sqlmodel_init, sqlmodel_validate, ) -from .sql.sqltypes import AutoString +from .sql.sqltypes import AutoString, UTCDateTime if TYPE_CHECKING: from pydantic._internal._model_construction import ModelMetaclass as ModelMetaclass @@ -79,7 +82,6 @@ from pydantic_core import PydanticUndefined as Undefined from pydantic_core import PydanticUndefinedType as UndefinedType -_T = TypeVar("_T") NoArgAnyCallable = Callable[[], Any] IncEx: TypeAlias = ( set[int] @@ -87,17 +89,15 @@ | Mapping[int, Union["IncEx", bool]] | Mapping[str, Union["IncEx", bool]] ) +SaTypeOrInstance: TypeAlias = TypeEngine[Any] | type[TypeEngine[Any]] OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"] - -def __dataclass_transform__( - *, - eq_default: bool = True, - order_default: bool = False, - kw_only_default: bool = False, - field_descriptors: tuple[type | Callable[..., Any], ...] = (()), -) -> Callable[[_T], _T]: - return lambda a: a +MIN_ITEMS_DEPRECATION_MSG = ( + "`min_items` is deprecated and will be removed, use `min_length` instead" +) +MAX_ITEMS_DEPRECATION_MSG = ( + "`max_items` is deprecated and will be removed, use `max_length` instead" +) class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class] @@ -152,6 +152,11 @@ def __init__(self, default: Any = Undefined, **kwargs: Any) -> None: raise RuntimeError( "Passing sa_type is not supported when also passing a sa_column" ) + if sa_column_kwargs is not Undefined and "type_" in sa_column_kwargs: + raise RuntimeError( + "Passing type_ is not supported in sa_column_kwargs, " + "use sa_type instead" + ) if ondelete is not Undefined: if foreign_key is Undefined: raise RuntimeError("ondelete can only be used with foreign_key") @@ -208,7 +213,7 @@ class FieldInfoMetadata: ondelete: OnDeleteType | UndefinedType = Undefined unique: bool | UndefinedType = Undefined index: bool | UndefinedType = Undefined - sa_type: type[Any] | UndefinedType = Undefined + sa_type: SaTypeOrInstance | UndefinedType = Undefined sa_column: Column[Any] | UndefinedType = Undefined sa_column_args: Sequence[Any] | UndefinedType = Undefined sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined @@ -253,21 +258,27 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, allow_mutation: bool = True, regex: str | None = None, - discriminator: str | None = None, + discriminator: str | Discriminator | None = None, repr: bool = True, primary_key: bool | UndefinedType = Undefined, foreign_key: Any = Undefined, unique: bool | UndefinedType = Undefined, nullable: bool | UndefinedType = Undefined, index: bool | UndefinedType = Undefined, - sa_type: type[Any] | UndefinedType = Undefined, + sa_type: SaTypeOrInstance | UndefinedType = Undefined, sa_column_args: Sequence[Any] | UndefinedType = Undefined, sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined, schema_extra: dict[str, Any] | None = None, @@ -296,14 +307,20 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, allow_mutation: bool = True, regex: str | None = None, - discriminator: str | None = None, + discriminator: str | Discriminator | None = None, repr: bool = True, primary_key: bool | UndefinedType = Undefined, foreign_key: str, @@ -311,7 +328,7 @@ def Field( unique: bool | UndefinedType = Undefined, nullable: bool | UndefinedType = Undefined, index: bool | UndefinedType = Undefined, - sa_type: type[Any] | UndefinedType = Undefined, + sa_type: SaTypeOrInstance | UndefinedType = Undefined, sa_column_args: Sequence[Any] | UndefinedType = Undefined, sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined, schema_extra: dict[str, Any] | None = None, @@ -348,14 +365,20 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, allow_mutation: bool = True, regex: str | None = None, - discriminator: str | None = None, + discriminator: str | Discriminator | None = None, repr: bool = True, sa_column: Column[Any] | UndefinedType = Undefined, schema_extra: dict[str, Any] | None = None, @@ -381,14 +404,20 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, allow_mutation: bool = True, regex: str | None = None, - discriminator: str | None = None, + discriminator: str | Discriminator | None = None, repr: bool = True, primary_key: bool | UndefinedType = Undefined, foreign_key: Any = Undefined, @@ -396,13 +425,23 @@ def Field( unique: bool | UndefinedType = Undefined, nullable: bool | UndefinedType = Undefined, index: bool | UndefinedType = Undefined, - sa_type: type[Any] | UndefinedType = Undefined, + sa_type: SaTypeOrInstance | UndefinedType = Undefined, sa_column: Column | UndefinedType = Undefined, sa_column_args: Sequence[Any] | UndefinedType = Undefined, sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined, schema_extra: dict[str, Any] | None = None, ) -> Any: current_schema_extra = schema_extra or {} + + if min_items is not None: + warnings.warn(MIN_ITEMS_DEPRECATION_MSG, DeprecationWarning, stacklevel=2) + if min_length is None: + min_length = min_items + if max_items is not None: + warnings.warn(MAX_ITEMS_DEPRECATION_MSG, DeprecationWarning, stacklevel=2) + if max_length is None: + max_length = max_items + # Extract possible alias settings from schema_extra so we can control precedence schema_validation_alias = current_schema_extra.pop("validation_alias", None) schema_serialization_alias = current_schema_extra.pop("serialization_alias", None) @@ -420,8 +459,6 @@ def Field( "multiple_of": multiple_of, "max_digits": max_digits, "decimal_places": decimal_places, - "min_items": min_items, - "max_items": max_items, "unique_items": unique_items, "min_length": min_length, "max_length": max_length, @@ -517,7 +554,7 @@ def Relationship( return relationship_info -@__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo)) +@dataclass_transform(kw_only_default=True, field_specifiers=(Field, FieldInfo)) class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta): __sqlmodel_relationships__: dict[str, RelationshipInfo] model_config: SQLModelConfig @@ -717,8 +754,10 @@ def get_sqlalchemy_type(field: Any) -> Any: return Boolean if issubclass(type_, int): return Integer - if issubclass(type_, datetime): - return DateTime + if issubclass(type_, (datetime, AwareDatetime, NaiveDatetime)): + if issubclass(type_, cast(type, NaiveDatetime)): + return DateTime(timezone=False) + return UTCDateTime() if issubclass(type_, date): return Date if issubclass(type_, timedelta): @@ -793,7 +832,7 @@ def get_column_from_field(field: Any) -> Column: ) if sa_column_kwargs is not Undefined: kwargs.update(cast(dict[Any, Any], sa_column_kwargs)) - return Column(sa_type, *args, **kwargs) + return Column(*args, type_=sa_type, **kwargs) default_registry = registry() @@ -805,7 +844,7 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry # SQLAlchemy needs to set weakref(s), Pydantic will set the other slots values __slots__ = ("__weakref__",) __tablename__: ClassVar[str | Callable[..., str]] - __sqlmodel_relationships__: ClassVar[builtins.dict[str, RelationshipProperty[Any]]] + __sqlmodel_relationships__: ClassVar[builtins.dict[str, RelationshipInfo]] __name__: ClassVar[str] metadata: ClassVar[MetaData] __allow_unmapped__ = True # https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-20-step-six @@ -903,6 +942,7 @@ def model_dump( warnings: bool | Literal["none", "warn", "error"] = True, fallback: Callable[[Any], Any] | None = None, # v2.11 serialize_as_any: bool = False, # v2.7 + polymorphic_serialization: bool | None = None, # v2.13 ) -> builtins.dict[str, Any]: if PYDANTIC_MINOR_VERSION < (2, 11): by_alias = by_alias or False @@ -913,6 +953,8 @@ def model_dump( extra_kwargs["fallback"] = fallback if PYDANTIC_MINOR_VERSION >= (2, 12): extra_kwargs["exclude_computed_fields"] = exclude_computed_fields + if PYDANTIC_MINOR_VERSION >= (2, 13): + extra_kwargs["polymorphic_serialization"] = polymorphic_serialization return super().model_dump( mode=mode, include=include, @@ -1003,6 +1045,6 @@ def sqlmodel_update( else: raise ValueError( "Can't use sqlmodel_update() with something that " - f"is not a dict or SQLModel or Pydantic model: {obj}" + f"is not a dict, SQLModel, or Pydantic model: {obj}" ) return self diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 5c721ae0da..d49d0aad77 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -89,7 +89,7 @@ def exec( 🚨 You probably want to use `session.exec()` instead of `session.execute()`. This is the original SQLAlchemy `session.execute()` method that returns objects - of type `Row`, and that you have to call `scalars()` to get the model objects. + of type `Row`, on which you have to call `scalars()` to get the model objects. For example: @@ -97,7 +97,7 @@ def exec( heroes = session.execute(select(Hero)).scalars().all() ``` - instead you could use `exec()`: + Instead, you could use `exec()`: ```Python heroes = session.exec(select(Hero)).all() @@ -119,7 +119,7 @@ def execute( 🚨 You probably want to use `session.exec()` instead of `session.execute()`. This is the original SQLAlchemy `session.execute()` method that returns objects - of type `Row`, and that you have to call `scalars()` to get the model objects. + of type `Row`, on which you have to call `scalars()` to get the model objects. For example: @@ -127,7 +127,7 @@ def execute( heroes = session.execute(select(Hero)).scalars().all() ``` - instead you could use `exec()`: + Instead, you could use `exec()`: ```Python heroes = session.exec(select(Hero)).all() @@ -146,10 +146,10 @@ def execute( """ 🚨 You probably want to use `session.exec()` instead of `session.query()`. - `session.exec()` is SQLModel's own short version with increased type + `session.exec()` is SQLModel's own short version with improved type annotations. - Or otherwise you might want to use `session.execute()` instead of + Otherwise, you might want to use `session.execute()` instead of `session.query()`. """ ) @@ -159,10 +159,10 @@ def query( # type: ignore """ 🚨 You probably want to use `session.exec()` instead of `session.query()`. - `session.exec()` is SQLModel's own short version with increased type + `session.exec()` is SQLModel's own short version with improved type annotations. - Or otherwise you might want to use `session.execute()` instead of + Otherwise, you might want to use `session.execute()` instead of `session.query()`. """ return super().query(*entities, **kwargs) diff --git a/sqlmodel/sql/_expression_select_cls.py b/sqlmodel/sql/_expression_select_cls.py index 124929d2c3..68e9aacfac 100644 --- a/sqlmodel/sql/_expression_select_cls.py +++ b/sqlmodel/sql/_expression_select_cls.py @@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self: """Return a new `Select` construct with the given expression added to its `WHERE` clause, joined to the existing clause via `AND`, if any. """ - return super().where(*whereclause) + return super().where(*whereclause) # ty: ignore[invalid-argument-type] def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self: """Return a new `Select` construct with the given expression added to its `HAVING` clause, joined to the existing clause via `AND`, if any. """ - return super().having(*having) + return super().having(*having) # ty: ignore[invalid-argument-type] class Select(SelectBase[_T]): diff --git a/sqlmodel/sql/sqltypes.py b/sqlmodel/sql/sqltypes.py index 3119b1b624..24d847899a 100644 --- a/sqlmodel/sql/sqltypes.py +++ b/sqlmodel/sql/sqltypes.py @@ -1,7 +1,52 @@ +from datetime import datetime, timedelta, timezone from typing import Any, cast from sqlalchemy import types from sqlalchemy.engine.interfaces import Dialect +from sqlalchemy.sql.operators import OperatorType + + +class UTCDateTime(types.TypeDecorator[datetime]): + """Store aware datetimes and return them in UTC.""" + + impl = types.DateTime + cache_ok = True + + def __init__(self) -> None: + super().__init__(timezone=True) + + def __repr__(self) -> str: + return "UTCDateTime()" + + def coerce_compared_value( + self, op: OperatorType | None, value: Any + ) -> types.TypeEngine[Any]: + if isinstance(value, timedelta): + return types.Interval() + return self + + def process_bind_param( + self, value: datetime | None, dialect: Dialect + ) -> datetime | None: + if value is None: + return None + if value.utcoffset() is None: + raise ValueError( + "Datetime values must have timezone information. " + "Use datetime.now(timezone.utc), or annotate the field with " + "NaiveDatetime for naive storage." + ) + return value.astimezone(timezone.utc) + + def process_result_value( + self, value: datetime | None, dialect: Dialect + ) -> datetime | None: + if value is None: + return None + if value.utcoffset() is None: + # Databases without timezone support store UTC without an offset. + return value.replace(tzinfo=timezone.utc) + return value.astimezone(timezone.utc) class AutoString(types.TypeDecorator): @@ -11,6 +56,6 @@ class AutoString(types.TypeDecorator): def load_dialect_impl(self, dialect: Dialect) -> "types.TypeEngine[Any]": impl = cast(types.String, self.impl) - if impl.length is None and dialect.name == "mysql": + if impl.length is None and dialect.name in {"mysql", "mariadb"}: return dialect.type_descriptor(types.String(self.mysql_default_length)) return super().load_dialect_impl(dialect) diff --git a/tests/conftest.py b/tests/conftest.py index 9e241eb51c..1595deffb0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ +import os import shutil import subprocess +import sys from collections.abc import Callable, Generator from dataclasses import dataclass, field from pathlib import Path @@ -8,7 +10,10 @@ import pytest from pydantic import BaseModel -from sqlmodel import SQLModel +from sqlalchemy import Engine +from sqlalchemy.engine import make_url +from sqlalchemy.pool import StaticPool +from sqlmodel import SQLModel, create_engine from sqlmodel.main import default_registry top_level_path = Path(__file__).resolve().parent.parent @@ -26,6 +31,25 @@ def clear_sqlmodel() -> Any: default_registry.dispose() +@pytest.fixture() +def database_engine(clear_sqlmodel: Any) -> Generator[Engine, None, None]: + url = make_url(os.environ.get("DATABASE_URL", "sqlite://")) + if url.get_backend_name() == "sqlite": + engine = create_engine( + url, connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + else: + engine = create_engine(url) + try: + yield engine + finally: + # Drop tables before clear_sqlmodel removes their metadata. + try: + SQLModel.metadata.drop_all(engine) + finally: + engine.dispose() + + @pytest.fixture() def cov_tmp_path(tmp_path: Path) -> Generator[Path, None, None]: yield tmp_path @@ -83,3 +107,8 @@ def print_mock_fixture() -> Generator[PrintMock, None, None]: new_print = get_testing_print_function(print_mock.calls) with patch("builtins.print", new=new_print): yield print_mock + + +needs_py311 = pytest.mark.skipif( + sys.version_info < (3, 11), reason="requires Python 3.11+" +) diff --git a/tests/test_advanced/test_datetime/__init__.py b/tests/test_advanced/test_datetime/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_advanced/test_datetime/test_tutorial001.py b/tests/test_advanced/test_datetime/test_tutorial001.py new file mode 100644 index 0000000000..c1088ede9b --- /dev/null +++ b/tests/test_advanced/test_datetime/test_tutorial001.py @@ -0,0 +1,46 @@ +import importlib +from datetime import datetime, timedelta, timezone +from types import ModuleType + +import pytest +from sqlalchemy import Engine +from sqlmodel import SQLModel + +from ...conftest import PrintMock, needs_py311 + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001_py310"), + pytest.param("tutorial001_py311", marks=needs_py311), + ], +) +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: + mod = importlib.import_module(f"docs_src.advanced.datetime.{request.param}") + mod.engine = database_engine + return mod + + +def test_tutorial(print_mock: PrintMock, mod: ModuleType) -> None: + before = datetime.now(timezone.utc) + mod.main() + after = datetime.now(timezone.utc) + + created_at = print_mock.calls[0][1]["created_at"] + assert created_at.tzinfo is timezone.utc + assert before <= created_at <= after + stored_created_at = print_mock.calls[1][1]["created_at"] + assert stored_created_at.tzinfo is timezone.utc + if mod.engine.dialect.name in {"mysql", "mariadb"}: + # The default DATETIME columns store whole seconds. + assert abs(stored_created_at - created_at) < timedelta(seconds=1) + else: + assert stored_created_at == created_at + assert print_mock.calls == [ + ["Event:", {"id": None, "created_at": created_at}], + ["Event from database:", {"id": 1, "created_at": stored_created_at}], + ] + + table = SQLModel.metadata.tables["event"] + assert table.c.created_at.type.timezone is True diff --git a/tests/test_advanced/test_datetime/test_tutorial002.py b/tests/test_advanced/test_datetime/test_tutorial002.py new file mode 100644 index 0000000000..85111a8bfa --- /dev/null +++ b/tests/test_advanced/test_datetime/test_tutorial002.py @@ -0,0 +1,50 @@ +import importlib +from datetime import datetime, timezone +from types import ModuleType + +import pytest +from pydantic import ValidationError +from sqlmodel import SQLModel + +from ...conftest import PrintMock + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial002_py310"), + ], +) +def get_module(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module(f"docs_src.advanced.datetime.{request.param}") + + +def test_tutorial(print_mock: PrintMock, mod: ModuleType) -> None: + mod.main() + assert print_mock.calls == [ + [ + "Event:", + { + "id": None, + "starts_at": datetime(2026, 1, 1, 12, tzinfo=timezone.utc), + "local_time": datetime(2026, 1, 1, 12), + }, + ] + ] + + with pytest.raises(ValidationError) as exc_info: + mod.Event.model_validate( + { + "starts_at": "2026-01-01T12:00:00", + "local_time": "2026-01-01T12:00:00Z", + } + ) + errors = exc_info.value.errors() + assert [(error["loc"], error["type"]) for error in errors] == [ + (("starts_at",), "timezone_aware"), + (("local_time",), "timezone_naive"), + ] + + table = SQLModel.metadata.tables["event"] + assert table.c.starts_at.type.timezone is True + assert table.c.local_time.type.timezone is False diff --git a/tests/test_advanced/test_datetime/test_tutorial003.py b/tests/test_advanced/test_datetime/test_tutorial003.py new file mode 100644 index 0000000000..f205226fc2 --- /dev/null +++ b/tests/test_advanced/test_datetime/test_tutorial003.py @@ -0,0 +1,31 @@ +import importlib +from datetime import datetime, timezone +from types import ModuleType + +import pytest +from sqlmodel import SQLModel + +from ...conftest import PrintMock + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial003_py310"), + ], +) +def get_module(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module(f"docs_src.advanced.datetime.{request.param}") + + +def test_tutorial(print_mock: PrintMock, mod: ModuleType) -> None: + mod.main() + assert print_mock.calls == [ + ["Event:", {"id": None, "created_at": datetime(2026, 1, 1, 12)}] + ] + + aware = datetime(2026, 1, 1, 12, tzinfo=timezone.utc) + assert mod.Event.model_validate({"created_at": aware}).created_at == aware + + table = SQLModel.metadata.tables["event"] + assert table.c.created_at.type.timezone is False diff --git a/tests/test_advanced/test_datetime/test_tutorial004.py b/tests/test_advanced/test_datetime/test_tutorial004.py new file mode 100644 index 0000000000..64fb4beaed --- /dev/null +++ b/tests/test_advanced/test_datetime/test_tutorial004.py @@ -0,0 +1,35 @@ +import importlib +from io import StringIO +from types import ModuleType + +import pytest +from alembic.migration import MigrationContext +from alembic.operations import Operations + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial004_py310"), + ], +) +def get_module(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module(f"docs_src.advanced.datetime.{request.param}") + + +def test_tutorial(mod: ModuleType) -> None: + output = StringIO() + context = MigrationContext.configure( + dialect_name="postgresql", + opts={"as_sql": True, "output_buffer": output}, + ) + with Operations.context(context): + mod.upgrade() + mod.downgrade() + + assert output.getvalue() == ( + "ALTER TABLE event ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE " + "USING created_at AT TIME ZONE 'UTC';\n\n" + "ALTER TABLE event ALTER COLUMN created_at TYPE TIMESTAMP WITHOUT TIME ZONE " + "USING created_at AT TIME ZONE 'UTC';\n\n" + ) diff --git a/tests/test_advanced/test_datetime/test_tutorial005.py b/tests/test_advanced/test_datetime/test_tutorial005.py new file mode 100644 index 0000000000..06b2a80e4e --- /dev/null +++ b/tests/test_advanced/test_datetime/test_tutorial005.py @@ -0,0 +1,38 @@ +import importlib +from datetime import datetime, timezone +from types import ModuleType + +import pytest +from pydantic import ValidationError +from sqlmodel import SQLModel + +from ...conftest import PrintMock + + +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial005_py310"), + ], +) +def get_module(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module(f"docs_src.advanced.datetime.{request.param}") + + +def test_tutorial(print_mock: PrintMock, mod: ModuleType) -> None: + mod.main() + assert print_mock.calls == [ + ["Event:", {"id": None, "created_at": datetime(2026, 1, 1, 12)}] + ] + + with pytest.raises(ValidationError) as exc_info: + mod.Event.model_validate( + {"created_at": datetime(2026, 1, 1, 12, tzinfo=timezone.utc)} + ) + errors = exc_info.value.errors() + assert [(error["loc"], error["type"]) for error in errors] == [ + (("created_at",), "timezone_naive"), + ] + + table = SQLModel.metadata.tables["event"] + assert table.c.created_at.type.timezone is False diff --git a/tests/test_advanced/test_decimal/test_tutorial001.py b/tests/test_advanced/test_decimal/test_tutorial001.py index 0350190003..1e91f318f4 100644 --- a/tests/test_advanced/test_decimal/test_tutorial001.py +++ b/tests/test_advanced/test_decimal/test_tutorial001.py @@ -3,7 +3,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -14,10 +14,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.advanced.decimal.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_advanced/test_uuid/test_tutorial001.py b/tests/test_advanced/test_uuid/test_tutorial001.py index ce0a330f21..426304f456 100644 --- a/tests/test_advanced/test_uuid/test_tutorial001.py +++ b/tests/test_advanced/test_uuid/test_tutorial001.py @@ -3,7 +3,7 @@ import pytest from dirty_equals import IsUUID -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -14,10 +14,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.advanced.uuid.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_advanced/test_uuid/test_tutorial002.py b/tests/test_advanced/test_uuid/test_tutorial002.py index 1035771502..10e5df55bb 100644 --- a/tests/test_advanced/test_uuid/test_tutorial002.py +++ b/tests/test_advanced/test_uuid/test_tutorial002.py @@ -3,7 +3,7 @@ import pytest from dirty_equals import IsUUID -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -14,10 +14,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.advanced.uuid.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_annotated_uuid.py b/tests/test_annotated_uuid.py index 44d5ced780..91cce572ef 100644 --- a/tests/test_annotated_uuid.py +++ b/tests/test_annotated_uuid.py @@ -1,16 +1,17 @@ import uuid -from sqlmodel import Field, Session, SQLModel, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Field, Session, SQLModel, select -def test_annotated_optional_types(clear_sqlmodel) -> None: +def test_annotated_optional_types(database_engine: Engine) -> None: from pydantic import UUID4 class Hero(SQLModel, table=True): # Pydantic UUID4 is: Annotated[UUID, UuidVersion(4)] id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True) - engine = create_engine("sqlite:///:memory:") + engine = database_engine SQLModel.metadata.create_all(engine) with Session(engine) as db: hero = Hero() diff --git a/tests/test_dataclass_transform.py b/tests/test_dataclass_transform.py new file mode 100644 index 0000000000..52c4b54712 --- /dev/null +++ b/tests/test_dataclass_transform.py @@ -0,0 +1,25 @@ +from typing import TYPE_CHECKING + +from sqlmodel import Field, SQLModel + + +def test_field_parameters() -> None: + class Hero(SQLModel): + name: str = Field() + secret_name: str = Field(alias="secretName") + age: int = Field(default=42) + tags: list[str] = Field(default_factory=list) + + hero = Hero(name="Deadpond", secretName="Dive Wilson") + other = Hero(name="Spider-Boy", secretName="Pedro Parqueador") + + assert hero.name == "Deadpond" + assert hero.secret_name == "Dive Wilson" + assert hero.age == 42 + assert hero.tags == [] + assert hero.tags is not other.tags + + if TYPE_CHECKING: + Hero(secretName="Dive Wilson") # ty: ignore[missing-argument] + Hero("Deadpond", "Dive Wilson") # ty: ignore[too-many-positional-arguments, missing-argument] + Hero(name=123, secretName="Dive Wilson") # ty: ignore[invalid-argument-type] diff --git a/tests/test_datetime.py b/tests/test_datetime.py new file mode 100644 index 0000000000..0f3e7fef32 --- /dev/null +++ b/tests/test_datetime.py @@ -0,0 +1,342 @@ +from collections.abc import Callable +from datetime import date, datetime, time, timedelta, timezone, tzinfo +from io import StringIO +from operator import add, sub +from typing import Annotated, Any + +import pytest +from alembic.autogenerate import compare_metadata, render_python_code +from alembic.migration import MigrationContext +from alembic.operations import Operations, ops +from pydantic import AwareDatetime, NaiveDatetime, ValidationError +from sqlalchemy import Column, Date, DateTime, Engine, Interval, Time +from sqlalchemy.dialects import mysql, postgresql, sqlite +from sqlalchemy.exc import StatementError +from sqlalchemy.schema import CreateTable +from sqlmodel import ( + Field, + Session, + SQLModel, + UTCDateTime, + create_engine, + select, + update, +) + + +@pytest.mark.parametrize("nullable", [False, True]) +@pytest.mark.parametrize( + "annotation, timezone_enabled, error_type", + [ + (datetime, True, None), + (AwareDatetime, True, "timezone_aware"), + (NaiveDatetime, False, "timezone_naive"), + (Annotated[datetime, AwareDatetime], True, "timezone_aware"), + (Annotated[datetime, NaiveDatetime], False, "timezone_naive"), + (Annotated[datetime, AwareDatetime()], True, "timezone_aware"), + (Annotated[datetime, NaiveDatetime()], False, "timezone_naive"), + (Annotated[NaiveDatetime, AwareDatetime], True, "timezone_aware"), + (Annotated[AwareDatetime, NaiveDatetime], False, "timezone_naive"), + (Annotated[datetime, NaiveDatetime, AwareDatetime], True, "timezone_aware"), + (Annotated[datetime, AwareDatetime, NaiveDatetime], False, "timezone_naive"), + (Annotated[datetime, "other metadata"], True, None), + ], +) +def test_datetime_types( + annotation: Any, timezone_enabled: bool, error_type: str | None, nullable: bool +) -> None: + if nullable: + annotation = annotation | None + + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: annotation + + column = SQLModel.metadata.tables["event"].c.occurred_at + assert isinstance(column.type, UTCDateTime if timezone_enabled else DateTime) + assert column.type.timezone is timezone_enabled + assert column.nullable is nullable + + aware = datetime(2026, 1, 1, 12, tzinfo=timezone(timedelta(hours=2))) + naive = datetime(2026, 1, 1, 12) + valid = aware if timezone_enabled else naive + assert Event.model_validate({"occurred_at": valid}).occurred_at == valid + if error_type is None: + assert Event.model_validate({"occurred_at": naive}).occurred_at == naive + else: + invalid = naive if timezone_enabled else aware + with pytest.raises(ValidationError) as exc_info: + Event.model_validate({"occurred_at": invalid}) + assert exc_info.value.errors()[0]["type"] == error_type + if nullable: + assert Event.model_validate({"occurred_at": None}).occurred_at is None + + +def test_inherited_annotated_datetime() -> None: + class EventBase(SQLModel): + occurred_at: Annotated[datetime, NaiveDatetime, Field(index=True)] + + class Event(EventBase, table=True): + id: int | None = Field(default=None, primary_key=True) + + column = SQLModel.metadata.tables["event"].c.occurred_at + assert column.type.timezone is False + assert column.index is True + + +@pytest.mark.parametrize("annotation", [datetime, AwareDatetime, NaiveDatetime]) +@pytest.mark.parametrize("timezone_enabled", [False, True]) +@pytest.mark.parametrize("use_column", [False, True]) +def test_explicit_datetime_type( + annotation: Any, timezone_enabled: bool, use_column: bool +) -> None: + sa_type = DateTime(timezone=timezone_enabled) + if use_column: + field = Field(sa_column=Column(sa_type)) + else: + field = Field(sa_type=sa_type) + + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: annotation = field + + assert SQLModel.metadata.tables["event"].c.occurred_at.type is sa_type + + +def test_explicit_datetime_type_class() -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime = Field(sa_type=DateTime) + + assert SQLModel.metadata.tables["event"].c.occurred_at.type.timezone is False + + +def test_other_temporal_types() -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + day: date + at: time + duration: timedelta + + columns = SQLModel.metadata.tables["event"].c + assert isinstance(columns.day.type, Date) + assert isinstance(columns.at.type, Time) + assert isinstance(columns.duration.type, Interval) + + +@pytest.mark.parametrize( + "dialect, aware_type, naive_type", + [ + ( + postgresql.dialect(), + "TIMESTAMP WITH TIME ZONE", + "TIMESTAMP WITHOUT TIME ZONE", + ), + (mysql.dialect(), "DATETIME", "DATETIME"), + (sqlite.dialect(), "DATETIME", "DATETIME"), + ], +) +def test_datetime_column_types(dialect: Any, aware_type: str, naive_type: str) -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + local_time: NaiveDatetime + + ddl = str(CreateTable(SQLModel.metadata.tables["event"]).compile(dialect=dialect)) + assert f"occurred_at {aware_type} NOT NULL" in ddl + assert f"local_time {naive_type} NOT NULL" in ddl + + +def test_datetime_round_trip(database_engine: Engine) -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + local_time: NaiveDatetime + optional_time: datetime | None = None + + aware = datetime(2026, 1, 1, 12, tzinfo=timezone(timedelta(hours=2))) + naive = datetime(2026, 1, 1, 12) + engine = database_engine + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + event = Event(occurred_at=aware, local_time=naive) + session.add(event) + session.flush() + # Binding doesn't change the original value on the model. + assert event.occurred_at is aware + session.commit() + session.refresh(event) + assert event.occurred_at == aware + assert event.occurred_at.tzinfo is timezone.utc + assert event.local_time == naive + assert event.optional_time is None + event_id = event.id + + with Session(engine) as session: + loaded = session.get(Event, event_id) + assert loaded is not None + assert loaded.occurred_at.tzinfo is timezone.utc + # Query parameters use the same UTC normalization as inserted values. + assert ( + session.exec(select(Event).where(Event.occurred_at == aware)).one() + is loaded + ) + assert session.exec(select(Event.occurred_at)).one().tzinfo is timezone.utc + later = aware + timedelta(hours=1) + session.exec(update(Event).values(occurred_at=later)) + session.commit() + session.refresh(loaded) + assert loaded.occurred_at == later + assert loaded.occurred_at.tzinfo is timezone.utc + + +class UndefinedOffset(tzinfo): + def utcoffset(self, dt: datetime | None) -> None: + return None + + +@pytest.mark.parametrize("offset", [None, UndefinedOffset()]) +@pytest.mark.parametrize("operation", ["insert", "update", "filter"]) +def test_naive_database_parameters( + database_engine: Engine, offset: tzinfo | None, operation: str +) -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + + naive = datetime(2026, 1, 1, 12, tzinfo=offset) + event = Event.model_validate({"occurred_at": naive}) + # Plain datetime validation remains permissive, including a tzinfo object + # that doesn't actually provide an offset. + assert event.occurred_at is naive + engine = database_engine + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + with pytest.raises(StatementError, match="timezone information") as exc_info: + if operation == "insert": + session.add(event) + session.flush() + elif operation == "update": + session.exec(update(Event).values(occurred_at=naive)) + else: + session.exec(select(Event).where(Event.occurred_at > naive)) + assert isinstance(exc_info.value.orig, ValueError) + assert "NaiveDatetime" in str(exc_info.value.orig) + + +@pytest.mark.parametrize("timezone_enabled", [False, True]) +def test_sqlite_explicit_datetime_bypasses_utc_processing( + timezone_enabled: bool, +) -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime = Field(sa_type=DateTime(timezone=timezone_enabled)) + + aware = datetime(2026, 1, 1, 12, tzinfo=timezone(timedelta(hours=2))) + engine = create_engine("sqlite://") + SQLModel.metadata.create_all(engine) + with Session(engine) as session: + event = Event(occurred_at=aware) + session.add(event) + session.commit() + session.refresh(event) + assert event.occurred_at == datetime(2026, 1, 1, 12) + assert event.occurred_at.tzinfo is None + + +def test_postgresql_datetime_processing() -> None: + # PostgreSQL drivers receive aware parameters and may return values in the + # connection's timezone. Exercise the complete SQLAlchemy type processors. + dialect = postgresql.dialect() + type_ = UTCDateTime().dialect_impl(dialect) + bind = type_.bind_processor(dialect) + result = type_.result_processor(dialect, None) + assert bind is not None + assert result is not None + aware = datetime(2026, 1, 1, 12, tzinfo=timezone(timedelta(hours=2))) + bound = bind(aware) + assert bound == aware + assert bound.tzinfo is timezone.utc + loaded = result(aware) + assert loaded == aware + assert loaded.tzinfo is timezone.utc + assert bind(None) is None + assert result(None) is None + + +@pytest.mark.parametrize( + "operation, reverse", + [(add, False), (sub, False), (add, True)], +) +def test_postgresql_datetime_interval_arithmetic( + operation: Callable[[Any, Any], Any], reverse: bool +) -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + + occurred_at = SQLModel.metadata.tables["event"].c.occurred_at + duration = timedelta(days=30) + expression = ( + operation(duration, occurred_at) + if reverse + else operation(occurred_at, duration) + ) + # Adding or subtracting a duration still produces a UTC datetime. + assert isinstance(expression.type, UTCDateTime) + # Exercise the actual parameter processor used for database execution. + parameter = expression.left if reverse else expression.right + dialect = postgresql.dialect() + bind = parameter.type.dialect_impl(dialect).bind_processor(dialect) + bound = bind(parameter.value) if bind is not None else parameter.value + assert bound == duration + + +def test_existing_sqlite_utc_data() -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + + engine = create_engine("sqlite://") + with engine.begin() as connection: + connection.exec_driver_sql( + "CREATE TABLE event (id INTEGER NOT NULL PRIMARY KEY, " + "occurred_at DATETIME NOT NULL)" + ) + connection.exec_driver_sql( + "INSERT INTO event VALUES (1, '2026-01-01 12:00:00.000000')" + ) + context = MigrationContext.configure(connection, opts={"compare_type": True}) + assert compare_metadata(context, SQLModel.metadata) == [] + + with Session(engine) as session: + event = session.get(Event, 1) + assert event is not None + assert event.occurred_at == datetime(2026, 1, 1, 12, tzinfo=timezone.utc) + assert event.occurred_at.tzinfo is timezone.utc + + +def test_alembic_datetime_rendering() -> None: + class Event(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + occurred_at: datetime + + table = SQLModel.metadata.tables["event"] + migration = ops.UpgradeOps(ops=[ops.CreateTableOp.from_table(table)]) + rendered = render_python_code(migration) + assert "sqlmodel.sql.sqltypes.UTCDateTime()" in rendered + # Execute the generated migration, including the custom type constructor. + namespace: dict[str, Any] = {} + exec( + "import sqlalchemy as sa\nimport sqlmodel.sql.sqltypes\n" + "from alembic import op\ndef upgrade():\n " + rendered, + namespace, + ) + output = StringIO() + context = MigrationContext.configure( + dialect_name="postgresql", + opts={"as_sql": True, "output_buffer": output}, + ) + with Operations.context(context): + namespace["upgrade"]() + assert "occurred_at TIMESTAMP WITH TIME ZONE NOT NULL" in output.getvalue() diff --git a/tests/test_field_sa_args_kwargs.py b/tests/test_field_sa_args_kwargs.py index 4a67e8e826..3daabe5e37 100644 --- a/tests/test_field_sa_args_kwargs.py +++ b/tests/test_field_sa_args_kwargs.py @@ -1,8 +1,11 @@ -from sqlalchemy import ForeignKey -from sqlmodel import Field, SQLModel, create_engine +from typing import Any +import pytest +from sqlalchemy import Engine, ForeignKey, Integer, String, inspect +from sqlmodel import Field, SQLModel -def test_sa_column_args(clear_sqlmodel, caplog) -> None: + +def test_sa_column_args(database_engine: Engine) -> None: class Team(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str @@ -14,24 +17,38 @@ class Hero(SQLModel, table=True): sa_column_args=[ForeignKey("team.id")], ) - engine = create_engine("sqlite://", echo=True) + engine = database_engine SQLModel.metadata.create_all(engine) - create_table_log = [ - message for message in caplog.messages if "CREATE TABLE hero" in message - ][0] - assert "FOREIGN KEY(team_id) REFERENCES team (id)" in create_table_log + foreign_keys = inspect(engine).get_foreign_keys("hero") + assert len(foreign_keys) == 1 + assert foreign_keys[0]["constrained_columns"] == ["team_id"] + assert foreign_keys[0]["referred_table"] == "team" + assert foreign_keys[0]["referred_columns"] == ["id"] -def test_sa_column_kargs(clear_sqlmodel, caplog) -> None: +def test_sa_column_kargs(database_engine: Engine) -> None: class Item(SQLModel, table=True): id: int | None = Field( default=None, sa_column_kwargs={"primary_key": True}, ) - engine = create_engine("sqlite://", echo=True) + engine = database_engine SQLModel.metadata.create_all(engine) - create_table_log = [ - message for message in caplog.messages if "CREATE TABLE item" in message - ][0] - assert "PRIMARY KEY (id)" in create_table_log + primary_key = inspect(engine).get_pk_constraint("item") + assert primary_key["constrained_columns"] == ["id"] + + +@pytest.mark.parametrize( + "field_kwargs", + [ + {"sa_column_kwargs": {"type_": Integer}}, + {"sa_type": String, "sa_column_kwargs": {"type_": Integer}}, + ], +) +def test_sa_column_kwargs_type_raises(field_kwargs: dict[str, Any]) -> None: + with pytest.raises( + RuntimeError, + match="Passing type_ is not supported in sa_column_kwargs, use sa_type instead", + ): + Field(**field_kwargs) diff --git a/tests/test_field_sa_type.py b/tests/test_field_sa_type.py new file mode 100644 index 0000000000..7cd774ba46 --- /dev/null +++ b/tests/test_field_sa_type.py @@ -0,0 +1,22 @@ +from typing import Any, cast + +from sqlalchemy import Integer, String +from sqlmodel import Field, SQLModel + + +def test_sa_type_class() -> None: + class Item(SQLModel, table=True): + id: int = Field(primary_key=True, sa_type=Integer) + + id_column = cast(Any, Item).id + assert isinstance(id_column.type, Integer) + + +def test_sa_type_instance() -> None: + class Item(SQLModel, table=True): + id: int = Field(primary_key=True) + name: str = Field(sa_type=String(50)) + + name_column = cast(Any, Item).name + assert isinstance(name_column.type, String) + assert name_column.type.length == 50 diff --git a/tests/test_future_annotations.py b/tests/test_future_annotations.py index 93f39a33fe..8a59ff358b 100644 --- a/tests/test_future_annotations.py +++ b/tests/test_future_annotations.py @@ -2,10 +2,11 @@ from typing import Annotated -from sqlmodel import Field, Session, SQLModel, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Field, Session, SQLModel, select -def test_model_with_future_annotations(clear_sqlmodel): +def test_model_with_future_annotations(database_engine: Engine): class Hero(SQLModel, table=True): id: Annotated[int | None, Field(primary_key=True)] = None name: str @@ -14,7 +15,7 @@ class Hero(SQLModel, table=True): hero = Hero(name="Deadpond", secret_name="Dive Wilson", age=25) - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) with Session(engine) as session: @@ -33,7 +34,7 @@ class Hero(SQLModel, table=True): assert heroes[0].name == "Deadpond" -def test_model_with_string_annotations(clear_sqlmodel): +def test_model_with_string_annotations(database_engine: Engine): class Team(SQLModel, table=True): id: Annotated[int | None, Field(primary_key=True)] = None name: str @@ -43,7 +44,7 @@ class Player(SQLModel, table=True): name: str team_id: Annotated[int | None, Field(foreign_key="team.id")] = None - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) team = Team(name="Champions") diff --git a/tests/test_instance_no_args.py b/tests/test_instance_no_args.py index 72680dfff9..df52677559 100644 --- a/tests/test_instance_no_args.py +++ b/tests/test_instance_no_args.py @@ -1,15 +1,16 @@ import pytest from pydantic import ValidationError -from sqlmodel import Field, Session, SQLModel, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Field, Session, SQLModel, select -def test_allow_instantiation_without_arguments(clear_sqlmodel): +def test_allow_instantiation_without_arguments(database_engine: Engine): class Item(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str description: str | None = None - engine = create_engine("sqlite:///:memory:") + engine = database_engine SQLModel.metadata.create_all(engine) with Session(engine) as db: item = Item() @@ -20,7 +21,6 @@ class Item(SQLModel, table=True): result = db.exec(statement).all() assert len(result) == 1 assert isinstance(item.id, int) - SQLModel.metadata.clear() def test_not_allow_instantiation_without_arguments_if_not_table(): diff --git a/tests/test_main.py b/tests/test_main.py index fa40b71853..66193e1706 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,12 +1,15 @@ from typing import Annotated import pytest +from sqlalchemy import Engine from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import RelationshipProperty -from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select +from sqlmodel import Field, Relationship, Session, SQLModel, select -def test_should_allow_duplicate_row_if_unique_constraint_is_not_passed(clear_sqlmodel): +def test_should_allow_duplicate_row_if_unique_constraint_is_not_passed( + database_engine: Engine, +): class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str @@ -16,7 +19,7 @@ class Hero(SQLModel, table=True): hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson") - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) @@ -36,7 +39,9 @@ class Hero(SQLModel, table=True): assert heroes[0].name == heroes[1].name -def test_should_allow_duplicate_row_if_unique_constraint_is_false(clear_sqlmodel): +def test_should_allow_duplicate_row_if_unique_constraint_is_false( + database_engine: Engine, +): class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str @@ -46,7 +51,7 @@ class Hero(SQLModel, table=True): hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson") - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) @@ -67,7 +72,7 @@ class Hero(SQLModel, table=True): def test_should_raise_exception_when_try_to_duplicate_row_if_unique_constraint_is_true( - clear_sqlmodel, + database_engine: Engine, ): class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) @@ -78,7 +83,7 @@ class Hero(SQLModel, table=True): hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson") - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) @@ -93,7 +98,7 @@ class Hero(SQLModel, table=True): session.commit() -def test_sa_relationship_property(clear_sqlmodel): +def test_sa_relationship_property(database_engine: Engine): """Test https://github.com/tiangolo/sqlmodel/issues/315#issuecomment-1272122306""" class Team(SQLModel, table=True): @@ -114,7 +119,7 @@ class Hero(SQLModel, table=True): team_preventers = Team(name="Preventers") hero_rusty_man = Hero(name="Rusty-Man", team=team_preventers) - engine = create_engine("sqlite://", echo=True) + engine = database_engine SQLModel.metadata.create_all(engine) @@ -127,13 +132,13 @@ class Hero(SQLModel, table=True): assert hero_rusty_man.team.name == "Preventers" -def test_composite_primary_key(clear_sqlmodel): +def test_composite_primary_key(database_engine: Engine): class UserPermission(SQLModel, table=True): user_id: int = Field(primary_key=True) resource_id: int = Field(primary_key=True) permission: str - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) pk_column_names = {column.name for column in UserPermission.__table__.primary_key} @@ -153,7 +158,7 @@ class UserPermission(SQLModel, table=True): session.commit() -def test_composite_primary_key_and_validator(clear_sqlmodel): +def test_composite_primary_key_and_validator(database_engine: Engine): from pydantic import AfterValidator def validate_resource_id(value: int) -> int: @@ -168,7 +173,7 @@ class UserPermission(SQLModel, table=True): ) permission: str - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) pk_column_names = {column.name for column in UserPermission.__table__.primary_key} @@ -188,7 +193,7 @@ class UserPermission(SQLModel, table=True): session.commit() -def test_foreign_key_ondelete_with_annotated(clear_sqlmodel): +def test_foreign_key_ondelete_with_annotated(database_engine: Engine): from pydantic import AfterValidator def ensure_positive(value: int) -> int: @@ -208,7 +213,7 @@ class Hero(SQLModel, table=True): ) name: str - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) team_id_column = Hero.__table__.c.team_id # type: ignore[attr-defined] diff --git a/tests/test_nullable.py b/tests/test_nullable.py index db028304ab..9c6bb45fe0 100644 --- a/tests/test_nullable.py +++ b/tests/test_nullable.py @@ -1,9 +1,10 @@ import pytest +from sqlalchemy import Engine, inspect from sqlalchemy.exc import IntegrityError from sqlmodel import Field, Session, SQLModel, create_engine -def test_nullable_fields(clear_sqlmodel, caplog): +def test_nullable_fields(database_engine: Engine): class Hero(SQLModel, table=True): primary_key: int | None = Field( default=None, @@ -48,37 +49,38 @@ class Hero(SQLModel, table=True): str_default_ellipsis_non_nullable: str = Field(default=..., nullable=False) str_default_ellipsis_nullable: str = Field(default=..., nullable=True) - engine = create_engine("sqlite://", echo=True) + engine = database_engine SQLModel.metadata.create_all(engine) - create_table_log = [ - message for message in caplog.messages if "CREATE TABLE hero" in message - ][0] - assert "primary_key INTEGER NOT NULL," in create_table_log - assert "required_value VARCHAR NOT NULL," in create_table_log - assert "optional_default_ellipsis VARCHAR," in create_table_log - assert "optional_default_none VARCHAR," in create_table_log - assert "optional_non_nullable VARCHAR NOT NULL," in create_table_log - assert "optional_nullable VARCHAR," in create_table_log - assert ( - "optional_default_ellipses_non_nullable VARCHAR NOT NULL," in create_table_log - ) - assert "optional_default_ellipses_nullable VARCHAR," in create_table_log - assert "optional_default_none_non_nullable VARCHAR NOT NULL," in create_table_log - assert "optional_default_none_nullable VARCHAR," in create_table_log - assert "default_ellipses_non_nullable VARCHAR NOT NULL," in create_table_log - assert "optional_default_str VARCHAR," in create_table_log - assert "optional_default_str_non_nullable VARCHAR NOT NULL," in create_table_log - assert "optional_default_str_nullable VARCHAR," in create_table_log - assert "str_default_str VARCHAR NOT NULL," in create_table_log - assert "str_default_str_non_nullable VARCHAR NOT NULL," in create_table_log - assert "str_default_str_nullable VARCHAR," in create_table_log - assert "str_default_ellipsis_non_nullable VARCHAR NOT NULL," in create_table_log - assert "str_default_ellipsis_nullable VARCHAR," in create_table_log + nullable = { + column["name"]: column["nullable"] + for column in inspect(engine).get_columns("hero") + } + assert nullable == { + "primary_key": False, + "required_value": False, + "optional_default_ellipsis": True, + "optional_default_none": True, + "optional_non_nullable": False, + "optional_nullable": True, + "optional_default_ellipses_non_nullable": False, + "optional_default_ellipses_nullable": True, + "optional_default_none_non_nullable": False, + "optional_default_none_nullable": True, + "default_ellipses_non_nullable": False, + "optional_default_str": True, + "optional_default_str_non_nullable": False, + "optional_default_str_nullable": True, + "str_default_str": False, + "str_default_str_non_nullable": False, + "str_default_str_nullable": True, + "str_default_ellipsis_non_nullable": False, + "str_default_ellipsis_nullable": True, + } # Test for regression in https://github.com/tiangolo/sqlmodel/issues/420 -def test_non_nullable_optional_field_with_no_default_set(clear_sqlmodel, caplog): +def test_non_nullable_optional_field_with_no_default_set(database_engine: Engine): class Hero(SQLModel, table=True): primary_key: int | None = Field( default=None, @@ -87,14 +89,17 @@ class Hero(SQLModel, table=True): optional_non_nullable_no_default: str | None = Field(nullable=False) - engine = create_engine("sqlite://", echo=True) + engine = database_engine SQLModel.metadata.create_all(engine) - create_table_log = [ - message for message in caplog.messages if "CREATE TABLE hero" in message - ][0] - assert "primary_key INTEGER NOT NULL," in create_table_log - assert "optional_non_nullable_no_default VARCHAR NOT NULL," in create_table_log + nullable = { + column["name"]: column["nullable"] + for column in inspect(engine).get_columns("hero") + } + assert nullable == { + "primary_key": False, + "optional_non_nullable_no_default": False, + } # We can create a hero with `None` set for the optional non-nullable field hero = Hero(primary_key=123, optional_non_nullable_no_default=None) @@ -105,7 +110,7 @@ class Hero(SQLModel, table=True): session.commit() -def test_nullable_primary_key(clear_sqlmodel, caplog): +def test_sqlite_nullable_primary_key(clear_sqlmodel, caplog): # Probably the weirdest corner case, it shouldn't happen anywhere, but let's test it class Hero(SQLModel, table=True): nullable_integer_primary_key: int | None = Field( diff --git a/tests/test_prepare_release.py b/tests/test_prepare_release.py new file mode 100644 index 0000000000..208c223810 --- /dev/null +++ b/tests/test_prepare_release.py @@ -0,0 +1,298 @@ +from datetime import date +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from scripts.prepare_release import ( + BumpType, + app, + bump_version, + get_release_notes_body, + update_release_notes, + update_version_file, +) + +runner = CliRunner() + + +@pytest.mark.parametrize( + ("current_version", "bump", "new_version"), + [ + ("0.0.38", "major", "1.0.0"), + ("0.0.38", "minor", "0.1.0"), + ("0.0.38", "patch", "0.0.39"), + ], +) +def test_bump_version(current_version: str, bump: BumpType, new_version: str) -> None: + assert bump_version(current_version, bump) == new_version + + +def test_update_version_file() -> None: + content = '__version__ = "0.0.38"\n\nfrom sqlalchemy.engine import create_engine\n' + + new_content = update_version_file(content, "0.0.39", Path("sqlmodel/__init__.py")) + + assert ( + new_content + == '__version__ = "0.0.39"\n\nfrom sqlalchemy.engine import create_engine\n' + ) + + +def test_update_version_file_requires_newer_version() -> None: + content = '__version__ = "0.0.38"\n' + + with pytest.raises(RuntimeError, match="must be greater"): + update_version_file(content, "0.0.38", Path("sqlmodel/__init__.py")) + + +def test_update_release_notes() -> None: + content = """# Release Notes + +## Latest Changes + +### Fixes + +* Fix something. + +## 0.0.38 (2026-04-02) + +### Fixes + +* Previous fix. +""" + + new_content = update_release_notes( + content, "0.0.39", date(2026, 5, 30), Path("docs/release-notes.md") + ) + + assert ( + new_content + == """# Release Notes + +## Latest Changes + +## 0.0.39 (2026-05-30) + +### Fixes + +* Fix something. + +## 0.0.38 (2026-04-02) + +### Fixes + +* Previous fix. +""" + ) + + +def test_update_release_notes_rejects_existing_version() -> None: + content = """# Release Notes + +## Latest Changes + +## 0.0.39 (2026-05-30) +""" + + with pytest.raises(RuntimeError, match="already contain"): + update_release_notes( + content, "0.0.39", date(2026, 5, 30), Path("docs/release-notes.md") + ) + + +def test_get_release_notes_body_with_dated_heading() -> None: + content = """# Release Notes + +## Latest Changes + +## 0.0.39 (2026-05-30) + +### Fixes + +* Fix something. + +## 0.0.38 (2026-04-02) + +### Fixes + +* Previous fix. +""" + + body = get_release_notes_body(content, "0.0.39", Path("docs/release-notes.md")) + + assert ( + body + == """### Fixes + +* Fix something. +""" + ) + + +def test_get_release_notes_body_with_plain_heading() -> None: + content = """# Release Notes + +## Latest Changes + +## 0.0.39 + +### Fixes + +* Fix something. +""" + + body = get_release_notes_body(content, "0.0.39", Path("docs/release-notes.md")) + + assert body == "### Fixes\n\n* Fix something.\n" + + +def test_get_release_notes_body_allows_non_version_h2_content() -> None: + content = """# Release Notes + +## Latest Changes + +## 0.0.39 + +## Highlights + +* Fix something. + +## 0.0.38 + +* Previous fix. +""" + + body = get_release_notes_body(content, "0.0.39", Path("docs/release-notes.md")) + + assert body == "## Highlights\n\n* Fix something.\n" + + +def test_get_release_notes_body_requires_version_section() -> None: + content = "# Release Notes\n\n## Latest Changes\n" + + with pytest.raises(RuntimeError, match="Could not find"): + get_release_notes_body(content, "0.0.39", Path("docs/release-notes.md")) + + +def test_get_release_notes_body_requires_non_empty_section() -> None: + content = """# Release Notes + +## Latest Changes + +## 0.0.39 + +## 0.0.38 + +* Previous fix. +""" + + with pytest.raises(RuntimeError, match="is empty"): + get_release_notes_body(content, "0.0.39", Path("docs/release-notes.md")) + + +def test_cli_updates_configured_files(tmp_path: Path) -> None: + version_file = tmp_path / "sqlmodel" / "__init__.py" + version_file.parent.mkdir() + version_file.write_text('__version__ = "0.0.38"\n') + release_notes_file = tmp_path / "release-notes.md" + release_notes_file.write_text( + """# Release Notes + +## Latest Changes + +### Fixes + +* Fix something. +""" + ) + + result = runner.invoke( + app, + [ + "prepare", + "patch", + "--version-file", + str(version_file), + "--release-notes-file", + str(release_notes_file), + "--date", + "2026-05-30", + ], + ) + + assert result.exit_code == 0, result.output + assert "Prepared release 0.0.39 (2026-05-30)" in result.output + assert version_file.read_text() == '__version__ = "0.0.39"\n' + assert "## 0.0.39 (2026-05-30)" in release_notes_file.read_text() + + +def test_cli_accepts_env_vars(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + version_file = tmp_path / "sqlmodel" / "__init__.py" + version_file.parent.mkdir() + version_file.write_text('__version__ = "0.0.38"\n') + release_notes_file = tmp_path / "docs" / "release-notes.md" + release_notes_file.parent.mkdir() + release_notes_file.write_text("# Release Notes\n\n## Latest Changes\n") + monkeypatch.setenv("PREPARE_RELEASE_BUMP", "minor") + monkeypatch.setenv("PREPARE_RELEASE_VERSION_FILE", str(version_file)) + monkeypatch.setenv("PREPARE_RELEASE_RELEASE_NOTES_FILE", str(release_notes_file)) + monkeypatch.setenv("PREPARE_RELEASE_DATE", "2026-05-30") + + result = runner.invoke(app, ["prepare"]) + + assert result.exit_code == 0, result.output + assert "Prepared release 0.1.0 (2026-05-30)" in result.output + assert version_file.read_text() == '__version__ = "0.1.0"\n' + assert "## 0.1.0 (2026-05-30)" in release_notes_file.read_text() + + +def test_cli_prints_current_version(tmp_path: Path) -> None: + version_file = tmp_path / "sqlmodel" / "__init__.py" + version_file.parent.mkdir() + version_file.write_text('__version__ = "0.0.39"\n') + + result = runner.invoke( + app, + [ + "current-version", + "--version-file", + str(version_file), + ], + ) + + assert result.exit_code == 0, result.output + assert result.output == "0.0.39\n" + + +def test_cli_prints_release_notes(tmp_path: Path) -> None: + version_file = tmp_path / "sqlmodel" / "__init__.py" + version_file.parent.mkdir() + version_file.write_text('__version__ = "0.0.39"\n') + release_notes_file = tmp_path / "release-notes.md" + release_notes_file.write_text( + """# Release Notes + +## Latest Changes + +## 0.0.39 (2026-05-30) + +### Fixes + +* Fix something. +""" + ) + + result = runner.invoke( + app, + [ + "release-notes", + "--version-file", + str(version_file), + "--release-notes-file", + str(release_notes_file), + ], + ) + + assert result.exit_code == 0, result.output + assert result.output == "### Fixes\n\n* Fix something.\n" diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py index 11f4150d98..e41a252852 100644 --- a/tests/test_pydantic/test_field.py +++ b/tests/test_pydantic/test_field.py @@ -1,9 +1,9 @@ from decimal import Decimal -from typing import Literal +from typing import Annotated, Any, Literal import pytest from pydantic import ValidationError -from sqlmodel import Field, SQLModel +from sqlmodel import Discriminator, Field, SQLModel, Tag def test_decimal(): @@ -47,6 +47,38 @@ class Model(SQLModel): Model(pet={"pet_type": "dog"}, n=1) # type: ignore[arg-type] +def test_discriminator_callable(): + # Example adapted from + # [Pydantic docs](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions-with-callable-discriminator): + + class Pie(SQLModel): + pass + + class ApplePie(Pie): + fruit: Literal["apple"] = "apple" + + class PumpkinPie(Pie): + filling: Literal["pumpkin"] = "pumpkin" + + def get_discriminator_value(v: Any) -> str: + if isinstance(v, dict): + return v.get("fruit", v.get("filling")) + return getattr(v, "fruit", getattr(v, "filling", None)) + + class ThanksgivingDinner(SQLModel): + dessert: ( + Annotated[ApplePie, Tag("apple")] | Annotated[PumpkinPie, Tag("pumpkin")] + ) = Field( + discriminator=Discriminator(get_discriminator_value), + ) + + apple_pie = ThanksgivingDinner.model_validate({"dessert": {"fruit": "apple"}}) + assert isinstance(apple_pie.dessert, ApplePie) + + pumpkin_pie = ThanksgivingDinner.model_validate({"dessert": {"filling": "pumpkin"}}) + assert isinstance(pumpkin_pie.dessert, PumpkinPie) + + def test_repr(): class Model(SQLModel): id: int | None = Field(primary_key=True) @@ -54,3 +86,37 @@ class Model(SQLModel): instance = Model(id=123, foo="bar") assert "foo=" not in repr(instance) + + +def test_min_items(): + with pytest.warns( + DeprecationWarning, + match="`min_items` is deprecated and will be removed, use `min_length` instead", + ): + + class Model(SQLModel): + items: list[int] = Field(min_items=2) + + Model(items=[1, 2]) + + with pytest.raises(ValidationError) as exc_info: + Model(items=[1]) + assert len(exc_info.value.errors()) == 1 + assert exc_info.value.errors()[0]["type"] == "too_short" + + +def test_max_items(): + with pytest.warns( + DeprecationWarning, + match="`max_items` is deprecated and will be removed, use `max_length` instead", + ): + + class Model(SQLModel): + items: list[int] = Field(max_items=2) + + Model(items=[1, 2]) + + with pytest.raises(ValidationError) as exc_info: + Model(items=[1, 2, 3]) + assert len(exc_info.value.errors()) == 1 + assert exc_info.value.errors()[0]["type"] == "too_long" diff --git a/tests/test_pydantic/test_model_dump.py b/tests/test_pydantic/test_model_dump.py new file mode 100644 index 0000000000..5264ae79bb --- /dev/null +++ b/tests/test_pydantic/test_model_dump.py @@ -0,0 +1,47 @@ +from typing import Any + +import pytest +from sqlmodel import SQLModel +from sqlmodel._compat import PYDANTIC_MINOR_VERSION + + +@pytest.mark.parametrize( + ("polymorphic_serialization", "expected_result"), + [ + (None, {"user": {"name": "pydantic"}}), + (False, {"user": {"name": "pydantic"}}), + pytest.param( + True, + {"user": {"name": "pydantic", "password": "password"}}, + marks=pytest.mark.skipif( + PYDANTIC_MINOR_VERSION < (2, 13), + reason="polymorphic_serialization is only available in Pydantic v2.13+", + ), + ), + ], +) +def test_polymorphic_serialization( + polymorphic_serialization: bool | None, expected_result: dict[str, Any] +): + + class User(SQLModel): + name: str + + class UserLogin(User): + password: str + + class OuterModel(SQLModel): + user: User + + outer_model = OuterModel( + user=UserLogin(name="pydantic", password="password"), + ) + + assert ( + outer_model.model_dump(polymorphic_serialization=polymorphic_serialization) + == expected_result + ) + + assert outer_model.model_dump(polymorphic_serialization=False) == { + "user": {"name": "pydantic"} + } diff --git a/tests/test_query.py b/tests/test_query.py index 26d35b3c2b..6a9295eedc 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,8 +1,9 @@ import pytest -from sqlmodel import Field, Session, SQLModel, create_engine +from sqlalchemy import Engine +from sqlmodel import Field, Session, SQLModel -def test_query(clear_sqlmodel): +def test_query(database_engine: Engine): class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str @@ -11,7 +12,7 @@ class Hero(SQLModel, table=True): hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") - engine = create_engine("sqlite://") + engine = database_engine SQLModel.metadata.create_all(engine) with Session(engine) as session: diff --git a/tests/test_select_typing.py b/tests/test_select_typing.py index 315f0efec7..b0b4f5fa02 100644 --- a/tests/test_select_typing.py +++ b/tests/test_select_typing.py @@ -1,8 +1,8 @@ -from sqlmodel import Field, Session, SQLModel, create_engine, select -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine +from sqlmodel import Field, Session, SQLModel, select -def test_fields() -> None: +def test_fields(database_engine: Engine) -> None: class Hero(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) name: str @@ -10,9 +10,7 @@ class Hero(SQLModel, table=True): age: int | None = None food: str | None = None - engine = create_engine( - "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool - ) + engine = database_engine SQLModel.metadata.create_all(engine) diff --git a/tests/test_tutorial/test_automatic_id_none_refresh/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_automatic_id_none_refresh/test_tutorial001_tutorial002.py index ad7615ab2c..c72af8bd66 100644 --- a/tests/test_tutorial/test_automatic_id_none_refresh/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_automatic_id_none_refresh/test_tutorial001_tutorial002.py @@ -3,7 +3,7 @@ from typing import Any import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from tests.conftest import PrintMock @@ -142,12 +142,11 @@ def check_calls(calls: list[list[str | dict[str, Any]]]) -> None: pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.automatic_id_none_refresh.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_code_structure/test_tutorial001.py b/tests/test_tutorial/test_code_structure/test_tutorial001.py index e4a4509aa5..e040147def 100644 --- a/tests/test_tutorial/test_code_structure/test_tutorial001.py +++ b/tests/test_tutorial/test_code_structure/test_tutorial001.py @@ -3,7 +3,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from tests.conftest import PrintMock @@ -37,15 +37,14 @@ class Modules: pytest.param("tutorial001_py310"), ], ) -def get_modules(request: pytest.FixtureRequest) -> Modules: +def get_modules(request: pytest.FixtureRequest, database_engine: Engine) -> Modules: app_module = importlib.import_module( f"docs_src.tutorial.code_structure.{request.param}.app" ) database_module = importlib.import_module( f"docs_src.tutorial.code_structure.{request.param}.database" ) - database_module.sqlite_url = "sqlite://" - database_module.engine = create_engine(database_module.sqlite_url) + database_module.engine = database_engine app_module.engine = database_module.engine return Modules(app=app_module, database=database_module) diff --git a/tests/test_tutorial/test_code_structure/test_tutorial002.py b/tests/test_tutorial/test_code_structure/test_tutorial002.py index cb3f049d48..31c9a8ab5f 100644 --- a/tests/test_tutorial/test_code_structure/test_tutorial002.py +++ b/tests/test_tutorial/test_code_structure/test_tutorial002.py @@ -3,7 +3,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -37,15 +37,14 @@ class Modules: pytest.param("tutorial002_py310"), ], ) -def get_modules(request: pytest.FixtureRequest) -> Modules: +def get_modules(request: pytest.FixtureRequest, database_engine: Engine) -> Modules: app_module = importlib.import_module( f"docs_src.tutorial.code_structure.{request.param}.app" ) database_module = importlib.import_module( f"docs_src.tutorial.code_structure.{request.param}.database" ) - database_module.sqlite_url = "sqlite://" - database_module.engine = create_engine(database_module.sqlite_url) + database_module.engine = database_engine app_module.engine = database_module.engine return Modules(app=app_module, database=database_module) diff --git a/tests/test_tutorial/test_connect/test_create_connected_tables/test_tutorial001.py b/tests/test_tutorial/test_connect/test_create_connected_tables/test_tutorial001.py index 55e9bda9e9..1360dc7d56 100644 --- a/tests/test_tutorial/test_connect/test_create_connected_tables/test_tutorial001.py +++ b/tests/test_tutorial/test_connect/test_create_connected_tables/test_tutorial001.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine @pytest.fixture( @@ -13,16 +12,15 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.create_tables.{request.param}" ) + module.engine = database_engine return module def test_tutorial001(module: ModuleType): - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) module.main() insp: Inspector = inspect(module.engine) assert insp.has_table(str(module.Hero.__tablename__)) diff --git a/tests/test_tutorial/test_connect/test_delete/test_tutorial001.py b/tests/test_tutorial/test_connect/test_delete/test_tutorial001.py index 11e3ceeb01..76733a1daf 100644 --- a/tests/test_tutorial/test_connect/test_delete/test_tutorial001.py +++ b/tests/test_tutorial/test_connect/test_delete/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -66,12 +66,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.delete.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_connect/test_insert/test_tutorial001.py b/tests/test_tutorial/test_connect/test_insert/test_tutorial001.py index 675011554f..bb23661461 100644 --- a/tests/test_tutorial/test_connect/test_insert/test_tutorial001.py +++ b/tests/test_tutorial/test_connect/test_insert/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -46,12 +46,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.insert.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_connect/test_select/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_connect/test_select/test_tutorial001_tutorial002.py index 1a8d07ccd7..277382588b 100644 --- a/tests/test_tutorial/test_connect/test_select/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_connect/test_select/test_tutorial001_tutorial002.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from dirty_equals import IsList +from sqlalchemy import Engine from ....conftest import PrintMock @@ -65,12 +66,11 @@ @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.select.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module @@ -83,7 +83,8 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: ) def test_tutorial001(print_mock: PrintMock, module: ModuleType): module.main() - assert print_mock.calls == expected_calls + assert print_mock.calls[:3] == expected_calls[:3] + assert print_mock.calls[3:] == IsList(*expected_calls[3:], check_order=False) @pytest.mark.parametrize( @@ -95,4 +96,5 @@ def test_tutorial001(print_mock: PrintMock, module: ModuleType): ) def test_tutorial002(print_mock: PrintMock, module: ModuleType): module.main() - assert print_mock.calls == expected_calls + assert print_mock.calls[:3] == expected_calls[:3] + assert print_mock.calls[3:] == IsList(*expected_calls[3:], check_order=False) diff --git a/tests/test_tutorial/test_connect/test_select/test_tutorial003.py b/tests/test_tutorial/test_connect/test_select/test_tutorial003.py index 0442617f39..5a6bf030cf 100644 --- a/tests/test_tutorial/test_connect/test_select/test_tutorial003.py +++ b/tests/test_tutorial/test_connect/test_select/test_tutorial003.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -82,12 +82,11 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.select.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_connect/test_select/test_tutorial004.py b/tests/test_tutorial/test_connect/test_select/test_tutorial004.py index be41b98dcc..107141ff24 100644 --- a/tests/test_tutorial/test_connect/test_select/test_tutorial004.py +++ b/tests/test_tutorial/test_connect/test_select/test_tutorial004.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -56,12 +56,11 @@ pytest.param("tutorial004_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.select.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_connect/test_select/test_tutorial005.py b/tests/test_tutorial/test_connect/test_select/test_tutorial005.py index 3e938f6378..c370c75459 100644 --- a/tests/test_tutorial/test_connect/test_select/test_tutorial005.py +++ b/tests/test_tutorial/test_connect/test_select/test_tutorial005.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -58,12 +58,11 @@ pytest.param("tutorial005_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.select.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_connect/test_update/test_tutorial001.py b/tests/test_tutorial/test_connect/test_update/test_tutorial001.py index 5e8003b072..cd1c2d39db 100644 --- a/tests/test_tutorial/test_connect/test_update/test_tutorial001.py +++ b/tests/test_tutorial/test_connect/test_update/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -56,12 +56,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.connect.update.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_create_db_and_table/test_tutorial002.py b/tests/test_tutorial/test_create_db_and_table/test_tutorial002.py index 0253b7becc..1b7f8c9162 100644 --- a/tests/test_tutorial/test_create_db_and_table/test_tutorial002.py +++ b/tests/test_tutorial/test_create_db_and_table/test_tutorial002.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine @pytest.fixture( @@ -13,12 +12,11 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.create_db_and_table.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_create_db_and_table/test_tutorial003.py b/tests/test_tutorial/test_create_db_and_table/test_tutorial003.py index 51c82b42ac..d8a936e8ac 100644 --- a/tests/test_tutorial/test_create_db_and_table/test_tutorial003.py +++ b/tests/test_tutorial/test_create_db_and_table/test_tutorial003.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine @pytest.fixture( @@ -13,12 +12,11 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module( f"docs_src.tutorial.create_db_and_table.{request.param}" ) - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_delete/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_delete/test_tutorial001_tutorial002.py index a8ad8a167d..056c1fd98a 100644 --- a/tests/test_tutorial/test_delete/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_delete/test_tutorial001_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -61,10 +61,9 @@ @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module(f"docs_src.tutorial.delete.{request.param}") - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py index 343a32ede0..9cf56131d4 100644 --- a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,12 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.fastapi.delete.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py index d918988a16..702ce61d7f 100644 --- a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,14 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.limit_and_offset.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py index 4fd90fe22f..35faa201fd 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py @@ -4,10 +4,8 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool @pytest.fixture( @@ -16,14 +14,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.multiple_models.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod @@ -220,22 +215,9 @@ def test_tutorial(module: ModuleType): # Test inherited indexes insp: Inspector = inspect(module.engine) indexes = insp.get_indexes(str(module.Hero.__tablename__)) - expected_indexes = [ - { - "name": "ix_hero_name", - "dialect_options": {}, - "column_names": ["name"], - "unique": 0, - }, - { - "name": "ix_hero_age", - "dialect_options": {}, - "column_names": ["age"], - "unique": 0, - }, - ] - for index in expected_indexes: - assert index in indexes, "This expected index should be in the indexes in DB" - # Now that this index was checked, remove it from the list of indexes - indexes.pop(indexes.index(index)) - assert len(indexes) == 0, "The database should only have the expected indexes" + assert { + index["name"]: (index["column_names"], index["unique"]) for index in indexes + } == { + "ix_hero_name": (["name"], False), + "ix_hero_age": (["age"], False), + } diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py index a4804125ea..8f032ba922 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py @@ -4,10 +4,8 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool @pytest.fixture( @@ -16,14 +14,11 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.multiple_models.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod @@ -220,22 +215,9 @@ def test_tutorial(module: ModuleType): # Test inherited indexes insp: Inspector = inspect(module.engine) indexes = insp.get_indexes(str(module.Hero.__tablename__)) - expected_indexes = [ - { - "name": "ix_hero_age", - "dialect_options": {}, - "column_names": ["age"], - "unique": 0, - }, - { - "name": "ix_hero_name", - "dialect_options": {}, - "column_names": ["name"], - "unique": 0, - }, - ] - for index in expected_indexes: - assert index in indexes, "This expected index should be in the indexes in DB" - # Now that this index was checked, remove it from the list of indexes - indexes.pop(indexes.index(index)) - assert len(indexes) == 0, "The database should only have the expected indexes" + assert { + index["name"]: (index["column_names"], index["unique"]) for index in indexes + } == { + "ix_hero_name": (["name"], False), + "ix_hero_age": (["age"], False), + } diff --git a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py index c934523b6a..0feac29dde 100644 --- a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,12 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.fastapi.read_one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py index fb3d8a125a..4fac2364eb 100644 --- a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,14 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py index 0dbb0fa800..aa9a26bcbc 100644 --- a/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,14 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.response_model.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py index 837c7b0fc4..3f1358d0e1 100644 --- a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,14 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.session_with_dependency.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py index 7234a52d9b..20dce60f69 100644 --- a/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,14 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.fastapi.simple_hero_api.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py index b5ec2a975a..b532b4795d 100644 --- a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,12 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.fastapi.teams.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py index 417a054383..085f71a866 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py @@ -4,8 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine @pytest.fixture( @@ -14,12 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.fastapi.update.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py index 1dc2f2906c..ae4b4e6e9b 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py @@ -4,8 +4,8 @@ import pytest from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from sqlmodel import Session, create_engine -from sqlmodel.pool import StaticPool +from sqlalchemy import Engine +from sqlmodel import Session @pytest.fixture( @@ -14,12 +14,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.fastapi.update.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine( - mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool - ) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_indexes/test_tutorial001.py b/tests/test_tutorial/test_indexes/test_tutorial001.py index c81d581a2e..e92ce78d50 100644 --- a/tests/test_tutorial/test_indexes/test_tutorial001.py +++ b/tests/test_tutorial/test_indexes/test_tutorial001.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine from ...conftest import PrintMock @@ -15,10 +14,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.indexes.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -30,22 +28,9 @@ def test_tutorial(print_mock: PrintMock, mod: ModuleType): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) - expected_indexes = [ - { - "name": "ix_hero_name", - "dialect_options": {}, - "column_names": ["name"], - "unique": 0, - }, - { - "name": "ix_hero_age", - "dialect_options": {}, - "column_names": ["age"], - "unique": 0, - }, - ] - for index in expected_indexes: - assert index in indexes, "This expected index should be in the indexes in DB" - # Now that this index was checked, remove it from the list of indexes - indexes.pop(indexes.index(index)) - assert len(indexes) == 0, "The database should only have the expected indexes" + assert { + index["name"]: (index["column_names"], index["unique"]) for index in indexes + } == { + "ix_hero_name": (["name"], False), + "ix_hero_age": (["age"], False), + } diff --git a/tests/test_tutorial/test_indexes/test_tutorial002.py b/tests/test_tutorial/test_indexes/test_tutorial002.py index d41dbe8f3a..44806ff197 100644 --- a/tests/test_tutorial/test_indexes/test_tutorial002.py +++ b/tests/test_tutorial/test_indexes/test_tutorial002.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine from ...conftest import PrintMock @@ -15,10 +14,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.indexes.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -31,22 +29,9 @@ def test_tutorial(print_mock: PrintMock, mod: ModuleType): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) - expected_indexes = [ - { - "name": "ix_hero_name", - "dialect_options": {}, - "column_names": ["name"], - "unique": 0, - }, - { - "name": "ix_hero_age", - "dialect_options": {}, - "column_names": ["age"], - "unique": 0, - }, - ] - for index in expected_indexes: - assert index in indexes, "This expected index should be in the indexes in DB" - # Now that this index was checked, remove it from the list of indexes - indexes.pop(indexes.index(index)) - assert len(indexes) == 0, "The database should only have the expected indexes" + assert { + index["name"]: (index["column_names"], index["unique"]) for index in indexes + } == { + "ix_hero_name": (["name"], False), + "ix_hero_age": (["age"], False), + } diff --git a/tests/test_tutorial/test_insert/test_tutorial001.py b/tests/test_tutorial/test_insert/test_tutorial001.py index a816a3a202..2fea0482bb 100644 --- a/tests/test_tutorial/test_insert/test_tutorial001.py +++ b/tests/test_tutorial/test_insert/test_tutorial001.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import Session, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Session, select @pytest.fixture( @@ -11,10 +12,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.insert.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_insert/test_tutorial002.py b/tests/test_tutorial/test_insert/test_tutorial002.py index b1f5058615..7d9a39b149 100644 --- a/tests/test_tutorial/test_insert/test_tutorial002.py +++ b/tests/test_tutorial/test_insert/test_tutorial002.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import Session, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Session, select @pytest.fixture( @@ -11,10 +12,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.insert.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_insert/test_tutorial003.py b/tests/test_tutorial/test_insert/test_tutorial003.py index fe138b0f8d..c340adc562 100644 --- a/tests/test_tutorial/test_insert/test_tutorial003.py +++ b/tests/test_tutorial/test_insert/test_tutorial003.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import Session, create_engine, select +from sqlalchemy import Engine +from sqlmodel import Session, select @pytest.fixture( @@ -11,10 +12,9 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.insert.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_limit_and_offset/test_tutorial001.py b/tests/test_tutorial/test_limit_and_offset/test_tutorial001.py index d2a10df7a1..b8696aa194 100644 --- a/tests/test_tutorial/test_limit_and_offset/test_tutorial001.py +++ b/tests/test_tutorial/test_limit_and_offset/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.offset_and_limit.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_limit_and_offset/test_tutorial002.py b/tests/test_tutorial/test_limit_and_offset/test_tutorial002.py index dc7c451b2d..0904eedfc3 100644 --- a/tests/test_tutorial/test_limit_and_offset/test_tutorial002.py +++ b/tests/test_tutorial/test_limit_and_offset/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.offset_and_limit.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_limit_and_offset/test_tutorial003.py b/tests/test_tutorial/test_limit_and_offset/test_tutorial003.py index 89b655e749..6e53aba174 100644 --- a/tests/test_tutorial/test_limit_and_offset/test_tutorial003.py +++ b/tests/test_tutorial/test_limit_and_offset/test_tutorial003.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.offset_and_limit.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py b/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py index 33a29679af..9c6c02af6b 100644 --- a/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py +++ b/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,20 +13,30 @@ pytest.param("tutorial004_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.offset_and_limit.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod def test_tutorial(print_mock: PrintMock, mod: ModuleType): mod.main() - assert print_mock.calls == [ - [ - [ - {"name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36, "id": 6}, - {"name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48, "id": 3}, - ] - ] + assert len(print_mock.calls) == 1 + assert len(print_mock.calls[0]) == 1 + heroes = print_mock.calls[0][0] + assert len(heroes) == 2 + assert len({hero["id"] for hero in heroes}) == 2 + # Without ORDER BY, the database determines which matching heroes are returned. + expected_heroes = [ + {"name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48, "id": 3}, + {"name": "Black Lion", "secret_name": "Trevor Challa", "age": 35, "id": 5}, + {"name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36, "id": 6}, + { + "name": "Captain North America", + "secret_name": "Esteban Rogelios", + "age": 93, + "id": 7, + }, ] + for hero in heroes: + assert hero in expected_heroes diff --git a/tests/test_tutorial/test_many_to_many/test_tutorial001.py b/tests/test_tutorial/test_many_to_many/test_tutorial001.py index 798d9f2b03..9cec6a3750 100644 --- a/tests/test_tutorial/test_many_to_many/test_tutorial001.py +++ b/tests/test_tutorial/test_many_to_many/test_tutorial001.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from dirty_equals import IsList +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +14,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.many_to_many.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -27,10 +27,11 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: ], [ "Deadpond teams:", - [ + IsList( {"id": 1, "name": "Z-Force", "headquarters": "Sister Margaret's Bar"}, {"id": 2, "name": "Preventers", "headquarters": "Sharp Tower"}, - ], + check_order=False, + ), ], [ "Rusty-Man:", diff --git a/tests/test_tutorial/test_many_to_many/test_tutorial002.py b/tests/test_tutorial/test_many_to_many/test_tutorial002.py index 101a2a8ae3..962375b13d 100644 --- a/tests/test_tutorial/test_many_to_many/test_tutorial002.py +++ b/tests/test_tutorial/test_many_to_many/test_tutorial002.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from dirty_equals import IsList +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +14,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.many_to_many.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -27,10 +27,11 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: ], [ "Deadpond teams:", - [ + IsList( {"id": 1, "name": "Z-Force", "headquarters": "Sister Margaret's Bar"}, {"id": 2, "name": "Preventers", "headquarters": "Sharp Tower"}, - ], + check_order=False, + ), ], [ "Rusty-Man:", @@ -50,14 +51,15 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: ], [ "Updated Spider-Boy's Teams:", - [ + IsList( {"id": 2, "name": "Preventers", "headquarters": "Sharp Tower"}, {"id": 1, "name": "Z-Force", "headquarters": "Sister Margaret's Bar"}, - ], + check_order=False, + ), ], [ "Z-Force heroes:", - [ + IsList( {"id": 1, "secret_name": "Dive Wilson", "age": None, "name": "Deadpond"}, { "id": 3, @@ -65,7 +67,8 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: "age": None, "name": "Spider-Boy", }, - ], + check_order=False, + ), ], [ "Reverted Z-Force's heroes:", diff --git a/tests/test_tutorial/test_many_to_many/test_tutorial003.py b/tests/test_tutorial/test_many_to_many/test_tutorial003.py index 672aabab72..36e5a28aa1 100644 --- a/tests/test_tutorial/test_many_to_many/test_tutorial003.py +++ b/tests/test_tutorial/test_many_to_many/test_tutorial003.py @@ -2,7 +2,8 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from dirty_equals import IsList +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +14,9 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.many_to_many.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -47,17 +47,19 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: ], [ "Updated Spider-Boy's Teams:", - [ + IsList( {"team_id": 2, "is_training": True, "hero_id": 2}, {"team_id": 1, "is_training": True, "hero_id": 2}, - ], + check_order=False, + ), ], [ "Z-Force heroes:", - [ + IsList( {"team_id": 1, "is_training": False, "hero_id": 1}, {"team_id": 1, "is_training": True, "hero_id": 2}, - ], + check_order=False, + ), ], [ "Spider-Boy team:", @@ -76,4 +78,7 @@ def get_module(request: pytest.FixtureRequest) -> ModuleType: def test_tutorial(print_mock: PrintMock, mod: ModuleType): mod.main() - assert print_mock.calls == expected_calls + assert print_mock.calls[:1] == expected_calls[:1] + assert print_mock.calls[1:4] == IsList(*expected_calls[1:4], check_order=False) + assert print_mock.calls[4:6] == expected_calls[4:6] + assert print_mock.calls[6:] == IsList(*expected_calls[6:], check_order=False) diff --git a/tests/test_tutorial/test_one/test_tutorial001.py b/tests/test_tutorial/test_one/test_tutorial001.py index 3c95328072..b6740805ae 100644 --- a/tests/test_tutorial/test_one/test_tutorial001.py +++ b/tests/test_tutorial/test_one/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial002.py b/tests/test_tutorial/test_one/test_tutorial002.py index 7c2197113b..ccc8bdd2a3 100644 --- a/tests/test_tutorial/test_one/test_tutorial002.py +++ b/tests/test_tutorial/test_one/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial003.py b/tests/test_tutorial/test_one/test_tutorial003.py index 80287855f3..c610be5dac 100644 --- a/tests/test_tutorial/test_one/test_tutorial003.py +++ b/tests/test_tutorial/test_one/test_tutorial003.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial004.py b/tests/test_tutorial/test_one/test_tutorial004.py index 302b9ea0f3..f598437a11 100644 --- a/tests/test_tutorial/test_one/test_tutorial004.py +++ b/tests/test_tutorial/test_one/test_tutorial004.py @@ -2,8 +2,9 @@ from types import ModuleType import pytest +from sqlalchemy import Engine from sqlalchemy.exc import MultipleResultsFound -from sqlmodel import Session, create_engine, delete +from sqlmodel import Session, delete from ...conftest import PrintMock @@ -14,10 +15,9 @@ pytest.param("tutorial004_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -28,15 +28,19 @@ def test_tutorial(print_mock: PrintMock, mod: ModuleType): # TODO: create delete() function # TODO: add overloads for .exec() with delete object session.exec(delete(mod.Hero)) - session.add(mod.Hero(name="Test Hero", secret_name="Secret Test Hero", age=24)) + hero = mod.Hero(name="Test Hero", secret_name="Secret Test Hero", age=24) + session.add(hero) session.commit() + session.refresh(hero) + hero_id = hero.id + assert hero_id is not None mod.select_heroes() assert print_mock.calls == [ [ "Hero:", { - "id": 1, + "id": hero_id, "name": "Test Hero", "secret_name": "Secret Test Hero", "age": 24, diff --git a/tests/test_tutorial/test_one/test_tutorial005.py b/tests/test_tutorial/test_one/test_tutorial005.py index d795b4c70a..908fe6687a 100644 --- a/tests/test_tutorial/test_one/test_tutorial005.py +++ b/tests/test_tutorial/test_one/test_tutorial005.py @@ -2,8 +2,9 @@ from types import ModuleType import pytest +from sqlalchemy import Engine from sqlalchemy.exc import NoResultFound -from sqlmodel import Session, create_engine, delete +from sqlmodel import Session, delete from ...conftest import PrintMock @@ -14,10 +15,9 @@ pytest.param("tutorial005_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -28,15 +28,19 @@ def test_tutorial(print_mock: PrintMock, mod: ModuleType): # TODO: create delete() function # TODO: add overloads for .exec() with delete object session.exec(delete(mod.Hero)) - session.add(mod.Hero(name="Test Hero", secret_name="Secret Test Hero", age=24)) + hero = mod.Hero(name="Test Hero", secret_name="Secret Test Hero", age=24) + session.add(hero) session.commit() + session.refresh(hero) + hero_id = hero.id + assert hero_id is not None mod.select_heroes() assert print_mock.calls == [ [ "Hero:", { - "id": 1, + "id": hero_id, "name": "Test Hero", "secret_name": "Secret Test Hero", "age": 24, diff --git a/tests/test_tutorial/test_one/test_tutorial006.py b/tests/test_tutorial/test_one/test_tutorial006.py index fe65e1910f..f5839a23a4 100644 --- a/tests/test_tutorial/test_one/test_tutorial006.py +++ b/tests/test_tutorial/test_one/test_tutorial006.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial006_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial007.py b/tests/test_tutorial/test_one/test_tutorial007.py index 0be06e5117..babaa57e78 100644 --- a/tests/test_tutorial/test_one/test_tutorial007.py +++ b/tests/test_tutorial/test_one/test_tutorial007.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial007_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial008.py b/tests/test_tutorial/test_one/test_tutorial008.py index aa60727896..70de2170f6 100644 --- a/tests/test_tutorial/test_one/test_tutorial008.py +++ b/tests/test_tutorial/test_one/test_tutorial008.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial008_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_one/test_tutorial009.py b/tests/test_tutorial/test_one/test_tutorial009.py index d7fcc46879..0fb9f54baf 100644 --- a/tests/test_tutorial/test_one/test_tutorial009.py +++ b/tests/test_tutorial/test_one/test_tutorial009.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial009_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.one.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial001.py b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial001.py index b11a496d48..b5a3e00bf1 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial001.py +++ b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial001.py @@ -2,8 +2,8 @@ from types import ModuleType import pytest +from sqlalchemy import Engine from sqlalchemy.exc import SAWarning -from sqlmodel import create_engine from ....conftest import PrintMock @@ -14,12 +14,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.back_populates.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial002.py b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial002.py index 3bb48d7f5e..addfa1a4e9 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial002.py +++ b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.back_populates.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial003.py b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial003.py index fe0210d06e..48e33c9ba2 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial003.py +++ b/tests/test_tutorial/test_relationship_attributes/test_back_populates/test_tutorial003.py @@ -2,9 +2,8 @@ from types import ModuleType import pytest -from sqlalchemy import inspect +from sqlalchemy import Engine, inspect from sqlalchemy.engine.reflection import Inspector -from sqlmodel import create_engine @pytest.fixture( @@ -13,12 +12,11 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.back_populates.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_create_and_update_relationships/test_tutorial001.py b/tests/test_tutorial/test_relationship_attributes/test_create_and_update_relationships/test_tutorial001.py index bb55823478..2dcea1cd6c 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_create_and_update_relationships/test_tutorial001.py +++ b/tests/test_tutorial/test_relationship_attributes/test_create_and_update_relationships/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.create_and_update_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_define_relationship_attributes/test_tutorial001.py b/tests/test_tutorial/test_relationship_attributes/test_define_relationship_attributes/test_tutorial001.py index fdd1ce6443..0285526205 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_define_relationship_attributes/test_tutorial001.py +++ b/tests/test_tutorial/test_relationship_attributes/test_define_relationship_attributes/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.define_relationship_attributes.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial001.py b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial001.py index 30e991a755..f0c176955d 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial001.py +++ b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial002.py b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial002.py index cb70c8a5e9..fed274e009 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial002.py +++ b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial003.py b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial003.py index 419c87b0eb..5b41c2e02c 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial003.py +++ b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial003.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial004.py b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial004.py index e0bc89a090..77000933bb 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial004.py +++ b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial004.py @@ -2,8 +2,9 @@ from types import ModuleType import pytest +from sqlalchemy import Engine from sqlalchemy.exc import IntegrityError -from sqlmodel import Session, create_engine, select +from sqlmodel import Session, select from ....conftest import PrintMock @@ -14,12 +15,11 @@ pytest.param("tutorial004_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod @@ -106,4 +106,4 @@ def test_tutorial(print_mock: PrintMock, mod: ModuleType): with pytest.raises(IntegrityError) as exc: mod.main() - assert "FOREIGN KEY constraint failed" in str(exc.value) + assert "foreign key" in str(exc.value).lower() diff --git a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial005.py b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial005.py index bdcb2f647b..39c2364f31 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial005.py +++ b/tests/test_tutorial/test_relationship_attributes/test_delete_records_relationship/test_tutorial005.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial005_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial001.py b/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial001.py index a600772810..5569e8ab0f 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial001.py +++ b/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.read_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial002.py b/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial002.py index d4c2003ed5..28a38cb993 100644 --- a/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial002.py +++ b/tests/test_tutorial/test_relationship_attributes/test_read_relationships/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ....conftest import PrintMock @@ -13,12 +13,11 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module( f"docs_src.tutorial.relationship_attributes.read_relationships.{request.param}" ) - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_select/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_select/test_tutorial001_tutorial002.py index c808b81721..cb35370601 100644 --- a/tests/test_tutorial/test_select/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_select/test_tutorial001_tutorial002.py @@ -3,7 +3,7 @@ from typing import Any import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -30,10 +30,9 @@ def check_calls(calls: list[list[str | dict[str, Any]]]): @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module(f"docs_src.tutorial.select.{request.param}") - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_select/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_select/test_tutorial003_tutorial004.py index 1784717237..4f04037155 100644 --- a/tests/test_tutorial/test_select/test_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_select/test_tutorial003_tutorial004.py @@ -3,7 +3,7 @@ from typing import Any import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -32,10 +32,9 @@ def check_calls(calls: list[list[str | dict[str, Any]]]): @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module(f"docs_src.tutorial.select.{request.param}") - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_update/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_update/test_tutorial001_tutorial002.py index ca52230a62..20b5dc2107 100644 --- a/tests/test_tutorial/test_update/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_update/test_tutorial001_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -29,10 +29,9 @@ @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module(f"docs_src.tutorial.update.{request.param}") - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_update/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_update/test_tutorial003_tutorial004.py index a1c008850f..fca7ef0bdb 100644 --- a/tests/test_tutorial/test_update/test_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_update/test_tutorial003_tutorial004.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -42,10 +42,9 @@ @pytest.fixture(name="module") -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: module = importlib.import_module(f"docs_src.tutorial.update.{request.param}") - module.sqlite_url = "sqlite://" - module.engine = create_engine(module.sqlite_url) + module.engine = database_engine return module diff --git a/tests/test_tutorial/test_where/test_tutorial001.py b/tests/test_tutorial/test_where/test_tutorial001.py index 92f0d82492..7d8cce1111 100644 --- a/tests/test_tutorial/test_where/test_tutorial001.py +++ b/tests/test_tutorial/test_where/test_tutorial001.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial001_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial002.py b/tests/test_tutorial/test_where/test_tutorial002.py index c1f68d092f..4aa4cc1f08 100644 --- a/tests/test_tutorial/test_where/test_tutorial002.py +++ b/tests/test_tutorial/test_where/test_tutorial002.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial002_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial003.py b/tests/test_tutorial/test_where/test_tutorial003.py index ec018a0bfc..8dca60b79c 100644 --- a/tests/test_tutorial/test_where/test_tutorial003.py +++ b/tests/test_tutorial/test_where/test_tutorial003.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial003_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial004.py b/tests/test_tutorial/test_where/test_tutorial004.py index 54ee881732..cc3293233d 100644 --- a/tests/test_tutorial/test_where/test_tutorial004.py +++ b/tests/test_tutorial/test_where/test_tutorial004.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial004_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial005.py b/tests/test_tutorial/test_where/test_tutorial005.py index fe425345b6..1f367ed95f 100644 --- a/tests/test_tutorial/test_where/test_tutorial005.py +++ b/tests/test_tutorial/test_where/test_tutorial005.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial005_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial006.py b/tests/test_tutorial/test_where/test_tutorial006.py index 7ccda9af04..d6b63ee19c 100644 --- a/tests/test_tutorial/test_where/test_tutorial006.py +++ b/tests/test_tutorial/test_where/test_tutorial006.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial006_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial006b.py b/tests/test_tutorial/test_where/test_tutorial006b.py index 5198922ed0..134508fd11 100644 --- a/tests/test_tutorial/test_where/test_tutorial006b.py +++ b/tests/test_tutorial/test_where/test_tutorial006b.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial006b_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial007.py b/tests/test_tutorial/test_where/test_tutorial007.py index d2ee397458..64bcaa0d79 100644 --- a/tests/test_tutorial/test_where/test_tutorial007.py +++ b/tests/test_tutorial/test_where/test_tutorial007.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial007_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial008.py b/tests/test_tutorial/test_where/test_tutorial008.py index fdf067cd72..48e6a80177 100644 --- a/tests/test_tutorial/test_where/test_tutorial008.py +++ b/tests/test_tutorial/test_where/test_tutorial008.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial008_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial009.py b/tests/test_tutorial/test_where/test_tutorial009.py index 8bd92b2071..a818de990a 100644 --- a/tests/test_tutorial/test_where/test_tutorial009.py +++ b/tests/test_tutorial/test_where/test_tutorial009.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial009_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/tests/test_tutorial/test_where/test_tutorial011.py b/tests/test_tutorial/test_where/test_tutorial011.py index 4b547cee42..78600447f0 100644 --- a/tests/test_tutorial/test_where/test_tutorial011.py +++ b/tests/test_tutorial/test_where/test_tutorial011.py @@ -2,7 +2,7 @@ from types import ModuleType import pytest -from sqlmodel import create_engine +from sqlalchemy import Engine from ...conftest import PrintMock @@ -13,10 +13,9 @@ pytest.param("tutorial011_py310"), ], ) -def get_module(request: pytest.FixtureRequest) -> ModuleType: +def get_module(request: pytest.FixtureRequest, database_engine: Engine) -> ModuleType: mod = importlib.import_module(f"docs_src.tutorial.where.{request.param}") - mod.sqlite_url = "sqlite://" - mod.engine = create_engine(mod.sqlite_url) + mod.engine = database_engine return mod diff --git a/uv.lock b/uv.lock index 640d5ddc61..2a3527e896 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,26 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "alembic" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy", version = "2.0.54", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sqlalchemy", version = "2.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/aa/02910bdb8e2f1444f6654d5b296cd827d126f82209050ee7b1000f92ac4b/alembic-1.20.0.tar.gz", hash = "sha256:db505480647bc60386c5369402f4a57a506b7539c9e9ef5e270d45cbbe4939bf", size = 2093272, upload-time = "2026-09-11T19:09:11.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/78a89b55b0904d222183164e079b4ca56208e94eff1d35ad1f1ad5be9b06/alembic-1.20.0-py3-none-any.whl", hash = "sha256:77eb101048d95f982c0353e9233404889dcd7a6fc244c107836c0e2fc9cf7d9d", size = 268719, upload-time = "2026-09-11T19:09:12.88Z" }, +] [[package]] name = "annotated-doc" @@ -34,32 +54,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, -] - -[[package]] -name = "backrefs" -version = "6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, -] - [[package]] name = "black" -version = "26.3.1" +version = "26.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -71,34 +68,34 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" }, - { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" }, - { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, - { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, - { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, - { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, - { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, - { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" }, + { url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, ] [[package]] @@ -436,67 +433,59 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.3" +version = "50.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" }, + { url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" }, + { url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" }, + { url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" }, + { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/c7/27/8d207af749c453ee17ea087340b3f2b4adef75aadd1d277b1b129bdda84e/cryptography-50.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94", size = 3974350, upload-time = "2026-08-25T19:45:26.551Z" }, + { url = "https://files.pythonhosted.org/packages/14/9a/6d3a4d7852e22d657438b7bf51f66102c7d71c0e1fafeec652281d0403e5/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f", size = 4698675, upload-time = "2026-08-25T19:45:28.658Z" }, + { url = "https://files.pythonhosted.org/packages/73/35/5c3717edf9e68a0550ce04e28eab493fe545eccd81742af03f6a75fe260b/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671", size = 4707410, upload-time = "2026-08-25T19:45:30.816Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e0/e786934472e3ac4ecdecc7b129a0ca1a2a40dffdafcf2c3ea9d4397f8def/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e", size = 4698378, upload-time = "2026-08-25T19:45:33.043Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/5b3f53a0b74d122f023476ede40ba5d3e70d5cf475f73b899740d26a4fb2/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6", size = 4706889, upload-time = "2026-08-25T19:45:35.086Z" }, + { url = "https://files.pythonhosted.org/packages/71/44/711e61f7d014be825ef79b285b047292d1bf893732ac1bc030a351fb517f/cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b", size = 3824006, upload-time = "2026-08-25T19:45:37.281Z" }, ] [[package]] @@ -521,6 +510,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/c0/9f59d2ebd9d585e1681c51767eb138bcd9d0ea770f6fc003cd875c7f5e62/cyclic-1.0.0-py3-none-any.whl", hash = "sha256:32d8181d7698f426bce6f14f4c3921ef95b6a84af9f96192b59beb05bc00c3ed", size = 2547, upload-time = "2018-09-26T16:47:05.609Z" }, ] +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -544,7 +542,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -553,7 +551,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.0" +version = "0.141.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -562,9 +560,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d9/e66315807e41e69e7f6a1b42a162dada2f249c5f06ad3f1a95f84ab336ef/fastapi-0.136.0.tar.gz", hash = "sha256:cf08e067cc66e106e102d9ba659463abfac245200752f8a5b7b1e813de4ff73e", size = 396607, upload-time = "2026-04-16T11:47:13.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/a3/0bd5f0cdb0bbc92650e8dc457e9250358411ee5d1b65e42b6632387daf81/fastapi-0.136.0-py3-none-any.whl", hash = "sha256:8793d44ec7378e2be07f8a013cf7f7aa47d6327d0dfe9804862688ec4541a6b4", size = 117556, upload-time = "2026-04-16T11:47:11.922Z" }, + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, ] [[package]] @@ -671,15 +669,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "hjson" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -738,6 +727,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "mako" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/12/b5fa2353e2754cd67fb9f83793fa48ff42c213a5da7e719869d2301f6ab8/mako-1.4.1.tar.gz", hash = "sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27", size = 410165, upload-time = "2026-08-05T06:10:56.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/54/12ed58d458474aaab5c3d180173e745a4fe131bb330370596876d19ff60f/mako-1.4.1-py3-none-any.whl", hash = "sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617", size = 80010, upload-time = "2026-08-05T06:10:58.248Z" }, +] + [[package]] name = "markdown" version = "3.10" @@ -940,70 +941,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] -[[package]] -name = "mkdocs-macros-plugin" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hjson" }, - { name = "jinja2" }, - { name = "mkdocs" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "super-collections" }, - { name = "termcolor" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" }, -] - -[[package]] -name = "mkdocs-material" -version = "9.7.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "backrefs" }, - { name = "colorama" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "mkdocs" }, - { name = "mkdocs-material-extensions" }, - { name = "paginate" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba", size = 9305470, upload-time = "2026-03-19T15:41:55.217Z" }, -] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, -] - -[[package]] -name = "mkdocs-redirects" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mkdocs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" }, -] - [[package]] name = "mkdocstrings" version = "1.0.3" @@ -1059,15 +996,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "paginate" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, -] - [[package]] name = "pathspec" version = "1.0.4" @@ -1079,100 +1007,96 @@ wheels = [ [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, - { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, - { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, - { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, - { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, - { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] [[package]] @@ -1195,26 +1119,117 @@ wheels = [ [[package]] name = "prek" -version = "0.3.10" +version = "0.4.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/31/3e68cd8f45cb5f3f81009ff42d3a3cde0887b83f214a4eb0ded9ae239cc2/prek-0.3.10.tar.gz", hash = "sha256:f4e9c533612bbaa9f89eca0e80ab6e59a05b0fd15ca7c6642a35bb303731ad6f", size = 425926, upload-time = "2026-04-21T11:29:37.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/51/135dc6ba2c021ce32b40700c8c337db72d802893e15291f4b3056076582f/prek-0.4.14.tar.gz", hash = "sha256:f6d0952e31ffd6e508660749dd51b8d8de96e955ed12c40e411f3224f502fed2", size = 537668, upload-time = "2026-08-17T04:27:55.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/b7/306d57aaaf503be1ee58929f9e58e812fbde9bea52c1a578e4996d82425a/prek-0.3.10-py3-none-linux_armv6l.whl", hash = "sha256:7250661f003d902b7b601141d64c7015f93eeeeeb1c485f714374582714d7c51", size = 5416913, upload-time = "2026-04-21T11:29:33.716Z" }, - { url = "https://files.pythonhosted.org/packages/93/ec/4b4b60d17c5eb639779a059d7f66a8fff5b38c7cb26c9c5b3a853cc6a9c8/prek-0.3.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9da699368bb11e85fc58b57ae4c1ccc703364b1d95b595bd17a27b27fa6df50d", size = 5784725, upload-time = "2026-04-21T11:29:38.356Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/6d4dcc9446a1630a888e3f105c4775536afe58c20f18833fec8e0d841ac0/prek-0.3.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f245fa9935cee6ea7d5ba1c6e7dd7546ccf6f01fb7d604bb6a344c8e3b49a9c2", size = 5360071, upload-time = "2026-04-21T11:29:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/36/2d/d1f63ca15f4a275fcdc712fd9ef64787abb4ba86f2e79986a26108a5f1e7/prek-0.3.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d321cad394ef4436ed9fce74150076a5eefabe310ed01b3f5735746284b9d046", size = 5615550, upload-time = "2026-04-21T11:29:30.985Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1b/a925050ba30791e50e7690a6dabe33dc08a0133e7efab4b9d91088261d40/prek-0.3.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e80017ecf06a11bf9c8ebcb67d38183e0abf2f18e012ee6a5f91d1b9c065d6", size = 5338159, upload-time = "2026-04-21T11:29:25.314Z" }, - { url = "https://files.pythonhosted.org/packages/56/ec/7d563a333198ead10f9a13fcf72ca910a59135b5d58429ed3b1fff9a0ff9/prek-0.3.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba24b4c8e40cb9ae96d58efa3eb91813cdff4f10b78b79201c93a0dc6b8763e2", size = 5728600, upload-time = "2026-04-21T11:29:23.687Z" }, - { url = "https://files.pythonhosted.org/packages/4c/88/10d493ef308f10321b92bb4e65ab60030b3c5c6304790c678be249e383e3/prek-0.3.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aac1c6f8dabb0a202a27516bf78cbfbe8a05b7cfebe2078ff6d0e12e7c77c7d5", size = 6610055, upload-time = "2026-04-21T11:29:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/c4/14/552b239d99fdd09fd6664b69089359340778f84a9f866a1fecc38b66c811/prek-0.3.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9a446e178562e2c3cfc71c8f0b21043b571209533a104042b6b9e36662849e", size = 6007394, upload-time = "2026-04-21T11:29:14.466Z" }, - { url = "https://files.pythonhosted.org/packages/21/80/12b9de6b6721a27f57ed633a8864435ec2fa7535cba7f48f3ea4a52ce683/prek-0.3.10-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d7344b2acb88b52592c0e0f3dd33349f75e6a4813d3bb44385aa8c70084990ca", size = 5616625, upload-time = "2026-04-21T11:29:29.511Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/7235f6b6d65a3e6e036b211a240432bc4f51adfdbbdcc03b5ce60a238a84/prek-0.3.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f2fe5ca3fe797a647243c10c0a9f9c2c27c1d4a0cdcb7f836a0cb8c5c2c603d8", size = 5431581, upload-time = "2026-04-21T11:29:20.399Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0c/0dd0013c6a9c6b23d070a57fe5c015c40c7b3e84217e4fb1ca9e465768c6/prek-0.3.10-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:6d39031e4acf7670b905d77ea807e2ce1b6309604d54740f3152d601b71ae5e0", size = 5318252, upload-time = "2026-04-21T11:29:40.34Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/5407ca904a7f2ffe63b1333e503d2f21ec23eda93e93fb45a491d78e0d76/prek-0.3.10-py3-none-musllinux_1_1_i686.whl", hash = "sha256:27865f3134dd566ffd76388a9f0de6ee931336794e32741b8e22c90a61f3fc39", size = 5589312, upload-time = "2026-04-21T11:29:18.3Z" }, - { url = "https://files.pythonhosted.org/packages/e0/05/460b0c498db65e9a2e590b6918aad3395291d8d9f2c080fce7201d12a5de/prek-0.3.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:3e6b65338a06add2bc47a86f837607b4705855ade9457f2f50676a7a5a509cd9", size = 6122323, upload-time = "2026-04-21T11:29:12.6Z" }, - { url = "https://files.pythonhosted.org/packages/ca/63/fd715bcd9ed5debd60dc589d6117ab9eaf0ef0b106e0bb976e1bcaa96e29/prek-0.3.10-py3-none-win32.whl", hash = "sha256:e2529515ce81292c938181702dbfc75516efa5bb1d4dc1295e6f8b2655eec881", size = 5115503, upload-time = "2026-04-21T11:29:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/6a/0d/a34f57ca3efa228fa75a976fcf698d5705b5b9a750c8dd1b86857b24f3b0/prek-0.3.10-py3-none-win_amd64.whl", hash = "sha256:0846228fa277a8bdf5a3eeea90eb3ac8eff2bf71900919373f12d142e19c8f8c", size = 5496962, upload-time = "2026-04-21T11:29:16.71Z" }, - { url = "https://files.pythonhosted.org/packages/ea/de/8b9f20f712c21756e52feed95fdacd52f48811a7bc6e3816cd04725b1240/prek-0.3.10-py3-none-win_arm64.whl", hash = "sha256:dedcdb4e5a52ee3e0463c061cabdcb94a7443875053cebfadb8228b3bf917035", size = 5340403, upload-time = "2026-04-21T11:29:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/9d/75/727724174d419cab6e11e0c22c3fbcdd083312b0a0202dfa6befdb7f1bcb/prek-0.4.14-py3-none-linux_armv6l.whl", hash = "sha256:cf7fe2e07c99948ca3326fbd4254054cf4caa71a69cf9032be09c3938544e1e3", size = 5878406, upload-time = "2026-08-17T04:27:30.135Z" }, + { url = "https://files.pythonhosted.org/packages/34/b6/82dfb41347342b4c53c1ddd27cfa81d3e1cf4ad0d5d76493c51e8da58dc9/prek-0.4.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6c8043aa5555c2ada561f4c69429e5117f370ad92f7d708340613b0965e725bd", size = 6216669, upload-time = "2026-08-17T04:27:31.795Z" }, + { url = "https://files.pythonhosted.org/packages/5b/68/ade0ba8b8c0044a3f7ca1a5cd7c97bc10656398c67466b04235f0f0b7418/prek-0.4.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:355570f0d8366a56817e55f44edf736ffc9ff2323894e21cd739ab519d343541", size = 5728085, upload-time = "2026-08-17T04:27:33.322Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cc/8ddb67fb000d1ad4c95288e6c7e27989d3c172cd88893e3a6f9cdd7199fb/prek-0.4.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:334f48a2b19e63c5ab741e601cb6088caaa11d1b75146900dc298b055452d551", size = 6043650, upload-time = "2026-08-17T04:27:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/5b/49/2780489798147fa1e81fa017af384a40229802cf36680cf72c74478d143c/prek-0.4.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5fbb17270df2dcb3c1aa6edfaa68f850d7968862413e58b1572e41c981f644f9", size = 5786094, upload-time = "2026-08-17T04:27:36.282Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2a/27a4cdfa767b46663eb32a2b5f78ebaaeefc6e5caa87a21587216a504e31/prek-0.4.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ec46be0b1b45943a0746fdf69feeede274b0230bad32b11e2489bb600846081", size = 6239874, upload-time = "2026-08-17T04:27:37.984Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ca/5ae58d95bc9dbad75ea13aea83fe0acfaca37d027c6bd7f9f1d1d97f3666/prek-0.4.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a21eea6ee996dbba351c1af8c6bd1959a61c37949757c0b97aef3edde82c126a", size = 6968009, upload-time = "2026-08-17T04:27:39.48Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1c/c6a7800406f987559fc8e7c2cfd257a5a68096e806e953f143b488e00e4f/prek-0.4.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34169cb1c8dfbe4b9cb7af164f5c1c3dff68929c61ca1e1de28d378b32720836", size = 6443370, upload-time = "2026-08-17T04:27:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/92/38/4bf84223216ce2d31b500691d1644a9f35d6e468cb8ecb9a7dfbc66bc82c/prek-0.4.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e39772ebf579f957fdbbc66ef6a7f7433bf603128cd60d5c804c70caac5f69c0", size = 6056449, upload-time = "2026-08-17T04:27:42.722Z" }, + { url = "https://files.pythonhosted.org/packages/75/0a/f1980039ab7bf8342c4aa4da2a4cafe40e6a840f456d5bbf86ba024fcdca/prek-0.4.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a79e5940354a789e1311172a960c5daa75e3f643a47ee2be85b7d2ca1d7d980a", size = 5839941, upload-time = "2026-08-17T04:27:44.306Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e2/67f067dcb912e157bc7f4cd2aeb422a0de65e7744c675d7f439676f6c832/prek-0.4.14-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:60b6539c1b28804807849173a6b49d467f6f36ca98bbb4536f5e34114d79942a", size = 5762723, upload-time = "2026-08-17T04:27:45.737Z" }, + { url = "https://files.pythonhosted.org/packages/84/39/6669fff5c3336a2b79cf853c86b95547cf9be720a2ee7e4ae5eaa6cb46a1/prek-0.4.14-py3-none-musllinux_1_1_i686.whl", hash = "sha256:24cfabd9e5b8c5546ecaef562841a41a255a9dec1b7ae7761ce5a7a024ab9dd9", size = 6090040, upload-time = "2026-08-17T04:27:47.185Z" }, + { url = "https://files.pythonhosted.org/packages/2a/23/c23210be4c89795a854e76146c2465aaffc3d85ea83df61c1d4d5bb41bca/prek-0.4.14-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:1f08d368da45439f949885bb0b637bc7dfe6910140d5285fa8bae058697ddd06", size = 6570273, upload-time = "2026-08-17T04:27:48.673Z" }, + { url = "https://files.pythonhosted.org/packages/00/9c/4f10728ed36295347e84f01bb3e4c9078c9e337edc66215f5e2066b30551/prek-0.4.14-py3-none-win32.whl", hash = "sha256:5bfb30808ce2099c67d2a2d4cd68dc031e3fec1eeb61d73ef51a8f7c04d021cb", size = 5586715, upload-time = "2026-08-17T04:27:50.471Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/d080a157d4e92927f7618d62d6a23979ef64a10c76a040cb8a8a194c50d7/prek-0.4.14-py3-none-win_amd64.whl", hash = "sha256:29364012d5704475d1092eb8a96ea30b163279096ad5e0c80a620fffa79bc639", size = 5969946, upload-time = "2026-08-17T04:27:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/37/e1/6fc64bb82e7270f61707e00b6d3154a4592ae0b2d3bd308173aa7aabe0a1/prek-0.4.14-py3-none-win_arm64.whl", hash = "sha256:ff588c02e10c8d05150763607671a22d5585c0ff7036c884d3489c2726eb215c", size = 5729906, upload-time = "2026-08-17T04:27:53.52Z" }, +] + +[[package]] +name = "psycopg" +version = "3.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/26/3ea4ca5eaea1c0debcdf7ee7c1613fbe721dc27a03c461c0817ffd8a0601/psycopg-3.3.6.tar.gz", hash = "sha256:c081f2250df751a943036e42db6df4571c66cd0aabe8291a7a506512b12007d2", size = 168171, upload-time = "2026-09-18T13:22:55.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/de/748bd7609c71cae5d737f0ba9192f19329f70180ecda8fff3cac02c5abe3/psycopg-3.3.6-py3-none-any.whl", hash = "sha256:a1db9f7148b06a28606767efaca51fa6f9398c5c0a3810519be69d7000bdb631", size = 215490, upload-time = "2026-09-18T13:15:29.374Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.3.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/92/00350a66de0af05e41d01aa3134e3970045e816afed3f99d58ec1abe15b2/psycopg_binary-3.3.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7beb3e41c9a1e509f3ed85263386588cbe3e975aa67be21f79f44fd35ffaeefc", size = 4728682, upload-time = "2026-09-18T13:15:36.605Z" }, + { url = "https://files.pythonhosted.org/packages/91/fc/afa9c7fd316a469af7ede6ebb020eac482f5d827fae57d5310c9bc0c41ae/psycopg_binary-3.3.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa73160077345ec21b3f51e8e24b3de2e99586217e497629326eb9b2ea88c52e", size = 4777111, upload-time = "2026-09-18T13:15:46.566Z" }, + { url = "https://files.pythonhosted.org/packages/f2/44/7c1e015f1bc56b36ff1369f09e852b2d83ccefd5a669a42633a916cdedc4/psycopg_binary-3.3.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f87dbdc42e78ee0f7ea180c03f8c78e80a949e373066629bd90fefff10552dff", size = 5592755, upload-time = "2026-09-18T13:15:52.886Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ae/314a251ca918cdac380bce1b87839ade9355382ea749e6ef3ba75ba0c09f/psycopg_binary-3.3.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a9348c5b43a3bb5ef8c2e89d5237c9c87eeafb01d338c84a7aebbc5cd0313299", size = 5267949, upload-time = "2026-09-18T13:16:00.53Z" }, + { url = "https://files.pythonhosted.org/packages/b6/9f/3bb0cfe9bb0f31ca57cf486ddc8c9ac51251aed8181bf88ff870b2623105/psycopg_binary-3.3.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a52991594ac4db888c7d39bccef331797e30cb31a95cae02cf2607f83a42dc2", size = 6862403, upload-time = "2026-09-18T13:16:10.385Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d6/7032c10309c3155e9b24300fdcc9a1afa539cfd20ce52fdef74a46f10161/psycopg_binary-3.3.6-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ea8beeb5541780b4b50b462eeacbc4f594ce3b911dc20c81c75f267876f71d2", size = 5107271, upload-time = "2026-09-18T13:16:16.843Z" }, + { url = "https://files.pythonhosted.org/packages/61/cc/79add2cf92684cf1a81da134b32caa662c25c72d0cc905d181ef4455f834/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:198a48e68cc99ccac03ba95ac857e73aa66f3bf6be77019fafb0832a05f7ad03", size = 4627984, upload-time = "2026-09-18T13:16:23.889Z" }, + { url = "https://files.pythonhosted.org/packages/c9/48/6dfb14f9350c14af6a2edb3c31262051b8cd94e2186e4b831e46dbbe8cd9/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fa34eb47969297471db7b7f193622c7e3ee839ec05abd05f1fe104d5b1b1dcf4", size = 4322054, upload-time = "2026-09-18T13:16:29.33Z" }, + { url = "https://files.pythonhosted.org/packages/29/35/2982338716a91cbb4dfc866be015be4457ee8106a445aabf3d1fb6a270e0/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b979a42815410432420275412633960807178b1ce26591a16ce06e78a5bd4bb2", size = 4048471, upload-time = "2026-09-18T13:16:34.119Z" }, + { url = "https://files.pythonhosted.org/packages/24/e1/171b1db1542c5f76a678b7ee0a7800bebc9735a0a03417c76cf948bfd63c/psycopg_binary-3.3.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:889e42acec10450185e0cdfb396f375e2c1a8d7737c114830a7fde4654f59e30", size = 4353931, upload-time = "2026-09-18T13:16:38.692Z" }, + { url = "https://files.pythonhosted.org/packages/08/89/4424e62a944eef40bd9326ada4ae23802b28eab6502af91e84ef7bba74fb/psycopg_binary-3.3.6-cp310-cp310-win_amd64.whl", hash = "sha256:cbd5f73073ed19c378d4c35499db1e3e703a5b1a324e521204065967bfaa7a18", size = 3674362, upload-time = "2026-09-18T13:16:44.454Z" }, + { url = "https://files.pythonhosted.org/packages/70/86/b71166048974d49c6d136b2ed1c0e5bec0b974d8c4de5cbce7e86a9e412a/psycopg_binary-3.3.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:be4f9b3c9338ac5dd217c5847e21521b396c8117f78dc420d495a5c49bbef874", size = 4728002, upload-time = "2026-09-18T13:16:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/1e06c0de7ed5aed898acb87544eac6ef0bc7d752a67ec6e5d6b835e9b40c/psycopg_binary-3.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0535693ce476a722b718b002d5d2c27d47e71ca945276ac194409c98e74c492", size = 4775403, upload-time = "2026-09-18T13:16:58.939Z" }, + { url = "https://files.pythonhosted.org/packages/84/02/2ffcbc43f8e4bbc38e5286a22013bcac01898d13cd38325f60dd5428a8af/psycopg_binary-3.3.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3c9e663b2e800e3218994cf948c11bcc2844e6491b34aa80d089baf6531827bf", size = 5594145, upload-time = "2026-09-18T13:17:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/e1/25/031dae2c7d2e7e77dcf5b1962c1e0684fa548d7af0ff6707b6b5e6054ca7/psycopg_binary-3.3.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a2e44a342d2aee40508e28a563d8961c39d9bbd8cae36d8578f0a3c6658aab0f", size = 5267985, upload-time = "2026-09-18T13:17:16.24Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/94c89ada3c003a4d858178f3bba49a35e0297ef2aad659b80eb5e380e690/psycopg_binary-3.3.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f598f19fa9a91540b5cee17932ffd227b7b53a481605bcc4573c0eafa647300", size = 6860986, upload-time = "2026-09-18T13:17:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a0/81bf499d095adee8413bd19822a6872fbfa21663ec78014a68d83a8db83c/psycopg_binary-3.3.6-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff05561e4a067d35507dc5c90f1deb2ec1c9703ac5cccc1bc26e08a197f9c5a", size = 5106702, upload-time = "2026-09-18T13:17:28.847Z" }, + { url = "https://files.pythonhosted.org/packages/00/75/99d56da64c27bd985fd82c6ecbf7976b724ac638fdd1654ef995323a1a26/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:566dd827f17728efdf7d88a5b066f815170f6fdad13967ae952842d90e6aaa9f", size = 4627090, upload-time = "2026-09-18T13:17:36.668Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0c/0222171d11233332c6a24b1cef1578215f0ffddf3642eb8dd8c4448ad69f/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b2f11794e017ce340934e35de46181c46ef71ec75ea3d85dd75cd836761c01e", size = 4320353, upload-time = "2026-09-18T13:17:42.526Z" }, + { url = "https://files.pythonhosted.org/packages/62/6f/e1cc2a28dd1228c67c969ba6fd37cd8726b312e2ff51380f847ddb38ccde/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:910ace140e3e7b7596898d083f37a8fe90c5c40684252ad4e682364b2cd3deba", size = 4047253, upload-time = "2026-09-18T13:17:47.068Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fd/38b64790ce7a515b1dbd2bab3d119637a858aeb22c380cf4859bc4ce0e42/psycopg_binary-3.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e517c146b185f9c0c6e8d0a0ebbdeeeb67896af28466e032bc810d0c7dc7a7", size = 4353208, upload-time = "2026-09-18T13:17:52.41Z" }, + { url = "https://files.pythonhosted.org/packages/f7/dc/45386530ceb2a8c789a226de9b9b34eca8fccf1feba2e4ef68a6aca50c56/psycopg_binary-3.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:c7f92daa0d2a1c76f07264abddf8cbabd30152a2f09c3270e50f0c7efdf5dcac", size = 3675638, upload-time = "2026-09-18T13:17:58.112Z" }, + { url = "https://files.pythonhosted.org/packages/e6/01/2cdd1824e58b4467ee0b9498664cd28c42d8794db6b1e35b6bcb834f0044/psycopg_binary-3.3.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f84dab25e0385692ee13274c68678377e0b1a70ab9d14e56264cbf61f60c62d", size = 4707086, upload-time = "2026-09-18T13:18:05.138Z" }, + { url = "https://files.pythonhosted.org/packages/f6/76/de9948ac06895261c84d5b9fbe283d8f3c5bc9f070691b8d9eaa1b51e322/psycopg_binary-3.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:612382ac3ed13651c7fa44b5fee9fbf7baaa2ddbc6f500391672682c5f1df9e0", size = 4769607, upload-time = "2026-09-18T13:18:12.83Z" }, + { url = "https://files.pythonhosted.org/packages/76/a9/72436c9915ee4905964689e7f0e182ce7767cc0a0390b3ce703be8177625/psycopg_binary-3.3.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:366db6e97e66b37211475f20c4c1324a2dc0dd825e46d4e87f9d599304d276f9", size = 5554134, upload-time = "2026-09-18T13:18:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/0a/42/948bb3d2617795093512613fd96ba380e922992c7908fbc073858147d196/psycopg_binary-3.3.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1679a1cb93fbe5a6d1fd58d82cbddcc6fcb8c61446ba7cae6eb2a7b19bc585de", size = 5235723, upload-time = "2026-09-18T13:18:27.071Z" }, + { url = "https://files.pythonhosted.org/packages/99/47/93e823ff1b0088400703410939c9bda3e63ed9c850b3ee088e8769f4c10b/psycopg_binary-3.3.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37d40450659401600e6d043ff586c89a71a69f33cbb8bcdba6cdb2569beecdbe", size = 6833587, upload-time = "2026-09-18T13:18:33.794Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2d/ecc69c847795aa704041a9f5667a6b0938a088cf1853636d762a6938e493/psycopg_binary-3.3.6-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5165300324efd5a772c48a88ab3a928513ab3979fca76553e62ee815f7b2b9c", size = 5070013, upload-time = "2026-09-18T13:18:39.628Z" }, + { url = "https://files.pythonhosted.org/packages/92/36/6126f0dac21713dcae91404f2a76da18598a6252339a8c669c46370d43b2/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d636338c8f21b0df2f84657b00bc34f9313f826ef93f1155bc743607e4a0c5eb", size = 4597367, upload-time = "2026-09-18T13:18:45.023Z" }, + { url = "https://files.pythonhosted.org/packages/4d/29/7ecfc04243b46c89ffd49924e9c5634ea904ef96c7d0f37e4073623584c1/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a4ee3bdd5468a725f2a4d9aab8a74b6d0279f768c8b5d3aeb102c5307ff3d59c", size = 4275419, upload-time = "2026-09-18T13:18:49.299Z" }, + { url = "https://files.pythonhosted.org/packages/6e/90/2f46d2e0de79706ac170df0a3637fe63c4498fc04f131f6049520b78b806/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:289aadd6a00e151203c081f708348ec89f1e483c9b510ef4ac3981f847f01f79", size = 4007358, upload-time = "2026-09-18T13:18:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/03/48/6744e91291b751a8cf12d63d719977974bb94c84ceba913e7ddb2e478e51/psycopg_binary-3.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f21d057f3e5f5491067e5b292498073b73847d48799b099803fef100775fcc52", size = 4320156, upload-time = "2026-09-18T13:18:59.258Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9b/94ff7fce53a64d5b286e2ec454e0a025cf3d6e6b4a9189bef16aa5de98b2/psycopg_binary-3.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:e23a66a763fbe83fcc210bc77c27e5a5ea380ebf091c06f34d8561b695e5a40f", size = 3658864, upload-time = "2026-09-18T13:19:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c3/c072584b69ad44a747b448cfc9766fecb8aae56e372a017e2ef668790057/psycopg_binary-3.3.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ad8f35e67cc16d1fad1fa8c88972dc9b3a3141ea67897399904edab96a301b6", size = 4712284, upload-time = "2026-09-18T13:19:13.451Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b9/4283b785339e8e2318d03048994b093d650ea6289fabaa806b765dc0d449/psycopg_binary-3.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373704aea331d3f3e3402c125a1543f5875e2986ebb54f97d1647942161f803f", size = 4772031, upload-time = "2026-09-18T13:19:18.524Z" }, + { url = "https://files.pythonhosted.org/packages/6f/72/7a1321d359246769fff1affffbd0132785a28f7f63c18524c15a502398f4/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b82491019b884d62318b5f30706c3d7e6d4e5a6cb7eabcb3edc0c1b0fdaceae9", size = 5556392, upload-time = "2026-09-18T13:19:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/de/b0/c6f8a0585a5dacbea74e130bcfc66629390e8f5bbc79d2a8e806e8952150/psycopg_binary-3.3.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cec5ea900390897d0b46130f60bc2883bf19c314f9044235217c8be88b0ef269", size = 5237855, upload-time = "2026-09-18T13:19:31.257Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fc/c3a7a8bbef7e945ec584ac61d460a612363ea398511cd0e220242b1d69f1/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98c02090d88f2ebc0ec1e8da538f77d225ce0fffecf372aa39262e62a1b054ef", size = 6833856, upload-time = "2026-09-18T13:19:43.622Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f2/8e80b921db728ebb68fc105bd7c4277f908210ad755bd6481d5ea7add740/psycopg_binary-3.3.6-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ee2c4728c691245e24501fcd7a97b5b381236b9985bc445bba88cdce7d1b5784", size = 5070730, upload-time = "2026-09-18T13:19:49.968Z" }, + { url = "https://files.pythonhosted.org/packages/54/6a/5b313e0c5348244f0e973aff3258bf86766656256d5ece8d541a53e35b4a/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f19cc87343eaa55255e76b31259a570072ac95d6ae82c92dd34b97691f5e49dc", size = 4598089, upload-time = "2026-09-18T13:19:56.426Z" }, + { url = "https://files.pythonhosted.org/packages/32/e9/db7f76ec24bf6699e92bf604e5c4bae10664a681a8999ef42aa0faf0f2c6/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdccb3a0e184b03e9baa673b15a809cf36c339c85dbda0ebc25a698846dfbee8", size = 4278481, upload-time = "2026-09-18T13:20:04.681Z" }, + { url = "https://files.pythonhosted.org/packages/61/83/72c67013656f4d6b547caabffb193e91d57e63f90eefdcc6d045c400e97d/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9892188bb15e5803beb51afe8a25add6b56be391a53058e8bca03b74e1e6bf22", size = 4009229, upload-time = "2026-09-18T13:20:11.905Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/5e4500df2c999eb0faed8b184e6958b834172128274f06167a5deef4c19c/psycopg_binary-3.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3af90f92769d8cc10f94515ee7a0aef36ea85ca733a0ce22858f6e0953f41138", size = 4321467, upload-time = "2026-09-18T13:20:17.949Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/e350e1cf498ba2565c3f87b12f429d2012eb86b76c2b3845a19ee5fbb4d6/psycopg_binary-3.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:0ebfad5d131de9f892ae9e70cc7616207768b6714b66a52d4612b8ceaf78b372", size = 3658179, upload-time = "2026-09-18T13:20:22.691Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/60711317c284a442511644ea7185b56ebe627606d6741e732cd16108c47b/psycopg_binary-3.3.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b3f75dee0f9afafabe4edc52c4842f1e1878ed2069bd05b22d6fe961e97e4dba", size = 4720512, upload-time = "2026-09-18T13:20:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/63/da/28befc84454cbc6374550de7746f591f8fe1b6165c1fce249652cc8291c4/psycopg_binary-3.3.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5927b7ba63153cd8e9862987290a2b783a5c590daf2a4ef981700cc3569166d4", size = 4782318, upload-time = "2026-09-18T13:20:35.401Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8a/0d21c2c833cdc0d4244c77e858e0ed37fa2abec2623be4fd686f617109ce/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0bf08b749cc144f33b44a91b78e3f71c60eb07963746a0df5a100b36ce3d7475", size = 5567460, upload-time = "2026-09-18T13:20:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/7692d0d4e656b6cc9868d8acc2e3b42f17a0db4a625400a6d093cb0533a1/psycopg_binary-3.3.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31cd942c23f613276b81a6e6598cefa12960058b0f46e1e874b540c793f6aca5", size = 5246902, upload-time = "2026-09-18T13:20:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c1/b8a1f18fb1b7558a17f57f7cb3fc8bc93189feea2958925950b3acb15743/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4690cf67738f0e0e49a32aeec99bf0e4595cc2b4f1af984a4345394b1dcff91a", size = 6847192, upload-time = "2026-09-18T13:20:56.874Z" }, + { url = "https://files.pythonhosted.org/packages/a5/76/404f33519167c65cca88ec4998776f1dbebccc301ee977f0e62c47fb0826/psycopg_binary-3.3.6-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ad1c785e784cfd87e8436c6b7702f2d321fc39601bbaf29bc63a41a867091638", size = 5079573, upload-time = "2026-09-18T13:21:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d9/79e8fbc8f37262a415f3550f0bcc5f98037442bf3d12ef6cbae2056655ae/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:79a2a1c3449f6c3409427078ed1cec10de79f3023cb5f2504f0597d350ad46c7", size = 4613633, upload-time = "2026-09-18T13:21:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/d4/47/96225db74be7d2ce04b3a58678b53cda610225055edf5faa775c9f501d8b/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:86147cb5d140341c3363fb5bacce31f8d5543902a46699d3c536b101bbceaf9e", size = 4293375, upload-time = "2026-09-18T13:21:16.027Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d2/18e9c779a5efd565250329adaf529ecc2b8b2ed5be5cb0f6ccee208cbfd9/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7308c93cf0b19bbaf8e6ff0a6ad50d3c442385739245fe15a8d593bf841734a6", size = 4019883, upload-time = "2026-09-18T13:21:21.587Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/0cc654afc6c2cda982767f5679d3646b30b1ec86545bdaa9402202d6776c/psycopg_binary-3.3.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05a83ac9fd52b9bca7cb5ab04b3691163170bd16f53defa27216ea3aa07ee781", size = 4332607, upload-time = "2026-09-18T13:21:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3e/0a753a74fbd7aef120f286c016e09d3cc3f1daf7688f4a145d27281260b2/psycopg_binary-3.3.6-cp314-cp314-win_amd64.whl", hash = "sha256:1fbd30e537dab22cafdf080608f10148fe2a5f3a61294ddb5113caac8a623840", size = 3755671, upload-time = "2026-09-18T13:21:33.855Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b1/a372b9c02aea50148e71c9853e19efca8fa5ae2010a8e27243b9b8f790c0/psycopg_binary-3.3.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:bf8c8481d026b85dd70c5fa7dde85b2333aed0b32a2602bcd38a900cbd78a49c", size = 4719571, upload-time = "2026-09-18T13:21:41.437Z" }, + { url = "https://files.pythonhosted.org/packages/65/7c/811e3828c6b82e2f10c6c9cdd963cfc66f3e024026e5a69ac18530bad984/psycopg_binary-3.3.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:b599defe9190b17e9907c8b4d114c181e702c87efcd1b8a0ad40971cdcc4634a", size = 4781230, upload-time = "2026-09-18T13:21:49.516Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/9a784eed813ea9e97c294af3ead63d02b7b203502c66380336c50065e441/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b8ece331509f7a975b90501f41e83ad905e4141753fedf3f2711b2bc70a8efbc", size = 5566111, upload-time = "2026-09-18T13:21:58.089Z" }, + { url = "https://files.pythonhosted.org/packages/68/16/47194e002007c27337b11e49bf459c4b19727463f9aff2e1a90917bcc806/psycopg_binary-3.3.6-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c61617eaae0112ca154da87ffb99b73af2c74067acac28dfb9a4455b019dff2e", size = 5249963, upload-time = "2026-09-18T13:22:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/53/84/5dcf9f310b11f0675cd860c6b2c70f58ce61798a3ee3f6f962b53fa358ca/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6d19cb4999d03231e8730a5f66c8f5068bc3b532677eb39dab0f600bff3e312", size = 6847925, upload-time = "2026-09-18T13:22:13.088Z" }, + { url = "https://files.pythonhosted.org/packages/f3/06/1957a06dc22963c418c27b284929579de84f29c37ad1abe6dc6ee9e8cf25/psycopg_binary-3.3.6-cp315-cp315-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8cbb54454dbf1bbf2ff08dd7693e8d94ac94b1a20f70f4b3b813d52ecb5cbc1", size = 5087720, upload-time = "2026-09-18T13:22:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/21/43/ac07d042bae99b57bf123bb473632f29af544008094da0ffd285ab8011e2/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dc75da5a20951049f7b773145f998f69d181adad9c58a0ff36e0cf1d73c10e10", size = 4613412, upload-time = "2026-09-18T13:22:26.719Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/019156fbeafcefb4cccc9d109de4699493bceb8313c7545c8349e089dfbc/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:955e3dd94da361e052d2e49acf591017158dc8f8ed2c8a42c2e3943403c39dc2", size = 4292618, upload-time = "2026-09-18T13:22:33.042Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0f/62113dc6b1df65983a1f2fc816c04b1edfa22f2ae9d4abee74ed267f4a96/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:c7753871eb57e6a5f4646f6168590c6653073dea5e9e720b201c8875332df4c8", size = 4027121, upload-time = "2026-09-18T13:22:38.334Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d5/cf0cbd1ea5a7d8167fe2c6953efde19101f7b193bd61a23e6d622ad6854c/psycopg_binary-3.3.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:303732e798fe6729f8e12021b9c96107df8e95ecec4dd487c67b98ec2a59435e", size = 4336388, upload-time = "2026-09-18T13:22:45.576Z" }, + { url = "https://files.pythonhosted.org/packages/98/33/e2a5b36edf8aa422f6fa4b894756eb33dc93b36df5f65121280bb8b929c4/psycopg_binary-3.3.6-cp315-cp315-win_amd64.whl", hash = "sha256:2f122603f36050937982abf9668d8bc4769a79f7c93a65013b1c49f1cab7b56b", size = 3756154, upload-time = "2026-09-18T13:22:51.283Z" }, ] [[package]] @@ -1228,7 +1243,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.5" +version = "2.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1236,146 +1251,144 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.46.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, ] [[package]] name = "pydantic-settings" -version = "2.14.0" +version = "2.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, ] [[package]] name = "pygithub" -version = "2.9.0" +version = "2.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyjwt", extra = ["crypto"] }, @@ -1384,18 +1397,18 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/9a/44f918e9be12e49cb8b053f09d5d0733b74df52bf4dabc570da1c3ecd9f6/pygithub-2.9.0.tar.gz", hash = "sha256:a26abda1222febba31238682634cad11d8b966137ed6cc3c5e445b29a11cb0a4", size = 2592289, upload-time = "2026-03-22T21:14:39.053Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/9b/195603d5371861005a3467c5e4afd02fd0698795a2aa36dc41498b9d879d/pygithub-2.10.0.tar.gz", hash = "sha256:90ff24ef1cd1bd57124c2a3869cafee9d7b066909129ecdaba2c2d1903bc118d", size = 2750952, upload-time = "2026-08-20T10:05:08.327Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/de/72e02bc7674e161b155a4b5a03b2347129d0626115bc97ba5bad5070cac9/pygithub-2.9.0-py3-none-any.whl", hash = "sha256:5e2b260ce327bffce9b00f447b65953ef7078ffe93e5a5425624a3075483927c", size = 449653, upload-time = "2026-03-22T21:14:37.726Z" }, + { url = "https://files.pythonhosted.org/packages/91/71/f314841697a1d52af3e1ea7c5e1c3f09685b64ae4c58ff16b605a866255d/pygithub-2.10.0-py3-none-any.whl", hash = "sha256:192ada2a76e4afc7d6b37e500c9bfeba1731e6506697445a5ba1c4af8bf0b924", size = 455554, upload-time = "2026-08-20T10:05:06.991Z" }, ] [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -1414,15 +1427,29 @@ crypto = [ [[package]] name = "pymdown-extensions" -version = "10.19.1" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/2d/9f30cee56d4d6d222430d401e85b0a6a1ae229819362f5786943d1a8c03b/pymdown_extensions-10.19.1.tar.gz", hash = "sha256:4969c691009a389fb1f9712dd8e7bd70dcc418d15a0faf70acb5117d022f7de8", size = 847839, upload-time = "2025-12-14T17:25:24.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/17/2db4b414de89659144488e0d9c6c0bf0c8395841dc12d81d0532cc6ef310/pymdown_extensions-11.0.2.tar.gz", hash = "sha256:9506fcbe66fa355a775b768084334238dd6805020ac4b92bea0c0dda6f8f223d", size = 855419, upload-time = "2026-08-22T19:28:47.236Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/35/b763e8fbcd51968329b9adc52d188fc97859f85f2ee15fe9f379987d99c5/pymdown_extensions-10.19.1-py3-none-any.whl", hash = "sha256:e8698a66055b1dc0dca2a7f2c9d0ea6f5faa7834a9c432e3535ab96c0c4e509b", size = 266693, upload-time = "2025-12-14T17:25:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/a6/43/9f45ec4d14e596efc32c925a78104934790438b0c0628b70d741016734ad/pymdown_extensions-11.0.2-py3-none-any.whl", hash = "sha256:259910762019732caa1dfd76f3faa62c59f191d46573e80bcb1d13c0f675bbe5", size = 269929, upload-time = "2026-08-22T19:28:45.389Z" }, +] + +[[package]] +name = "pymysql" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/d4/c15b459e25a23767d2f4065ef40968920320f04e302889574310c21c96a3/pymysql-1.2.3.tar.gz", hash = "sha256:d5b288529782e536ae171866df3ca9dc4f6cbfb3cc2f18e6f837fbb90dbc262b", size = 50629, upload-time = "2026-09-17T12:22:49.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/4b/0a906d8184f011ff8dbd4722743783867589b33269d2c5fff238d636fdcb/pymysql-1.2.3-py3-none-any.whl", hash = "sha256:14f1c68e2ed859243ae5ca41ffbe677027fc46bc136a9f0be8a4e928e5e7415a", size = 46740, upload-time = "2026-09-17T12:22:47.826Z" }, +] + +[package.optional-dependencies] +rsa = [ + { name = "cryptography" }, ] [[package]] @@ -1464,7 +1491,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1475,9 +1502,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -1655,27 +1682,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, - { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, +version = "0.16.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, + { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, + { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, + { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, + { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, + { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, ] [[package]] @@ -1711,62 +1738,145 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.49" +version = "2.0.54" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, + { name = "greenlet", marker = "(python_full_version < '3.11' and platform_machine == 'AMD64') or (python_full_version < '3.11' and platform_machine == 'WIN32') or (python_full_version < '3.11' and platform_machine == 'aarch64') or (python_full_version < '3.11' and platform_machine == 'amd64') or (python_full_version < '3.11' and platform_machine == 'ppc64le') or (python_full_version < '3.11' and platform_machine == 'win32') or (python_full_version < '3.11' and platform_machine == 'x86_64')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/271aa905cf2964f841371a97f3e63ab692bf51b4423d0491e67bc7f64037/sqlalchemy-2.0.54.tar.gz", hash = "sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b", size = 9969559, upload-time = "2026-09-15T21:06:57.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/2f/7a427d7e10bc5f285d41fa1cadf46031ffc6c84f35a9d6e19737587a4eb0/sqlalchemy-2.0.54-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:24ae093dec196ba37fc2beb0316de53e7871d3d246a50faecbbb53034e41ded2", size = 2189267, upload-time = "2026-09-15T22:11:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/80122de187c0f80cb47addce97609160004a8605010afdd815e608fcf003/sqlalchemy-2.0.54-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8cc6532f930c27974e9239e5ce5abebe7600ba9807cea4fcf42f1b6cab18fe7", size = 3358362, upload-time = "2026-09-15T22:33:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/c8/93/9d26b109f5472017bf6e7a1b9dac12ac5f6165ba37a297834d0690c42cf3/sqlalchemy-2.0.54-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e7a76d5dce712ce50435d0f97181eb955ec27d138c004176f01282e063bac52", size = 3357692, upload-time = "2026-09-15T22:33:14.781Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d4/63e800b45bd4fa59cb4b14c013130e0aa92c7a019a0b8bf68115643bf32b/sqlalchemy-2.0.54-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f5c09090b1a7c4d389d1431f820931e8df318f82caafc53f9a72c872fef467c5", size = 3308258, upload-time = "2026-09-15T22:33:38.469Z" }, + { url = "https://files.pythonhosted.org/packages/f4/fe/6c96fe018b9db15476b46619228dc0749cdd9b4dc8baab89aca59205b605/sqlalchemy-2.0.54-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:762cfe4d340c56368256d936a98b620a9a5650e49c1c84eba51d6edd17ffefb2", size = 3336734, upload-time = "2026-09-15T22:33:16.533Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f1/8d225d6b77863ae83ba51e913fe5954bcf58b49dd54229f0b5ba0bc983f0/sqlalchemy-2.0.54-cp310-cp310-win32.whl", hash = "sha256:6b6d4e601c4f6d85e99bb3416107cc9418c5603ca73d4ee0f5f8d79c2a1ed9e8", size = 2144294, upload-time = "2026-09-15T22:41:20.424Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f6/4247fdd087bf1b39be90930bb2b276a1c55041540628c8736512b4242669/sqlalchemy-2.0.54-cp310-cp310-win_amd64.whl", hash = "sha256:03cbf8d9a67da618bd65500a5eb3ddac89caf4c61e99b2f03fa4a1952a0725a9", size = 2170318, upload-time = "2026-09-15T22:41:21.973Z" }, + { url = "https://files.pythonhosted.org/packages/76/bf/7339b18ef05c03335a32f077e450c8040eab0c952c2964e32cd889eafc22/sqlalchemy-2.0.54-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d03084f3352dd92048cb19c71d90f116d076c9c7937e0ebc7752c4685de6d38", size = 2187776, upload-time = "2026-09-15T22:32:35.535Z" }, + { url = "https://files.pythonhosted.org/packages/9b/bb/3b6f5f3f51582d63af4c88b1e70da7888e936dee6b86a237b7633fb3f216/sqlalchemy-2.0.54-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92622fbbda1b1fe1632f3402a6e516a93c0e41d9158839c6b3dfb12117f26b72", size = 3428952, upload-time = "2026-09-15T22:40:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/ffd6b59795ebec81d582ad866f71ec7c73d356768e0442c6a07bba61c40c/sqlalchemy-2.0.54-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5800ddea045c2c860ef1d359a07a3066c7c0c426f45e3abc3874e116cb3c6937", size = 3428566, upload-time = "2026-09-15T22:35:33.739Z" }, + { url = "https://files.pythonhosted.org/packages/63/ff/1f0bffb0653b30711e2445a48968eff9412b4e5bf0870f1e72e8a69595ef/sqlalchemy-2.0.54-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1019abef05a4b5eafc8eae6fb483167fa28a4dbe5f518d577b744f31a5276a37", size = 3377697, upload-time = "2026-09-15T22:40:16.494Z" }, + { url = "https://files.pythonhosted.org/packages/97/76/774906be0d41cc4a14ecfbc015112fb7d0a3f1c3393f2c179d10388f8f0b/sqlalchemy-2.0.54-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b67749f7da3985a529cefbb1474783cb91ef44371cb9713630bade3de908760d", size = 3402966, upload-time = "2026-09-15T22:35:35.726Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bb/1d7fe50ace612bf342d3440d8b09544746949120326c3dc487cfc2592fba/sqlalchemy-2.0.54-cp311-cp311-win32.whl", hash = "sha256:2f61a70b3b82e2ec7ad6a4f2301422b9ca93ff06917983e41317bcae878bddf6", size = 2142297, upload-time = "2026-09-15T21:25:19.917Z" }, + { url = "https://files.pythonhosted.org/packages/d6/49/18fe80f64b1f6bcd422e649e40a78031f8624b2fde22ca54e950dfa6e25a/sqlalchemy-2.0.54-cp311-cp311-win_amd64.whl", hash = "sha256:1d887fbd5d248e250807bd801e697fc73e3b44866ce5f093dbc90512e75bde25", size = 2169222, upload-time = "2026-09-15T21:25:21.114Z" }, + { url = "https://files.pythonhosted.org/packages/c6/30/75504fd1d70458000e85a3e772333dd0fbf80254b0dc4d41c99922e4d112/sqlalchemy-2.0.54-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffba7eb2d67c7505e82a0902aa854d8824b74c28a183820d6a8bd3cfd0f812c2", size = 2187596, upload-time = "2026-09-15T22:32:37.031Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3d/5dfbb9528a391186a99986daecc5cbe003f34408dee91db8f5cdcf917040/sqlalchemy-2.0.54-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63cae7210fea9899e0bf35c1f1ae55d3ddd9c6d47cae8b6b43d945afa79dd65b", size = 3448745, upload-time = "2026-09-15T22:40:18.168Z" }, + { url = "https://files.pythonhosted.org/packages/d9/93/34fdc4a4faced77037a6b3ba1db1acd92e9bd12c21e229b327edd1d3e881/sqlalchemy-2.0.54-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68d994e9b0d0423a02a20039631fa6fcbb7fa829a992f7605025774940305d19", size = 3466644, upload-time = "2026-09-15T22:35:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/66/68/4beab40ae60ac3d679dbbb46bc0d2bb277013264a0bde37ecaf4b6780e98/sqlalchemy-2.0.54-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3de32cc6721eb42c3aad35bcfb244bb7a18f66c00f3582aae6281d6287a339b5", size = 3401519, upload-time = "2026-09-15T22:40:19.907Z" }, + { url = "https://files.pythonhosted.org/packages/e5/df/a24757e3249b1c7c1c5f0317666d16a8ab901ba239819ecef29eb4ed172e/sqlalchemy-2.0.54-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d31a2bc06a854ee52dd86b455be4df7c750b28817e2d1b884e31fff126c4fd7b", size = 3432590, upload-time = "2026-09-15T22:35:39.434Z" }, + { url = "https://files.pythonhosted.org/packages/d9/0c/69559e3d90d200fbfe9c44aff39a9ff4e506b1a3e9557a203d69df721244/sqlalchemy-2.0.54-cp312-cp312-win32.whl", hash = "sha256:32de6deded25e8b9b11d07428d496ff24dfbc882b8e990c177266948cb5f3d9e", size = 2140825, upload-time = "2026-09-15T21:25:22.444Z" }, + { url = "https://files.pythonhosted.org/packages/d9/10/4a0f7113664c2877906db143709c52ddbd28a88e4d51f0b520bc30048ba7/sqlalchemy-2.0.54-cp312-cp312-win_amd64.whl", hash = "sha256:d65f8ca742ef1e1e14bc417ef59dc2ddf207a7b66b30cfdc6152447314e030cf", size = 2170005, upload-time = "2026-09-15T21:25:23.885Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/021ccd7a838425f52e67c02edc541e8d53be8a27a103e684e14b18aac17b/sqlalchemy-2.0.54-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b374e3bc91e246a942592a98ba6a23be76fff21358b00546ac8c0ebc0fd0e00b", size = 2184259, upload-time = "2026-09-15T22:28:59.153Z" }, + { url = "https://files.pythonhosted.org/packages/74/8f/f95de908a4af7ac3a6925cdcb837a93274f608925a0fbc786df38811bccf/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d5458672a6f72db2c087f4a5098b3c8503ea0254186ff29205d63afa9401a4", size = 3398792, upload-time = "2026-09-15T22:29:35.218Z" }, + { url = "https://files.pythonhosted.org/packages/84/26/bd327a1a6be223e438c98ac8faf299307d83797225a81f3b209607a9fd98/sqlalchemy-2.0.54-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cad78d04254967bdbcccbed5e631d88fe4868530946ab0929aa45e9032849518", size = 3417562, upload-time = "2026-09-15T22:40:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b1/f13a8fe8e8e167d8e65484b91b827897b8a5ca1efd7ba6e87a5f63412fed/sqlalchemy-2.0.54-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:48611087a75d26d798003645c688c7d3cfc26b89dbe4a2c568d6b378d330deae", size = 3349138, upload-time = "2026-09-15T22:29:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/5fd58f03281a74c57f02f509433e1be58a79d9f5b0e14020e665e0dcb614/sqlalchemy-2.0.54-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6adf80277372a89910a0f3ccfe960b846d279dc55b366dd5c5ec07f41c84758", size = 3382943, upload-time = "2026-09-15T22:40:34.587Z" }, + { url = "https://files.pythonhosted.org/packages/4b/73/e29fa88dfc809857a55e6523911d345d00cab4adf87032c21ade8874223c/sqlalchemy-2.0.54-cp313-cp313-win32.whl", hash = "sha256:264460333ed0b177cbb1956355d0ee4e0cab83fb415c934ce12a25db2e7be39c", size = 2138931, upload-time = "2026-09-15T22:42:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/89/9d/885e7491836f3dad368c74f0d5b93551d37b0ec66c9e8753240ee988308f/sqlalchemy-2.0.54-cp313-cp313-win_amd64.whl", hash = "sha256:cf89e92bf0d4204a6afcc17af27b9271ed9c7e34e17d6f80c085d431ea4a1747", size = 2166496, upload-time = "2026-09-15T22:42:53.603Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c0/4a6503c9d22d6d00a5631082ab1484222ecf7d573db791e0f53161bf7745/sqlalchemy-2.0.54-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0", size = 2185899, upload-time = "2026-09-15T22:29:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/f4424f618bd1f373761a32a821d53ce2c257350e9894bae9b968cb03d8fd/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139", size = 3394763, upload-time = "2026-09-15T22:29:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/59/d2/7f0c77f8e042cb5f28275fea29c3080b4ac6fd4b3fdd7f59ff1ef3e28c11/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e", size = 3402800, upload-time = "2026-09-15T22:40:36.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/32/3eaa930bcf71d17a72587081d2706a5fb97ab3f11a7e0fb838f581f7cff1/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912", size = 3341469, upload-time = "2026-09-15T22:29:39.682Z" }, + { url = "https://files.pythonhosted.org/packages/eb/cc/cddb6cbd4408e5c55b3bf722be26b9d3b54d42509f901b7ecc15debd3d1f/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615", size = 3373232, upload-time = "2026-09-15T22:40:39.181Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/9c2aa5efc642b7f3b985d13565cd1a5e78856e079ef3022796fea5180498/sqlalchemy-2.0.54-cp314-cp314-win32.whl", hash = "sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44", size = 2142018, upload-time = "2026-09-15T22:42:55.118Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0b/3594f1f51769feb3022d686135dc5d8682a12345ed15ae61d0c0ca42cbee/sqlalchemy-2.0.54-cp314-cp314-win_amd64.whl", hash = "sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835", size = 2169220, upload-time = "2026-09-15T22:42:56.727Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a5/c211a9a7af83222509519407e16a4db760c6df3d03be69ebc5414d465321/sqlalchemy-2.0.54-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a", size = 2208458, upload-time = "2026-09-15T22:30:05.718Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2e/490ad7b3731116cb48ba170f7722eaa99a89707193e54389ca84b7ad55af/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77", size = 3660585, upload-time = "2026-09-15T22:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/eb/25/15dfe6814847eeda773bd58ab6cf42a94b0176e5cf25a578fc1165777160/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd", size = 3624442, upload-time = "2026-09-15T22:36:38.377Z" }, + { url = "https://files.pythonhosted.org/packages/aa/19/724d0a6a2fb2a86ff2d6008e581c258f722d9b7d8e52adc7b79085febdd4/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f", size = 3562972, upload-time = "2026-09-15T22:36:08.581Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/9df1bd81c2f2d000cf5e7a1b1a9b331468a3aab939ad983355e701fa42b2/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb", size = 3576479, upload-time = "2026-09-15T22:36:40.272Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/b5775465d3b89061d7c46057c31c56ff8fb6c509550b2b0c6570ffc248b3/sqlalchemy-2.0.54-cp314-cp314t-win32.whl", hash = "sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51", size = 2174794, upload-time = "2026-09-15T22:31:45.925Z" }, + { url = "https://files.pythonhosted.org/packages/77/f8/296c2e46b4ccd3f29b00b954ef2f195dde32f98e352ed21de1d292cedc0d/sqlalchemy-2.0.54-cp314-cp314t-win_amd64.whl", hash = "sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970", size = 2211942, upload-time = "2026-09-15T22:31:47.368Z" }, + { url = "https://files.pythonhosted.org/packages/24/a1/bd5e3e99bc9c8863b51ac5b9b03008a7f2da8c6b59695992f5c654e1265b/sqlalchemy-2.0.54-py3-none-any.whl", hash = "sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2", size = 1958015, upload-time = "2026-09-15T22:24:22.95Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/76/f908955139842c362aa877848f42f9249642d5b69e06cee9eae5111da1bd/sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f", size = 2159321, upload-time = "2026-04-03T16:50:11.8Z" }, - { url = "https://files.pythonhosted.org/packages/24/e2/17ba0b7bfbd8de67196889b6d951de269e8a46057d92baca162889beb16d/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b", size = 3238937, upload-time = "2026-04-03T16:54:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/90/1e/410dd499c039deacff395eec01a9da057125fcd0c97e3badc252c6a2d6a7/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1", size = 3237188, upload-time = "2026-04-03T16:56:53.217Z" }, - { url = "https://files.pythonhosted.org/packages/ab/06/e797a8b98a3993ac4bc785309b9b6d005457fc70238ee6cefa7c8867a92e/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339", size = 3190061, upload-time = "2026-04-03T16:54:47.489Z" }, - { url = "https://files.pythonhosted.org/packages/44/d3/5a9f7ef580af1031184b38235da6ac58c3b571df01c9ec061c44b2b0c5a6/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d", size = 3211477, upload-time = "2026-04-03T16:56:55.056Z" }, - { url = "https://files.pythonhosted.org/packages/69/ec/7be8c8cb35f038e963a203e4fe5a028989167cc7299927b7cf297c271e37/sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3", size = 2119965, upload-time = "2026-04-03T17:00:50.009Z" }, - { url = "https://files.pythonhosted.org/packages/b5/31/0defb93e3a10b0cf7d1271aedd87251a08c3a597ee4f353281769b547b5a/sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75", size = 2142935, upload-time = "2026-04-03T17:00:51.675Z" }, - { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, - { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, - { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, - { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, - { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, - { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, - { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, - { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, - { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, - { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, - { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, - { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, - { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, - { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, - { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, - { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, - { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +dependencies = [ + { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/cb/7c68da82239ffae9378eca076afe14c310e17a4f01e3d315112f266e26c7/sqlalchemy-2.1.1.tar.gz", hash = "sha256:fcf3cbb33bb23bad75d2150157c21804618745731e9931ab27e61879a9f6123b", size = 10503203, upload-time = "2026-09-25T14:59:00.117Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/49/5046a3fc309a3c1953bb163d37d417914ce17e4768f64e76376a4ccdc908/sqlalchemy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5516bdaacf5521b7de69a5c76961fde2cdc4edcb39730eccff16246a44cb8715", size = 2455148, upload-time = "2026-09-25T15:17:03.244Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7d/b3aaed3af03e8f36953c8d0eeec76c3b16fbec17dcfba4966d602ad7f22e/sqlalchemy-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f083b4b6a6d0061b0f9ffec0ea9ded82700a6bd11ced32ccb42f855f7806f812", size = 4612428, upload-time = "2026-09-25T15:25:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/a1df6a02d5765cbf564bc115f6116857e7d84eff5e053b4198fea1a02661/sqlalchemy-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:758d52653c8902baac25269c5b7c6fca4a198e066f2212fd2ed1b98174fd7bbb", size = 4633286, upload-time = "2026-09-25T15:25:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3e/04d3f468bc8e8eaedd300fd72936ae857428f9ecddc8ff598b0645718490/sqlalchemy-2.1.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a7ff6a02a102e0575d237172e90adc96466e6ab6511eae7fd43775e6ab19db2", size = 4363892, upload-time = "2026-09-25T15:31:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/6c34b184f9e29d124603d70fb072bb386be019bb37ba1c182d9f1e47d07f/sqlalchemy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b91e020ba33c0cb9842be6eb7de32dfebd9ad1e8f9a660a0fdb567fd0aeaed1", size = 4551082, upload-time = "2026-09-25T15:25:44.916Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/a94db16b6f62e6d9793355ea4b0fe649b8c56adc24802345159b8610a0e0/sqlalchemy-2.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:73743f135b489d3551eeb3cbbb3756d6cc41bb68e033d2b7b2b620b11c6c9d12", size = 4361249, upload-time = "2026-09-25T15:31:41.444Z" }, + { url = "https://files.pythonhosted.org/packages/4d/24/80c7de16f0248faeb4193fea62ec346f4b56df2ad2e906c82dbea7e73c69/sqlalchemy-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0593ed87886c44e94dfac4b22d20ed88b19a7dacd7440177d7156f9b011e4376", size = 4603313, upload-time = "2026-09-25T15:25:53.288Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/18d9916461ff24deb02c564cb26740ddb536b21da72164670995cefe11fa/sqlalchemy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:c19633f7614e1af93192f89debfa5bfbfaed93c80dd8903417d1605d019958dc", size = 2372093, upload-time = "2026-09-25T15:36:01.461Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a1/d91a5a3b2637a9ed1b99ba0ed35384404f5cf470dc638e9bab096bae512e/sqlalchemy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:9dcedb4b1ad160a1297772162f9a15fc317627b76ac7c98d12c41ce38522d8ef", size = 2422083, upload-time = "2026-09-25T15:36:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/42/18/1240d7c33784b6737bb47bfec63da246b174db4878c1cf2bc3b7897e945a/sqlalchemy-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:e08c92feb447b60e352c3581cc8a03119e36917b79362af62fd1a289c4d8e27b", size = 2387119, upload-time = "2026-09-25T15:11:43.595Z" }, + { url = "https://files.pythonhosted.org/packages/c8/44/950ac8e093784fdd5fd4fc385d110e0cf1d1d2bae781dc7be659bf3e7af3/sqlalchemy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1abf1acecd4d0fc34d4ab7e9cda9e849ce95b647e1a5f76db7d2a5a0bea4e1e5", size = 2458475, upload-time = "2026-09-25T15:17:05.018Z" }, + { url = "https://files.pythonhosted.org/packages/20/13/ade5c1da6221e865a57ee45132a3c61528bb244d7b22ddbbddd589989754/sqlalchemy-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61333b2f806f64a08a1edf0b0e4271905f73c1f7e525c23da7a491f74c109835", size = 4653012, upload-time = "2026-09-25T15:25:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/b2/95/37b6846549a76c69beca71974905446130d78bddd88a4414b5b88c88af16/sqlalchemy-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fad3dc37851b096ef72538b18152a5c1ae9c8b2013cd89f51eca83fc6eba36b", size = 4701803, upload-time = "2026-09-25T15:25:55.644Z" }, + { url = "https://files.pythonhosted.org/packages/57/88/abdc1ce5daa0af77c5d27183625ab7bb1155de41067fd77db4fb1cdc9616/sqlalchemy-2.1.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a3126c1203cdfc0f00884f9e8dbe95307b813f883ee9d9691983a563ebc96f8", size = 4351745, upload-time = "2026-09-25T15:31:43.337Z" }, + { url = "https://files.pythonhosted.org/packages/7b/28/4375244fc658c95033593429bc6dfc61df96beb9b64c8bf57821fdebf85d/sqlalchemy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f90e517b2eb9c4c40fcc978c8c242789fa6cb0064c501fd8c1515145ed5fe23d", size = 4576898, upload-time = "2026-09-25T15:25:49.478Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ce/0aa3c294150a7a018e61af42462cf299022ad71b4b94384be36fff6ec567/sqlalchemy-2.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:054a2c2b6cbe3ca7cb88aba9c5e2ecdbfc43656bea22326a41d09e5398c86ec4", size = 4346156, upload-time = "2026-09-25T15:31:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/cf00fdbcdd8f1933aa4b33d499218a8d470ec9fd935a2f33ec55dcef4fdc/sqlalchemy-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ea049f3e2702967414bc79f58a484dab191359267726607e6c1579a09ef93788", size = 4650545, upload-time = "2026-09-25T15:25:57.819Z" }, + { url = "https://files.pythonhosted.org/packages/48/9b/8b4a17884e2b286d5f89e3b93bc2be928c6d86d7bfeadf9ae4459d64c244/sqlalchemy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:842a5c00ca9719cc26fba6f401f70e4bac19dc8cc4dc36350803a03954d6aea2", size = 2369166, upload-time = "2026-09-25T15:36:05.081Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/c91c3cf7f0e65ae7ad9ebc87ec4cc68f6d163fddaa36736621a00dac5905/sqlalchemy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:0220e0d1d1ad2391a03cd6713ca5d795419033cc40baa1233f9ad454dffa25e6", size = 2421474, upload-time = "2026-09-25T15:36:07.069Z" }, + { url = "https://files.pythonhosted.org/packages/e3/65/fc72cd55397792553b72a8c43e14531baa436d08f832486d2cff9e806f36/sqlalchemy-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af189bd861bd31a84f0ba244d6db0c8aeae9e56448c9001bf02ee6a93758b39e", size = 2381060, upload-time = "2026-09-25T15:11:45.018Z" }, + { url = "https://files.pythonhosted.org/packages/cc/54/83400a33b70e02ef03b877e9d35c451124b4e74846a02e8a8a42d66b3dc9/sqlalchemy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b04365ffe57cdeaf4d0f75630332d857dde3f91336bc4bdec76c810473c9c35e", size = 2452431, upload-time = "2026-09-25T15:12:48.551Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ab/15a4bda565d148aae53f8df650a5e3c908735460b97ce37a89f637a87d49/sqlalchemy-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dee7886f4725c304641929a38d1ebe163f9fb45a5c9363f34bbb85bfdcb084c5", size = 4577496, upload-time = "2026-09-25T15:18:56.457Z" }, + { url = "https://files.pythonhosted.org/packages/b0/98/35dd80a408ae2d880f92631eaaff7ae1b1004a3cd8f070f95d9c534380ee/sqlalchemy-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abd893deac6b0765f51d87129c5631cc5bc8f784f8bf22a9d3903695c764bccc", size = 4628607, upload-time = "2026-09-25T15:19:51.653Z" }, + { url = "https://files.pythonhosted.org/packages/47/61/177fad5305fbd3ca2b528361f8723d7804995eeb458bdf33eba5547eb747/sqlalchemy-2.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c60fb6793cf60b2733012352f0e1861c38b24727d0e37a1da1f3974eb79f0ed", size = 4295693, upload-time = "2026-09-25T16:17:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/ec0273d82a196180f75084cfd7bc5ad5ae43724d0b8e4addf5dd97f1a0a5/sqlalchemy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a74c46cd93e34b912adafab98aa71c862518451a019d97f9c2f37392fc8eea3f", size = 4502443, upload-time = "2026-09-25T15:18:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/4b/91/36ff623fa5f28c5b96cec0f31c5c76c66ab9362d21f1d97df14261b5ec3e/sqlalchemy-2.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:eef01acb10295adfac8a960f716687052fe16f14a3f5955b56c24eefa9df7851", size = 4297102, upload-time = "2026-09-25T16:17:54.314Z" }, + { url = "https://files.pythonhosted.org/packages/71/58/a5a62bb8e2aa32c202dad2930efe7d7775b98f6cce7424dc7e6b5f357500/sqlalchemy-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ed18c0747eec4a0d8d9abe4c89d96ec0eda97721ab7d3a31d0c1ebcbf38cd2e", size = 4589847, upload-time = "2026-09-25T15:19:53.839Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d4/8e80c47eea173dcfb09ed73d255ae2f5f52b6c053a335639fed9000b0cfd/sqlalchemy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:389e7098e9ae15094f222496de98b0fd211c06b896c73c6e46aa9ca5b6fcd590", size = 2366456, upload-time = "2026-09-25T15:20:28.824Z" }, + { url = "https://files.pythonhosted.org/packages/07/ec/29eb6ce8f821d36df50ebe7bd05fb6f04b61aeed78ccb6cf830f413b34d0/sqlalchemy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:cf88881d593f527860fa7c63a50095496a5fa15632b1fee58e3114353f7fbc22", size = 2416469, upload-time = "2026-09-25T15:20:30.607Z" }, + { url = "https://files.pythonhosted.org/packages/69/ff/ab43fb0042119bd7ac7347d2df1391f37515b2e27cc49bf29bf799651878/sqlalchemy-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:28b882509319789d52966d70164ccfc5bc1aaffc1c7f98a79bbccec13346ebd7", size = 2377438, upload-time = "2026-09-25T15:25:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/3739dc61a0bd20db049f1b220cf380d55cc6c6e6e80a9cab382b3fe2d826/sqlalchemy-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:597e1edf9cbe6ce63974813000c02c7a44da3d21f1da47f07174732ba188e8ae", size = 2455544, upload-time = "2026-09-25T15:12:50.464Z" }, + { url = "https://files.pythonhosted.org/packages/51/9b/29879ca69033f466da33486d6fb238ce1f4465e15529959e5bd3a47a8f8d/sqlalchemy-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e37b1fe2e080bf47f3b693dfbec9aa39d3c3d52bfac3b4fef290a449570bcf3f", size = 4571544, upload-time = "2026-09-25T15:19:00.881Z" }, + { url = "https://files.pythonhosted.org/packages/41/0f/6db5347662a9ed83509a5236e64afc15285947ed4dd71e618912bde83445/sqlalchemy-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18148691d761ef20a92cfc7dced305834e2c6ab4f4f3648f66f5d4028e234089", size = 4601803, upload-time = "2026-09-25T15:19:55.751Z" }, + { url = "https://files.pythonhosted.org/packages/77/e4/073eecd01e8a1bb5b90638c15f06b86c897518ef96d70118a70ab37bb673/sqlalchemy-2.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44dff425e7d88b04497b4317478a6af2a694327a087e6f1670a4266180838269", size = 4295583, upload-time = "2026-09-25T16:17:56.955Z" }, + { url = "https://files.pythonhosted.org/packages/25/71/c4e261d50297457c2496bb39b03f2a2ea0578a298defe4edc5ef514d4423/sqlalchemy-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6a261e2244fa88e6eded95a078550c0584da55db22d49b0b2db8c075788282fb", size = 4501686, upload-time = "2026-09-25T15:19:02.804Z" }, + { url = "https://files.pythonhosted.org/packages/7c/85/9ec520ba12027bfdbcaa3e6633e977d1984dca5739ab73bc6c9f16cb4900/sqlalchemy-2.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4789593e8979d7c946da1985a560200b6ef6c3dc6dcc5442985d62c6f2908875", size = 4296847, upload-time = "2026-09-25T16:17:59.584Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/877c7f19fe976dffbffc579148d842bb99665e520af83d28b287a162f843/sqlalchemy-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1477f2f1f531697753a729dd8f3e4e98f7019086f001fdc4d50350c929871c8c", size = 4560888, upload-time = "2026-09-25T15:19:57.887Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f3/feb8b0803a6a2d095f4330cdc598aacef46cfb1ab6e83f4b396c6f5b89cb/sqlalchemy-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1ef7bc38d140b3cf08cc6e8823b8e76da2e188b5594997e2c16586c9778af472", size = 2373288, upload-time = "2026-09-25T15:20:32.159Z" }, + { url = "https://files.pythonhosted.org/packages/be/b6/eef7d11752b5b52ad3fecfe03fabf7324e676bf1db5a22788cbcb3ba69c6/sqlalchemy-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:73b0f7cb41a3df79dc0427377a9e2a099eb15995a327120d651f21309faa662a", size = 2423714, upload-time = "2026-09-25T15:20:34.012Z" }, + { url = "https://files.pythonhosted.org/packages/22/a0/6ce78eaac3a3197847a1d184a24633869cc343b0885f9207aa605e916bf5/sqlalchemy-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:a0421f9eedad616b04a097ae3f155c5a0a31380364369861fd6e0f46074c3fce", size = 2388489, upload-time = "2026-09-25T15:25:20.78Z" }, + { url = "https://files.pythonhosted.org/packages/b1/56/db8f82c576dbe4f52db82e0e430debca0a6398fd0bf6a45bde11e7feb126/sqlalchemy-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2f0bb0502de28baed425278d4bd98658d8256419fd9ad6ca964138710f933aa0", size = 2494990, upload-time = "2026-09-25T15:27:27.737Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e0/cbadd9f851fa376c619f97adb4abd4b0c9d727841e988c4283dc45007c1f/sqlalchemy-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e20233c7d000c006a03f3734d8e7a6e63aa878fcd3482c1f412c864a0992e5a0", size = 4899710, upload-time = "2026-09-25T15:28:30.224Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5f/8cb1a5c751161cdac50355b2c91617e2a872c237922a54b47ce014f11dd3/sqlalchemy-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b0660ded5f9c090f9bf49fba78d52843ff3ca8c10f71624811f42f11655762d", size = 4810110, upload-time = "2026-09-25T15:49:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4f/a4329f5a006b6cd2e8836b160d179c522c0cd7edfd494971a6d5a504f33e/sqlalchemy-2.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:63c0291bf0f721543db3719c71f2d5ad6649f12abbcbc8324d42805dac112cbc", size = 4490328, upload-time = "2026-09-25T17:35:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/4c/40/7bfd8136b8038bc1ffad70f283bd51aa93e43808ea522dac5a99be7b26b3/sqlalchemy-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:092a743f28f3a660321e9b093144b6a5b16f08dbb9b2f52466b47a2a128475c2", size = 4757854, upload-time = "2026-09-25T15:28:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4b/a25d5f0e8493fa8ad2334a7100479302cd1928f788c6200faa952b47ee27/sqlalchemy-2.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fdef70d59afce463bedef54580ba6a832f9f2313c1d0054e15cbb4ff46f37a97", size = 4490016, upload-time = "2026-09-25T17:35:05.664Z" }, + { url = "https://files.pythonhosted.org/packages/20/74/075ae28f89ba586a2229dc746aa8b4e5b0b2595f228c96bacadf66940bf4/sqlalchemy-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f94929be35def6580cf161f20c71930d9364d62f7040b6aed02b13fd3ca09e6", size = 4748267, upload-time = "2026-09-25T15:49:05.376Z" }, + { url = "https://files.pythonhosted.org/packages/57/fd/744a9e6d2788d1d4a077ed711d06cf503762ee8a614f887e284f0869beba/sqlalchemy-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:dabf6a73f2cc1fa2155b29f2a8a85afd82d8613262e6054cc3c932f5dee5d474", size = 2431659, upload-time = "2026-09-25T15:25:37.802Z" }, + { url = "https://files.pythonhosted.org/packages/c7/11/f4a73cb472cf1401ba3f47b02eed7983a0545f0347582afd469d5da2f324/sqlalchemy-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:3353333f8c25fc5e0661f54630238a17b0d6600305e224400cd4642cce4af34f", size = 2499109, upload-time = "2026-09-25T15:25:39.706Z" }, + { url = "https://files.pythonhosted.org/packages/ee/54/21ceb7c4ad51255be2333aa4fb32c973d35fa28b26f9895c6d66767aff4d/sqlalchemy-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7f15820c84b2377bcf02cf754232690ac2ad90d425978bf1e5ba30cc04f94d55", size = 2417171, upload-time = "2026-09-25T15:20:58.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b4/bd32e5cc1d641871ca95c0d7bd06c9d7e03d47bcab100f0c27fd2e6c8c74/sqlalchemy-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:481d926922d9bff632955ed6f07e2b87066b0d1069eb67cbc8c35e27f7fe5e1e", size = 2455487, upload-time = "2026-09-25T15:07:27.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2c/87f7754471eff7ad3186243a835ebd609f10a46d73d0c62a40188d8ace70/sqlalchemy-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2a036e3c8b183f9c31df7611e03a4bd160e055915772bb8c13ec382da8f3571", size = 4575174, upload-time = "2026-09-25T15:24:16.573Z" }, + { url = "https://files.pythonhosted.org/packages/c5/82/c6684ed8aea294894b26b8b832b5ad42dc448e385d35d8d265f6c24309bd/sqlalchemy-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1f36ca818146b0e844dbde6dced65805d4ee06ce6282aa46a807f9a6619ede", size = 4610877, upload-time = "2026-09-25T15:35:07.336Z" }, + { url = "https://files.pythonhosted.org/packages/e8/14/ce3422645f694cc59eadc267009c245c401f15e03b342a9342cb05517b99/sqlalchemy-2.1.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:583038541d9246dbb05fc4ab73038dc61820983c12fa5e32a3b0d903e1ba7bba", size = 4312066, upload-time = "2026-09-25T15:28:55.17Z" }, + { url = "https://files.pythonhosted.org/packages/51/58/0888d935b0f9e2a61dfd28872cdda833be63239a4f21ac49f22a79fbb848/sqlalchemy-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5274d07ae1dc5dc0e452ec24bdc97cf76ba189cab3f0c1c0386a1523240f775", size = 4505127, upload-time = "2026-09-25T15:24:18.447Z" }, + { url = "https://files.pythonhosted.org/packages/95/4b/c54b6cd56a1d064bef1dd1e202aa54d6b9baac4f3ecb8f42c179907d5e99/sqlalchemy-2.1.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:f2c1c27a41d1a26db380b3bad45f822d979c9608108c32ee613a418ec454ecbf", size = 4312019, upload-time = "2026-09-25T15:28:57.606Z" }, + { url = "https://files.pythonhosted.org/packages/db/b0/a42e854cc34c9fd47b36f3df15b4036f778b5032bc3206b1be31d35476f9/sqlalchemy-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3ac9781474e4b6c3e184487643d47408666be794d5782165a817da63f981f89a", size = 4571994, upload-time = "2026-09-25T15:35:10.02Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2d/34b2b42bdc5c5e4bd2d65406ac02cea51ad4bb6fed258223f6501a5ee902/sqlalchemy-2.1.1-cp315-cp315-win32.whl", hash = "sha256:eb2095d39baecbf8b78ea202d3d21178cc8d73e52c3b450c82feb200d5b0d1a9", size = 2373014, upload-time = "2026-09-25T15:19:07.42Z" }, + { url = "https://files.pythonhosted.org/packages/5c/91/441d75e7a94f3b5cf92b655282f5405925403daeba543d2cdfbcc6742923/sqlalchemy-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:ee446c07db92e94af30e1390ef7d65c1172756403f743fb7d8b4fc5f377cd427", size = 2423755, upload-time = "2026-09-25T15:19:09.005Z" }, + { url = "https://files.pythonhosted.org/packages/52/1e/414a98da13eec0e6b984a0659c7cdcc835be8e5cb73ed13cf134aefff9e8/sqlalchemy-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:57ae70935cd382cd41d84757e802687f7c5e7411aecf2b962b7bdb6ea74e088c", size = 2388191, upload-time = "2026-09-25T15:29:56.041Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d6/750d7aeb670f4954704206a5a0d20b07833549080d2e5cb1503890da568a/sqlalchemy-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:558d6d7d6eec0e54ed212320a587bb7f30553550396226d8488803c93757f17b", size = 2491192, upload-time = "2026-09-25T15:27:29.424Z" }, + { url = "https://files.pythonhosted.org/packages/e3/07/8e5c0691325466bbca0768406b4d5c2b6585d9684e25a4894bdc4df9c745/sqlalchemy-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1986faa95fd7b206e5bdd1af7f4ba9ac10f1166225fb2eca0fbca04e3851f7d", size = 4875769, upload-time = "2026-09-25T15:28:35.083Z" }, + { url = "https://files.pythonhosted.org/packages/29/2f/4deede5e238c5d539422e8f6cd692925a604a37b7c518bbcc89ac49ca801/sqlalchemy-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87d45175e8ba4a1ee027ad9307e6a4bf8a242469415f12afc9477def739474b0", size = 4826021, upload-time = "2026-09-25T15:49:07.847Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0d/b620b9ccafbdefd83cda6fd479e82d7f5dd0baec0fcae8c9e2b1296091f1/sqlalchemy-2.1.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8d846615b89702ef5d0809e701ff5f3816b88dd02670c777adc456f6d81ef3ef", size = 4476462, upload-time = "2026-09-25T17:35:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b1/e6829e519e73365c180568b64b5d2f79350daf6eb468e74cdc45b2bef018/sqlalchemy-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9324681cf8d8e9c7f6e507fea6a8ac2215fee253340d4399645216a08b936660", size = 4733363, upload-time = "2026-09-25T15:28:37.973Z" }, + { url = "https://files.pythonhosted.org/packages/af/78/9315b9732ce85aae954a4609f9e7d45975a8cf3a4133f2f8bda7fb7bdca0/sqlalchemy-2.1.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:88cb0a27ee9571fe339d7809a40b14ff2387ea05d02629a390a03c87cc981277", size = 4475138, upload-time = "2026-09-25T17:35:10.766Z" }, + { url = "https://files.pythonhosted.org/packages/99/f0/50f86f633f6ddb76d4a81bba1d0b382acfe465063699bc19900daeefdf05/sqlalchemy-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:359e2411cf4713b4b323b3c2558a07663a19ab82a5d7b23c601e1be5432aac0c", size = 4752931, upload-time = "2026-09-25T15:49:10.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/0c/e5a498c55b4f73b06bd9d541dff00b9736942bade23203719f1ba2c90774/sqlalchemy-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:d111318e32b4452d85b491d2ba3f39f6be2736ff858da616f0e6627bb3810145", size = 2428440, upload-time = "2026-09-25T15:25:41.517Z" }, + { url = "https://files.pythonhosted.org/packages/99/0b/df1c33f575ef39074dc682db2a33a1c5f259d48ca392b95a695f43553a29/sqlalchemy-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:b357598df0676accd1ccfb6bbc5db53dbfb499052d2d1677adf4ce14a7180664", size = 2494146, upload-time = "2026-09-25T15:25:43.453Z" }, + { url = "https://files.pythonhosted.org/packages/43/e1/f3d25c2ca66ed394b0fc9786af9857c448bc9ac68e59c3d2283245e9dc40/sqlalchemy-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:b76d8fddf080c53f2afdea6e7149721e7b980086a2f17a9b0bdb42571a92c285", size = 2412985, upload-time = "2026-09-25T15:20:59.747Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7a/7d1879d78c086737acee5f3250cbff9d285e5bd7cb6bc2c730f24e8d400e/sqlalchemy-2.1.1-py3-none-any.whl", hash = "sha256:4357c1a222e141662251a59d3702f9b124294941171dfa6bd570513e9f4905ee", size = 2047471, upload-time = "2026-09-25T15:20:37.797Z" }, ] [[package]] @@ -1774,12 +1884,14 @@ name = "sqlmodel" source = { editable = "." } dependencies = [ { name = "pydantic" }, - { name = "sqlalchemy" }, + { name = "sqlalchemy", version = "2.0.54", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sqlalchemy", version = "2.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions" }, ] [package.dev-dependencies] dev = [ + { name = "alembic" }, { name = "black" }, { name = "cairosvg" }, { name = "coverage", extra = ["toml"] }, @@ -1791,18 +1903,19 @@ dev = [ { name = "jinja2" }, { name = "markdown-include-variants" }, { name = "mdx-include" }, - { name = "mkdocs-macros-plugin" }, - { name = "mkdocs-material" }, - { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, { name = "pillow" }, { name = "prek" }, + { name = "psycopg", extra = ["binary"] }, + { name = "pymysql", extra = ["rsa"] }, { name = "pytest" }, { name = "pyyaml" }, { name = "ruff" }, { name = "ty" }, { name = "typer" }, { name = "typing-extensions" }, + { name = "zensical" }, + { name = "zizmor" }, ] docs = [ { name = "black" }, @@ -1811,13 +1924,11 @@ docs = [ { name = "griffe-warnings-deprecated" }, { name = "markdown-include-variants" }, { name = "mdx-include" }, - { name = "mkdocs-macros-plugin" }, - { name = "mkdocs-material" }, - { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, { name = "pillow" }, { name = "pyyaml" }, { name = "typer" }, + { name = "zensical" }, ] github-actions = [ { name = "httpx" }, @@ -1827,15 +1938,19 @@ github-actions = [ { name = "smokeshow" }, ] tests = [ + { name = "alembic" }, { name = "black" }, { name = "coverage", extra = ["toml"] }, { name = "dirty-equals" }, { name = "fastapi" }, { name = "httpx" }, { name = "jinja2" }, + { name = "psycopg", extra = ["binary"] }, + { name = "pymysql", extra = ["rsa"] }, { name = "pytest" }, { name = "ruff" }, { name = "ty" }, + { name = "typer" }, { name = "typing-extensions" }, ] @@ -1848,6 +1963,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "alembic", specifier = ">=1.12.0" }, { name = "black", specifier = ">=24.1.0" }, { name = "cairosvg", specifier = ">=2.9.0" }, { name = "coverage", extras = ["toml"], specifier = ">=6.2" }, @@ -1859,18 +1975,19 @@ dev = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "markdown-include-variants", specifier = ">=0.0.8" }, { name = "mdx-include", specifier = ">=1.4.1" }, - { name = "mkdocs-macros-plugin", specifier = ">=1.5.0" }, - { name = "mkdocs-material", specifier = ">=9.7.5" }, - { name = "mkdocs-redirects", specifier = ">=1.2.1" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.3" }, { name = "pillow", specifier = ">=12.1.1" }, { name = "prek", specifier = ">=0.2.24,<1.0.0" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.10" }, + { name = "pymysql", extras = ["rsa"], specifier = ">=1.1.1" }, { name = "pytest", specifier = ">=7.0.1" }, { name = "pyyaml", specifier = ">=5.3.1" }, { name = "ruff", specifier = ">=0.15.6" }, { name = "ty", specifier = ">=0.0.25" }, - { name = "typer", specifier = ">=0.24.1" }, + { name = "typer", specifier = ">=0.26.1" }, { name = "typing-extensions", specifier = ">=4.15.0" }, + { name = "zensical", specifier = ">=0.0.42" }, + { name = "zizmor", specifier = ">=1.23.1" }, ] docs = [ { name = "black", specifier = ">=24.1.0" }, @@ -1879,13 +1996,11 @@ docs = [ { name = "griffe-warnings-deprecated", specifier = ">=1.1.0" }, { name = "markdown-include-variants", specifier = ">=0.0.8" }, { name = "mdx-include", specifier = ">=1.4.1" }, - { name = "mkdocs-macros-plugin", specifier = ">=1.5.0" }, - { name = "mkdocs-material", specifier = ">=9.7.5" }, - { name = "mkdocs-redirects", specifier = ">=1.2.1" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.3" }, { name = "pillow", specifier = ">=12.1.1" }, { name = "pyyaml", specifier = ">=5.3.1" }, - { name = "typer", specifier = ">=0.24.1" }, + { name = "typer", specifier = ">=0.26.1" }, + { name = "zensical", specifier = ">=0.0.42" }, ] github-actions = [ { name = "httpx", specifier = ">=0.28.1" }, @@ -1895,15 +2010,19 @@ github-actions = [ { name = "smokeshow", specifier = ">=0.5.0" }, ] tests = [ + { name = "alembic", specifier = ">=1.12.0" }, { name = "black", specifier = ">=24.1.0" }, { name = "coverage", extras = ["toml"], specifier = ">=6.2" }, { name = "dirty-equals", specifier = ">=0.11" }, { name = "fastapi", specifier = ">=0.128.0" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "jinja2", specifier = ">=3.1.6" }, + { name = "psycopg", extras = ["binary"], specifier = ">=3.2.10" }, + { name = "pymysql", extras = ["rsa"], specifier = ">=1.1.1" }, { name = "pytest", specifier = ">=7.0.1" }, { name = "ruff", specifier = ">=0.15.6" }, { name = "ty", specifier = ">=0.0.25" }, + { name = "typer", specifier = ">=0.26.1" }, { name = "typing-extensions", specifier = ">=4.15.0" }, ] @@ -1920,27 +2039,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] -[[package]] -name = "super-collections" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hjson" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" }, -] - -[[package]] -name = "termcolor" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/56/ab275c2b56a5e2342568838f0d5e3e66a32354adcc159b495e374cda43f5/termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58", size = 14423, upload-time = "2025-10-25T19:11:42.586Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, -] - [[package]] name = "tinycss2" version = "1.5.1" @@ -1955,99 +2053,105 @@ wheels = [ [[package]] name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] name = "ty" -version = "0.0.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/7e/2aa791c9ae7b8cd5024cd4122e92267f664ca954cea3def3211919fa3c1f/ty-0.0.32.tar.gz", hash = "sha256:8743174c5f920f6700a4a0c9de140109189192ba16226884cd50095b43b8a45c", size = 5522294, upload-time = "2026-04-20T19:29:01.626Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/eb/1075dc6a49d7acbe2584ae4d5b410c41b1f177a5adcc567e09eca4c69000/ty-0.0.32-py3-none-linux_armv6l.whl", hash = "sha256:dacbc2f6cd698d488ae7436838ff929570455bf94bfa4d9fe57a630c552aff83", size = 10902959, upload-time = "2026-04-20T19:28:31.907Z" }, - { url = "https://files.pythonhosted.org/packages/33/d2/c35fc8bc66e98d1ee9b0f8ed319bf743e450e1f1e997574b178fab75670f/ty-0.0.32-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914bbc4f605ce2a9e2a78982e28fae1d3359a169d141f9dc3b4c7749cd5eca81", size = 10726172, upload-time = "2026-04-20T19:28:44.765Z" }, - { url = "https://files.pythonhosted.org/packages/96/32/c827da3ca480456fb02d8cea68a2609273b6c220fea0be9a4c8d8470b86e/ty-0.0.32-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4787ac9fe1f86b1f3133f5c6732adbe2df5668b50c679ac6e2d98cd284da812f", size = 10163701, upload-time = "2026-04-20T19:28:27.005Z" }, - { url = "https://files.pythonhosted.org/packages/ba/9e/2734478fbdb90c160cb2813a3916a16a2af5c1e231f87d635f6131d781fb/ty-0.0.32-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ea0a728af99fe40dd744cba6441a2404f80b7f4bde17aa6da393810af5ea57", size = 10656220, upload-time = "2026-04-20T19:29:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/44/9f/0007da2d35e424debe7e9f86ffbc1ab7f60983cfbc5f0411324ab2de5292/ty-0.0.32-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2850561f9b018ae33d7e5bbfa0ac414d3c518513edcffe43877dc9801446b9c5", size = 10696086, upload-time = "2026-04-20T19:28:46.829Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5e/ce5fd4ec803222ae3e69a76d2a2db2eed55e19f5b131702b9789ef45f93d/ty-0.0.32-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5fa2fb3c614349ee211d36476b49d88c5ef79a687cdb91b2872ad023b94d2f8", size = 11184800, upload-time = "2026-04-20T19:28:42.57Z" }, - { url = "https://files.pythonhosted.org/packages/6c/46/ebcf67a5999421331214aac51a7464db42de2be15bbe929c612a3ed0b039/ty-0.0.32-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b89969307ab2417d41c9be8059dd79feea577234e1e10d35132f5495e0d42c6", size = 11718718, upload-time = "2026-04-20T19:28:36.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/2c/2141c86ed0ce0962b45cefb658a95e734f59759d47f20afdcd9c732910a1/ty-0.0.32-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b59868ede9b1d69a088f0d695df52a0061f95fa7baa1d5e0dc6fc9cf06e1334", size = 11346369, upload-time = "2026-04-20T19:28:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/7a/da/ed6f772339cf29bd9a46def9d6db5084689eb574ee4d150ff704224c1ed8/ty-0.0.32-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8300caf35345498e9b9b03e550bba03cee8f5f5f8ab4c83c3b1ff1b7403b7d3a", size = 11280714, upload-time = "2026-04-20T19:28:51.516Z" }, - { url = "https://files.pythonhosted.org/packages/da/9b/c6813987edf4816a40e0c8e408b555f97d3f267c7b3a1688c8bbdf65609c/ty-0.0.32-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:583c7094f4574b02f724db924f98b804d1387a0bd9405ecb5e078cc0f47fbcfb", size = 10638806, upload-time = "2026-04-20T19:28:29.651Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d4/0cefcbd2ad0f3d51762ccf58e652ec7da146eb6ae34f87228f6254bbb8be/ty-0.0.32-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e44ebe1bb4143a5628bc4db67ac0dfebe14594af671e4ee66f6f2e983da56501", size = 10726106, upload-time = "2026-04-20T19:29:06.3Z" }, - { url = "https://files.pythonhosted.org/packages/32/ad/2c8a97f91f06311f4367400f7d13534bbda2522c73c99a3e4c0757dff9b8/ty-0.0.32-py3-none-musllinux_1_2_i686.whl", hash = "sha256:06f17ada3e069cba6148342ef88e9929156beca8473e8d4f101b68f66c75643e", size = 10872951, upload-time = "2026-04-20T19:28:34.077Z" }, - { url = "https://files.pythonhosted.org/packages/ba/68/42293f9248106dd51875120971a5cc6ea315c2c4dcfb8e59aa063aa0af26/ty-0.0.32-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e96e60fa556cec04f15d7ea62d2ceee5982bd389233e961ab9fd42304e278175", size = 11363334, upload-time = "2026-04-20T19:28:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/df/92/be9abf4d3e589ad5023e2ea965b93e204ec856420d46adf73c5c36c04678/ty-0.0.32-py3-none-win32.whl", hash = "sha256:2ff2ebb4986b24aebcf1444db7db5ca41b36086040e95eea9f8fb851c11e805c", size = 10260689, upload-time = "2026-04-20T19:28:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/14/61/dc86acea899349d2579cb8419aecedd83dc504d7d6a10df65eef546c8300/ty-0.0.32-py3-none-win_amd64.whl", hash = "sha256:ba7284a4a954b598c1b31500352b3ec1f89bff533825592b5958848226fdc7ee", size = 11255371, upload-time = "2026-04-20T19:28:39.917Z" }, - { url = "https://files.pythonhosted.org/packages/43/01/beffec56d71ca25b343ede63adb076456b5b3e211f1c066452a44cd120b3/ty-0.0.32-py3-none-win_arm64.whl", hash = "sha256:7e10aadbdbda989a7d567ee6a37f8b98d4d542e31e3b190a2879fd581f75d658", size = 10658087, upload-time = "2026-04-20T19:28:59.286Z" }, +version = "0.0.74" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/0f/c767853e88567a2ec7e996dd95e3105b1bc62c95d103689311ef0f4a603c/ty-0.0.74.tar.gz", hash = "sha256:da14344fc8625fc9ff359bafb856ad575636ea86d9bb6a629b146bff27b380e6", size = 6786318, upload-time = "2026-08-22T15:05:54.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/95/6ded58bc97885c6d88fa1f9cd815031489200738f961cbf0466663213f80/ty-0.0.74-py3-none-linux_armv6l.whl", hash = "sha256:8969ef4e508debf00cf58f9ea85a539f799b1732c59cdfcecd037630b9755b30", size = 12790043, upload-time = "2026-08-22T15:05:05.015Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8a/5e323603b6ab8731144421877ee8a0f8ac5a5511e67857127caa09f6730e/ty-0.0.74-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:51fb6cf5b98e1e1140825b2430943f78d744876a735231656eafbb4c3f7eca3c", size = 12371748, upload-time = "2026-08-22T15:05:08.609Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/ee72e08cb705281e8d8c42917dd577aa598a8a098008495fda5176ee3f6e/ty-0.0.74-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ebe60b1f0a948c793d6c77fc9e9ddda599e4f023c04ab16e8e03bcb428c3fa0", size = 12282403, upload-time = "2026-08-22T15:05:11.448Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/fd935b694ff68bc278af50f7ad04770b36ce6306399baef7e1847b553a9d/ty-0.0.74-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa97f407a695c890a53615966a663c7d2167e2cabe88db7ca1a24d62635cdfc8", size = 12345164, upload-time = "2026-08-22T15:05:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/54/5c/5b5825268e029ebb164c909780103dbbae367f069801410068bf1cef29b3/ty-0.0.74-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:673ddb733d4a0db31385ba1ed9ff1f6bd9dc5565413ce57b1ca5ac4c7803da5d", size = 12556646, upload-time = "2026-08-22T15:05:16.994Z" }, + { url = "https://files.pythonhosted.org/packages/56/e7/515914e571d62ce0101744fed3f881936eeb1b30dc37beb72b4f7ca1e289/ty-0.0.74-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1028e7c6b4f6145e9704552f43a5fffdcd51b42263ffdcd9c9677762bc395a4a", size = 13311653, upload-time = "2026-08-22T15:05:20.254Z" }, + { url = "https://files.pythonhosted.org/packages/b0/07/d1452babb6f9266c2122cabc095180b70ed306fb770b2996753814d2237d/ty-0.0.74-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79841a8890493021fb308772474983316eb91f7b56cb227a6a05a06b262a36f0", size = 13768284, upload-time = "2026-08-22T15:05:23.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/60/8d4a2fc7842a47210a1cb0a16a187d9de39ad5d509a00fb74c1c073afcde/ty-0.0.74-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94859d321f3c6a6c8f7bfc3f40e8319cda7e6e012e613440f3dfd145d5010e2e", size = 13422306, upload-time = "2026-08-22T15:05:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/76/ebbc269a8c4efcc4d44624993bd188145f20d60ebda9680b15aaec42cc50/ty-0.0.74-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:970a8b2c09ff3be04c8a1c6767332d861be4fce85efe7bb205e4ade7c8655274", size = 12970637, upload-time = "2026-08-22T15:05:29.15Z" }, + { url = "https://files.pythonhosted.org/packages/9e/dd/b99f7236acbf856780ca1779a48143d2d9f2c24d7f531a0ce15a022b8a87/ty-0.0.74-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:795f763b3ded85574c2c2846a6fb8acf2aa76e9e83d761143e92b1f0c7ffa2cd", size = 13344891, upload-time = "2026-08-22T15:05:32.033Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/9ff7449a4c7e6428f2c6f298e74cf24b70668f29d45c249507a723ff3782/ty-0.0.74-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dc086db5367d912c31c0cc872deb7387290e779a4b9b54fcb944673a7cd52c7b", size = 12395272, upload-time = "2026-08-22T15:05:34.702Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dd/b23a5b6b35d37df89dc8dc5daa09efd9245a668b50c4c81c25de21567dc1/ty-0.0.74-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0314d7b391cf684e47c2fa093d2ce4c597cfc9b01d9a315fe204aed6359b271b", size = 12573079, upload-time = "2026-08-22T15:05:37.683Z" }, + { url = "https://files.pythonhosted.org/packages/23/c5/ccba16239d6129533c8b3603458d0f4dd2ba69478e47059073968e74261d/ty-0.0.74-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4a45dd2e991e8bdae82ba78c8cd051b253f60bc71a6536598fa3ef580b4fc9b", size = 12832506, upload-time = "2026-08-22T15:05:40.505Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1c/2390912634dff4f341f97b397f2aee341ff062be0a66cda37d59375454f2/ty-0.0.74-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:210e2eac6b018fb934e2b8dac3956a0ba076a3fb1fa6f135058c825e5b759b81", size = 13154752, upload-time = "2026-08-22T15:05:43.355Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/a8c12188227e6f74f91853a7374e01ed81d6ad21c16c8b70e92dbebfe46a/ty-0.0.74-py3-none-win32.whl", hash = "sha256:db0bb6a8f098ef9bd1be861f73b4f7c0320d40d4c05c7ae0a8677d4e7aa4f6e5", size = 12130002, upload-time = "2026-08-22T15:05:46.058Z" }, + { url = "https://files.pythonhosted.org/packages/21/5c/064f28ccb9c234cfce5a2f7aa69a256663d5ae5bb0290b3a9706cc4d1e4c/ty-0.0.74-py3-none-win_amd64.whl", hash = "sha256:bebff181515255b3c78bd2e7693ae66fab6064ad4feea2065c68bc01022aa678", size = 12771435, upload-time = "2026-08-22T15:05:48.811Z" }, + { url = "https://files.pythonhosted.org/packages/fe/06/d6becdaca0315346c26b6df97cb0eafa81de4f870945d6989e88704374ed/ty-0.0.74-py3-none-win_arm64.whl", hash = "sha256:1a3469eaaf8c85b1c0a15bede25d36daea4b09fce1d913e965b24e24b3f1d6c6", size = 12558299, upload-time = "2026-08-22T15:05:51.543Z" }, ] [[package]] name = "typer" -version = "0.24.2" +version = "0.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/b8/9ebb531b6c2d377af08ac6746a5df3425b21853a5d2260876919b58a2a4a/typer-0.24.2.tar.gz", hash = "sha256:ec070dcfca1408e85ee203c6365001e818c3b7fffe686fd07ff2d68095ca0480", size = 119849, upload-time = "2026-04-22T17:45:34.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994, upload-time = "2026-08-03T14:41:03.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/d1/9484b497e0a0410b901c12b8251c3e746e1e863f7d28419ffe06f7892fda/typer-0.24.2-py3-none-any.whl", hash = "sha256:b618bc3d721f9a8d30f3e05565be26416d06e9bcc29d49bc491dc26aba674fa8", size = 55977, upload-time = "2026-04-22T17:45:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874, upload-time = "2026-08-03T14:41:04.391Z" }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] @@ -2062,6 +2166,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2026.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/31/3d74fa778a63b98b7374323befcc0be5ab3bd94afd4096a0124e7379152c/tzdata-2026.4.tar.gz", hash = "sha256:f1b8bd365d8d210c55353f4d7f8d6d8561c0ba50d704b700d195a9424bba0d79", size = 199350, upload-time = "2026-09-12T12:56:03.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl", hash = "sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81", size = 347494, upload-time = "2026-09-12T12:56:01.67Z" }, +] + [[package]] name = "urllib3" version = "2.6.2" @@ -2111,3 +2224,51 @@ sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda5308 wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, ] + +[[package]] +name = "zensical" +version = "0.0.57" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/f4/fa40086c46a2e59e3d9239031f76623622e60e0d79f3df1282df2797a5c4/zensical-0.0.57.tar.gz", hash = "sha256:25fcbdf89a57153cc3ad1108a89d17c7226da5d3c551a8839c69cbd9c472a9d8", size = 4000458, upload-time = "2026-08-21T20:43:49.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/b9/49c37dc65105d1ca4a8b600a02c84ece00218d2293b2630611c620185ca3/zensical-0.0.57-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:98867d1a6ea2c57f1ebcf4902f61601f427350f2df0c04e30cfac8ba6163cd29", size = 12888507, upload-time = "2026-08-21T20:43:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/05/f7/54539984418de11387bbace39a744195555d32c98c95bf4d112b432548f5/zensical-0.0.57-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0d7935d77d73a279545052e05d89d31960f30c1f33f53933f4c101fa271aee74", size = 12778169, upload-time = "2026-08-21T20:43:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/40/16/74aa60aa4cfecd5bd31ce60cb6a092cb56f1bc1aaadcc173463861ea4eb5/zensical-0.0.57-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7046d433511d97aa603915f0f6792d15b7f839793abc2b66ab7b7ff753ecff5", size = 13230823, upload-time = "2026-08-21T20:43:25.141Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d1/742d2487dd65dd18277daebcd37db56d5bd4a2408df02bde703ef8fb7b64/zensical-0.0.57-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab85c5066b95e3a877cf8971e4ce30abb1ca1459fbfcc631f0a5a2bab56351a4", size = 13170523, upload-time = "2026-08-21T20:43:27.456Z" }, + { url = "https://files.pythonhosted.org/packages/56/6f/12b570775d344f1a3d77e26d4ae0160bcac9e41ca38f7135352ccdf9b2c8/zensical-0.0.57-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f13d1b57ad3c8b8634933a93ea870ebac11245fe0c968d27fd2a059ee1c6311", size = 13549941, upload-time = "2026-08-21T20:43:29.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4e/436e6fc76674244c084ef7f6f17dc5ff85c76b15aef77c48b703fd0a2dda/zensical-0.0.57-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:021dd8fb70d1816cd012684fcf45d32b8f88a0cd28b7cbe71e5f8564f6d5764d", size = 13210086, upload-time = "2026-08-21T20:43:32.098Z" }, + { url = "https://files.pythonhosted.org/packages/ef/52/20f3aeda9af1090f24241670a5cc20fff7494545fea9f5fa094c82f3dbdf/zensical-0.0.57-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7e10f3c27fdc3eac3a9ae6ddcd87f3f00edc9f332050923313c95537961bfadd", size = 13408253, upload-time = "2026-08-21T20:43:34.258Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f2/2b18ba2f19674dbfcf745f3b66e005cc8efa66a1bcaba5e1b4f79467868a/zensical-0.0.57-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:78c85fee55c5aac3bdf8157e980c56397dca835167a5577c5429b5eb24ed990c", size = 13446689, upload-time = "2026-08-21T20:43:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/05/ba/68cdba447a9097e5f97742eef046020c6fa42d82972849b3a46a0718e890/zensical-0.0.57-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:478d252e1924f3876e72cf7806967cb62e50d86eddb3da04bf43e882b532fa1b", size = 13598580, upload-time = "2026-08-21T20:43:38.646Z" }, + { url = "https://files.pythonhosted.org/packages/ec/89/6358a4df272328bed5bea90b04d43e73758bc45ff058c5cb2665e1147314/zensical-0.0.57-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66a9ca6b5f625b2a2b215eec2f3c72843a92d5d512042045ac6351d5dee9b339", size = 13557609, upload-time = "2026-08-21T20:43:40.866Z" }, + { url = "https://files.pythonhosted.org/packages/77/e1/8831301a24f736743e3788f09ea048918b0bdcea4aaa90f7770a433d6eec/zensical-0.0.57-cp310-abi3-win32.whl", hash = "sha256:f0fe3dc27ca7dc4e168eddd0fe5b0f4d44e311fd4e0019241e289819e445203c", size = 12446805, upload-time = "2026-08-21T20:43:43.097Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3f/5d0ecd77d9ce962fdfde22dec036f4257a43ef6dbd55fb5c05fd294985ad/zensical-0.0.57-cp310-abi3-win_amd64.whl", hash = "sha256:a756834025c1c54e806e943be6d8df1048d0f8bcf6086e958568407a070a2572", size = 12716781, upload-time = "2026-08-21T20:43:45.273Z" }, +] + +[[package]] +name = "zizmor" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/f8/f4e3fc0b316d5241b6d6968e8fb702e28446bc7d3c1e2b229f4caa6eacf2/zizmor-1.29.0.tar.gz", hash = "sha256:60e34e83c67064e0036989c7c525d13413e897aa4c4f683f1efb2048cdb28a47", size = 571865, upload-time = "2026-08-01T21:09:19.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/97/667ef4db0ca9225ee402c1b947b5b6f17fd234d72c15a71db150b7695c62/zizmor-1.29.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ea72f84d610643d57f96430c655a3780d0b874e477d32e14eae8e910f6cce1fd", size = 9037504, upload-time = "2026-08-01T21:08:56.752Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d4/9fc7deaf75778e7516fa1d6c836377c3cb5d203dedc28899946b6f11ecdb/zizmor-1.29.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5aafe617d7b1e0c0c15d58fdf20495f360f74a791dfa136f76630b4cc06c2a34", size = 8654426, upload-time = "2026-08-01T21:08:59.212Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f6/6db714fb0aa08aeec62eb9d6ad6a443a1f4ed50d4c0b789944ae55fb83e4/zizmor-1.29.0-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:67644ae8d6d0394204b9a488f7d86f0dd66fe562f4ba85fc53e6105a6bfc7b6a", size = 8918927, upload-time = "2026-08-01T21:09:01.46Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/a12edc0c0c0a0101c54dbb9099ff08f8de2fcb35c36cb706db3deb2c2728/zizmor-1.29.0-py3-none-manylinux_2_28_armv7l.whl", hash = "sha256:81e4093fed5c8a41d6ae7bb773085a9d2e6c0b0a0b560d46a9c76d69be0a07ed", size = 8500655, upload-time = "2026-08-01T21:09:03.677Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f0/dfa67018b76bc4f2f50e265e8cbd1293833d1b1de5f3f02fbbb7487ae9c6/zizmor-1.29.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:587b99c2e1b34575c6c8565c2bfde415ca8bc0310f5589f19bc948c8dea10a20", size = 9351035, upload-time = "2026-08-01T21:09:06.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/1b/93cdd5a06984b394d90001f9778008a21689052904109080a09952626c99/zizmor-1.29.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:061600f23c46f2e400bcdef666c236de7e5c0b07dd6ca046daa001eb1514b909", size = 8941717, upload-time = "2026-08-01T21:09:08.861Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f4/8d9e54405b477bc8e4b56c1a60123fca26c109ea6a762eea104fab32555e/zizmor-1.29.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:332546480be38aca95c149f835e0dcb7679ab5d74618a90c6ccb3fa6b8c7b99d", size = 8468289, upload-time = "2026-08-01T21:09:11.096Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/06d57ca830cc4653369c5aca22cccbf04c8c36ee84a67f351214e556bad8/zizmor-1.29.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a7462b9ab45d72a20ad5ab8193b430df8184c59e2bf46954ddd09496f2f00b45", size = 9446505, upload-time = "2026-08-01T21:09:13.38Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f4/253d9a3538e0ea6f96a3bcd69839c0c5a816a00299620b58a10b5bd1df59/zizmor-1.29.0-py3-none-win32.whl", hash = "sha256:8c759e68cd866375030ca39e19e2de47a056b7be7288c1620e2d5b4c274f631f", size = 7655723, upload-time = "2026-08-01T21:09:15.758Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2d/7919bc23475273ed8038a031fc24bc3f2005c78e608ce8df96746ef0fb98/zizmor-1.29.0-py3-none-win_amd64.whl", hash = "sha256:0fb85948ba5ffc7a8116eee36fe9cfc10167225c97bd2810e3378e66a9fd27c4", size = 8785519, upload-time = "2026-08-01T21:09:17.513Z" }, +]