diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 9311ae8d0..000000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 88 -exclude = cookiecutter -ignore = E, W -per-file-ignores = - # Don't require docstrings conventions in private modules - singer_sdk/helpers/_*.py:DAR - # Disabled some checks in samples code - samples/*:DAR -docstring-convention = google diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36ea5cce3..e75ca6463 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # nested in that directory, on any level # Default owners -* @edgarrmondragon @kgpayne +* @edgarrmondragon # CI/CD /.github/workflows/ @edgarrmondragon @meltano/engineering diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f14dd52f5..ee12485bf 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -13,7 +13,7 @@ body: attributes: label: Singer SDK Version description: Version of the library you are using - placeholder: "0.37.0" + placeholder: "0.39.1" validations: required: true - type: checkboxes diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c4226472c..4b12bfa9b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,23 +3,35 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: daily + interval: weekly time: "12:00" reviewers: [meltano/engineering] - labels: [deps] + labels: [Dependencies] + groups: + development-dependencies: + dependency-type: development + runtime-dependencies: + dependency-type: production + update-types: + - "minor" + - "patch" - package-ecosystem: pip directory: "/.github/workflows" schedule: - interval: daily + interval: weekly time: "12:00" reviewers: [meltano/engineering] - labels: [deps] + labels: [Dependencies] + groups: + ci: + patterns: + - "*" - package-ecosystem: github-actions directory: "/" schedule: interval: weekly reviewers: [meltano/engineering] - labels: [deps] + labels: [Dependencies] groups: actions: patterns: diff --git a/.github/semantic.yml b/.github/semantic.yml index ff00a1ad4..08bebb333 100644 --- a/.github/semantic.yml +++ b/.github/semantic.yml @@ -15,6 +15,7 @@ types: - docs - feat - fix + - packaging - perf - refactor - revert diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml new file mode 100644 index 000000000..c758517c4 --- /dev/null +++ b/.github/workflows/api-changes.yml @@ -0,0 +1,53 @@ +name: API Changes + +on: + pull_request: + paths: + - singer_sdk/** + - .github/workflows/api-changes.yml + - CHANGELOG.md + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: # added using https://github.com/step-security/secure-repo + contents: read + +jobs: + check-api-changes: + name: Check API Changes + runs-on: ubuntu-latest + env: + NOXSESSION: api + steps: + - name: Check out the repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Install tools + env: + PIP_CONSTRAINT: ${{ github.workspace }}/.github/workflows/constraints.txt + run: | + python -Im pip install -U pip + pipx install griffe nox + pipx inject nox nox-poetry + pipx list + + - name: Set REF + id: set-ref + if: always() && !startsWith(github.head_ref, 'release/') + run: | + echo "ref=${{ github.event.pull_request.base.sha }}" >> $GITHUB_OUTPUT + + # Check API against the latest commit on the base branch + - name: Run Nox + run: | + nox -- ${{ steps.set-ref.outputs.ref }} diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 98f8e85ff..122e9d090 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -48,7 +48,7 @@ jobs: --with benchmark --all-extras - - uses: CodSpeedHQ/action@v2 + - uses: CodSpeedHQ/action@v3 with: token: ${{ secrets.CODSPEED_TOKEN }} run: pytest tests/ --codspeed diff --git a/.github/workflows/constraints.txt b/.github/workflows/constraints.txt index 20948dd13..e6615dcc3 100644 --- a/.github/workflows/constraints.txt +++ b/.github/workflows/constraints.txt @@ -1,7 +1,8 @@ -pip==24.0 +griffe==0.48.0 +pip==24.2 poetry==1.8.3 -poetry-plugin-export==1.7.1 -poetry-dynamic-versioning==1.3.0 -pre-commit==3.7.0 +poetry-plugin-export==1.8.0 +poetry-dynamic-versioning==1.4.0 +pre-commit==3.8.0 nox==2024.4.15 nox-poetry==1.0.3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d35972778..cfb5ac75e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,12 +3,9 @@ name: Release on: push: -permissions: - contents: write # Needed to upload artifacts to the release - id-token: write # Needed for OIDC PyPI publishing - jobs: build: + name: Build artifacts runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -16,27 +13,91 @@ jobs: fetch-depth: 0 - uses: hynek/build-and-inspect-python-package@v2 + check-tag: + name: Check tag + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + outputs: + is_final: ${{ steps.check.outputs.is_final }} + steps: + - name: Check if tag is a pre-release + id: check + run: | + echo "is_final=$(echo '${{ github.ref }}' | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$' && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + + provenance: + name: Provenance + runs-on: ubuntu-latest + needs: [build] + if: startsWith(github.ref, 'refs/tags/') + permissions: + id-token: write # Needed for attestations + attestations: write # Needed for attestations + outputs: + bundle-path: ${{ steps.attest.outputs.bundle-path }} + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - uses: actions/attest-build-provenance@v1 + id: attest + with: + subject-path: "./dist/singer_sdk*" + - uses: actions/upload-artifact@v4 + with: + name: Attestations + path: ${{ steps.attest.outputs.bundle-path }} + publish: - name: Publish to PyPI + name: PyPI runs-on: ubuntu-latest needs: [build] environment: name: publishing url: https://pypi.org/p/singer-sdk if: startsWith(github.ref, 'refs/tags/') + permissions: + id-token: write # Needed for OIDC PyPI publishing + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Publish + uses: pypa/gh-action-pypi-publish@v1.9.0 + + upload-to-release: + name: Upload files to release + runs-on: ubuntu-latest + needs: [build, check-tag, provenance] + if: ${{ startsWith(github.ref, 'refs/tags/') && needs.check-tag.outputs.is_final == 'true' }} + permissions: + contents: write # Needed for uploading files to the release + steps: - uses: actions/download-artifact@v4 with: name: Packages path: dist - - name: Upload wheel to release + + - uses: actions/download-artifact@v4 + with: + name: Attestations + path: attestations + + - name: Upload wheel and sdist to release uses: svenstaro/upload-release-action@v2 with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: dist/*.whl + file: dist/singer_sdk* tag: ${{ github.ref }} overwrite: true file_glob: true - - name: Publish - uses: pypa/gh-action-pypi-publish@v1.8.14 + - name: Upload attestations to release + uses: svenstaro/upload-release-action@v2 + with: + file: attestations/attestation.jsonl + tag: ${{ github.ref }} + overwrite: true + asset_name: attestations.intoto.jsonl diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 960aede32..3acc509fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,9 +113,6 @@ jobs: SAMPLE_TAP_GITLAB_GROUP_IDS: ${{ secrets.SAMPLE_TAP_GITLAB_GROUP_IDS }} SAMPLE_TAP_GITLAB_PROJECT_IDS: ${{ secrets.SAMPLE_TAP_GITLAB_PROJECT_IDS }} SAMPLE_TAP_GITLAB_START_DATE: "2022-01-01T00:00:00Z" - SAMPLE_TAP_GOOGLE_ANALYTICS_CLIENT_EMAIL: ${{ secrets.SAMPLE_TAP_GOOGLE_ANALYTICS_CLIENT_EMAIL }} - SAMPLE_TAP_GOOGLE_ANALYTICS_PRIVATE_KEY: ${{ secrets.SAMPLE_TAP_GOOGLE_ANALYTICS_PRIVATE_KEY }} - SAMPLE_TAP_GOOGLE_ANALYTICS_VIEW_ID: ${{ secrets.SAMPLE_TAP_GOOGLE_ANALYTICS_VIEW_ID }} steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e5e67b33..e3bfd2a9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,6 +23,7 @@ repos: cookiecutter/.*/meltano.yml| cookiecutter/.*/.pre-commit-config.yaml| cookiecutter/.*/dependabot.yml| + cookiecutter/.*/build.yml| cookiecutter/.*/test.yml )$ - id: end-of-file-fixer @@ -42,14 +43,14 @@ repos: )$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.29.1 hooks: - id: check-dependabot - id: check-github-workflows - id: check-readthedocs - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.3 + rev: v0.5.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes] @@ -63,18 +64,6 @@ repos: cookiecutter/.* )$ -- repo: https://github.com/pycqa/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - additional_dependencies: - - darglint==1.8.1 - files: | - (?x)^( - singer_sdk/.*| - samples/.* - )$ - - repo: https://github.com/python-poetry/poetry rev: 1.8.0 hooks: diff --git a/.readthedocs.yml b/.readthedocs.yml index decc69828..3b35af1b7 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.12" sphinx: builder: html diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1d1ed69..27d538a6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.39.1 (2024-08-07) + +### 🐛 Fixes + +- [#2589](https://github.com/meltano/sdk/issues/2589) Make sink assertion compatible with stream maps -- _**Thanks @JCotton1123!**_ +- [#2592](https://github.com/meltano/sdk/issues/2592) Fixed typos in `--about` plain text output +- [#2580](https://github.com/meltano/sdk/issues/2580) Date fields are now properly serialized as ISO dates, i.e. "YYYY-MM-DD" +- [#2583](https://github.com/meltano/sdk/issues/2583) Quote add-column-ddl with column starting with `_` (underscore) based on engine -- _**Thanks @haleemur!**_ +- [#2582](https://github.com/meltano/sdk/issues/2582) DDL for adding a column now uses a SQLAlchemy-compiled clause to apply proper quoting -- _**Thanks @haleemur!**_ +- [#2418](https://github.com/meltano/sdk/issues/2418) Check replication method instead of key to determine if a SQL stream is sorted + +### ⚙️ Under the Hood + +- [#2520](https://github.com/meltano/sdk/issues/2520) Remove unused dependencies `pendulum` and `python-dateutil` + +### 📚 Documentation Improvements + +- [#2579](https://github.com/meltano/sdk/issues/2579) Documented support for `packaging` semantic type in contributing guide + +## v0.39.0 (2024-07-27) + +### ✨ New + +- [#2432](https://github.com/meltano/sdk/issues/2432) Developers can now customize the default logging configuration for their taps/targets by adding `default_logging.yml` to their package +- [#2531](https://github.com/meltano/sdk/issues/2531) The `json` module is now avaiable to stream maps -- _**Thanks @grigi!**_ +- [#2529](https://github.com/meltano/sdk/issues/2529) Stream sync context is now available to all instances methods as a `Stream.context` attribute + +### 🐛 Fixes + +- [#2554](https://github.com/meltano/sdk/issues/2554) Use mapped stream aliases when handling `ACTIVATE_VERSION` messages in the base target class +- [#2526](https://github.com/meltano/sdk/issues/2526) Moved up the supported Python versions in the Markdown output of `--about` + +### ⚙️ Under the Hood + +- [#2564](https://github.com/meltano/sdk/issues/2564) Make `SQLSink` a generic with a `SQLConnector` type parameter +- [#2540](https://github.com/meltano/sdk/issues/2540) Implement abstract `serialize_message` for Singer writers +- [#2259](https://github.com/meltano/sdk/issues/2259) Centralize JSON SerDe into helper functions -- _**Thanks @BuzzCutNorman!**_ +- [#2525](https://github.com/meltano/sdk/issues/2525) Make `PyJWT` and `cryptography` dependencies optional +- [#2528](https://github.com/meltano/sdk/issues/2528) Moved class-level attributes to the top in REST tap template +- [#2132](https://github.com/meltano/sdk/issues/2132) Limit internal usage of pendulum + +### 📚 Documentation Improvements + +- [#2557](https://github.com/meltano/sdk/issues/2557) Document that `get_starting_timestamp` requires setting a non-null replication_key +- [#2556](https://github.com/meltano/sdk/issues/2556) Reference state partitioning in stream partitioning page +- [#2536](https://github.com/meltano/sdk/issues/2536) Prepare for RTD addons migration +- [#2535](https://github.com/meltano/sdk/issues/2535) Added more intersphinx links to Python and Faker docs +- [#2530](https://github.com/meltano/sdk/issues/2530) Explained how the request URL is generated from `url_base`, `path` and the sync context +- [#2527](https://github.com/meltano/sdk/issues/2527) Updated the footer +- [#2506](https://github.com/meltano/sdk/issues/2506) Fixed a typo in the stream maps docs + +## v0.38.0 (2024-06-17) + +### ✨ New + +- [#2433](https://github.com/meltano/sdk/issues/2433) Tap developers can now disable HTTP redirects +- [#2426](https://github.com/meltano/sdk/issues/2426) Added an optional GitHub workflow to publish to PyPI with trusted publishers + +### 🐛 Fixes + +- [#2438](https://github.com/meltano/sdk/issues/2438) Null replication values are now handled when incrementing bookmarks +- [#2431](https://github.com/meltano/sdk/issues/2431) Updated cookiecutter VSCode `launch.json` to use `debugpy` +- [#2421](https://github.com/meltano/sdk/issues/2421) An error message is now logged every time schema validation fails for any record + +### ⚙️ Under the Hood + +- [#2455](https://github.com/meltano/sdk/issues/2455) Use parent `datetime.datetime` class in type conforming checks +- [#2453](https://github.com/meltano/sdk/issues/2453) Change to return type of `utc_now` from `pendulum.DateTime` to `datetime.datetime` + +### 📚 Documentation Improvements + +- [#2449](https://github.com/meltano/sdk/issues/2449) Add a short guide on defining a configuration schema +- [#2436](https://github.com/meltano/sdk/issues/2436) Documented how context fields are passed to a child stream +- [#2435](https://github.com/meltano/sdk/issues/2435) Using an empty list for `__key_properties__` to disable a stream primary keys is now recommended as an alternative to `null` + ## v0.37.0 (2024-04-29) ### ✨ New diff --git a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/dependabot.yml b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/dependabot.yml index 933e6b1c2..0660ffdd4 100644 --- a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/dependabot.yml +++ b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/dependabot.yml @@ -8,19 +8,34 @@ updates: - package-ecosystem: pip directory: "/" schedule: - interval: "daily" + interval: weekly commit-message: prefix: "chore(deps): " prefix-development: "chore(deps-dev): " + groups: + development-dependencies: + dependency-type: development + runtime-dependencies: + dependency-type: production + update-types: + - "patch" - package-ecosystem: pip directory: "/.github/workflows" schedule: - interval: daily + interval: weekly commit-message: prefix: "ci: " + groups: + ci: + patterns: + - "*" - package-ecosystem: github-actions directory: "/" schedule: - interval: "weekly" + interval: weekly commit-message: prefix: "ci: " + groups: + actions: + patterns: + - "*" diff --git a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/workflows/build.yml b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/workflows/build.yml new file mode 100644 index 000000000..f9503baf6 --- /dev/null +++ b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: Release + +on: + push: + +permissions: + contents: write # Needed to upload artifacts to the release + id-token: write # Needed for OIDC PyPI publishing + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: hynek/build-and-inspect-python-package@v2 + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [build] + ## TODO: optionally provide the name of the environment for the trusted + ## publisher on PyPI + ## https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment + # environment: pypi + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Upload wheel to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: {{ '${{secrets.GITHUB_TOKEN}}' }} + file: dist/*.whl + tag: {{ '${{github.ref}}' }} + overwrite: true + file_glob: true + + - name: Publish + ## TODO: create a trusted publisher on PyPI + ## https://docs.pypi.org/trusted-publishers/ + uses: pypa/gh-action-pypi-publish@v1.9.0 diff --git a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.pre-commit-config.yaml b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.pre-commit-config.yaml index 37b3bf300..29308ec2d 100644 --- a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.pre-commit-config.yaml +++ b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/.pre-commit-config.yaml @@ -18,19 +18,19 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.29.1 hooks: - id: check-dependabot - id: check-github-workflows - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.5.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.11.1 hooks: - id: mypy diff --git a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/meltano.yml b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/meltano.yml index 019015d06..7ee8496d7 100644 --- a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/meltano.yml +++ b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/meltano.yml @@ -13,18 +13,26 @@ plugins: streams: - stream_name: animals input_filename: https://raw.githubusercontent.com/meltano/tap-smoke-test/main/demo-data/animals-data.jsonl + loaders: - name: target-jsonl variant: andyh1203 pip_url: target-jsonl + mappers: - name: "{{cookiecutter.mapper_id}}" - pip_url: -e . namespace: "{{cookiecutter.library_name}}" - # TODO: replace these with the actual settings + pip_url: -e . + + # TODO: Declare settings and their types here: settings: - name: example_config kind: string + label: Example Config + description: An example configuration setting + + # TODO: Declare mapping instances here: + # https://docs.meltano.com/guide/mappers/#example-1 mappings: - name: example config: diff --git a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/pyproject.toml b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/pyproject.toml index 87d9576c5..e77cf5346 100644 --- a/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/pyproject.toml +++ b/cookiecutter/mapper-template/{{cookiecutter.mapper_id}}/pyproject.toml @@ -5,7 +5,7 @@ name = "{{cookiecutter.variant}}-{{cookiecutter.mapper_id}}" name = "{{cookiecutter.mapper_id}}" {%- endif %} version = "0.0.1" -description = "`{{cookiecutter.mapper_id}}` is a Singer mapper {{cookiecutter.name}}, built with the Meltano Singer SDK." +description = "Singer mapper {{cookiecutter.name}}, built with the Meltano Singer SDK." readme = "README.md" authors = ["{{ cookiecutter.admin_name }} <{{ cookiecutter.admin_email }}>"] keywords = [ @@ -31,16 +31,19 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8" -singer-sdk = { version="~=0.37.0"{{ ', extras = ["faker"]' if cookiecutter.faker_extra }} } +singer-sdk = { version="~=0.39.1"{{ ', extras = ["faker"]' if cookiecutter.faker_extra }} } fs-s3fs = { version = "~=1.1.1", optional = true } [tool.poetry.group.dev.dependencies] -pytest = ">=7.4.0" -singer-sdk = { version="~=0.37.0", extras = ["testing"] } +pytest = ">=8" +singer-sdk = { version="~=0.39.1", extras = ["testing"] } [tool.poetry.extras] s3 = ["fs-s3fs"] +[tool.pytest.ini_options] +addopts = '--durations=10' + [tool.mypy] python_version = "3.12" warn_unused_configs = true diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/dependabot.yml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/dependabot.yml index 933e6b1c2..0660ffdd4 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/dependabot.yml +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/dependabot.yml @@ -8,19 +8,34 @@ updates: - package-ecosystem: pip directory: "/" schedule: - interval: "daily" + interval: weekly commit-message: prefix: "chore(deps): " prefix-development: "chore(deps-dev): " + groups: + development-dependencies: + dependency-type: development + runtime-dependencies: + dependency-type: production + update-types: + - "patch" - package-ecosystem: pip directory: "/.github/workflows" schedule: - interval: daily + interval: weekly commit-message: prefix: "ci: " + groups: + ci: + patterns: + - "*" - package-ecosystem: github-actions directory: "/" schedule: - interval: "weekly" + interval: weekly commit-message: prefix: "ci: " + groups: + actions: + patterns: + - "*" diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/workflows/build.yml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/workflows/build.yml new file mode 100644 index 000000000..f9503baf6 --- /dev/null +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: Release + +on: + push: + +permissions: + contents: write # Needed to upload artifacts to the release + id-token: write # Needed for OIDC PyPI publishing + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: hynek/build-and-inspect-python-package@v2 + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [build] + ## TODO: optionally provide the name of the environment for the trusted + ## publisher on PyPI + ## https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment + # environment: pypi + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Upload wheel to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: {{ '${{secrets.GITHUB_TOKEN}}' }} + file: dist/*.whl + tag: {{ '${{github.ref}}' }} + overwrite: true + file_glob: true + + - name: Publish + ## TODO: create a trusted publisher on PyPI + ## https://docs.pypi.org/trusted-publishers/ + uses: pypa/gh-action-pypi-publish@v1.9.0 diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.pre-commit-config.yaml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.pre-commit-config.yaml index 253f7263c..ced959bcc 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.pre-commit-config.yaml +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.pre-commit-config.yaml @@ -18,20 +18,20 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.29.1 hooks: - id: check-dependabot - id: check-github-workflows - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.5.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.11.1 hooks: - id: mypy additional_dependencies: diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.vscode/launch.json b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.vscode/launch.json index 2605119c4..8fde19f6e 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.vscode/launch.json +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/.vscode/launch.json @@ -6,7 +6,7 @@ "configurations": [ { "name": "{{ cookiecutter.tap_id }}", - "type": "python", + "type": "debugpy", "request": "launch", "cwd": "${workspaceFolder}", "program": "{{ cookiecutter.library_name }}", diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/meltano.yml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/meltano.yml index c4f2ba480..8855ef34b 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/meltano.yml +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/meltano.yml @@ -15,16 +15,32 @@ plugins: - discover - about - stream-maps - config: - start_date: '2010-01-01T00:00:00Z' + + # TODO: Declare settings and their types here: settings: - # TODO: To configure using Meltano, declare settings and their types here: - name: username + label: Username + description: The username to use for authentication + - name: password kind: password + label: Password + description: The password to use for authentication sensitive: true + - name: start_date - value: '2010-01-01T00:00:00Z' + kind: date_iso8601 + label: Start Date + description: Initial date to start extracting data from + + # TODO: Declare required settings here: + settings_group_validation: + - [username, password] + + # TODO: Declare default configuration values here: + config: + start_date: '2010-01-01T00:00:00Z' + loaders: - name: target-jsonl variant: andyh1203 diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml index 7157fe436..98851d925 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/pyproject.toml @@ -5,7 +5,7 @@ name = "{{cookiecutter.variant}}-{{cookiecutter.tap_id}}" name = "{{cookiecutter.tap_id}}" {%- endif %} version = "0.0.1" -description = "`{{cookiecutter.tap_id}}` is a Singer tap for {{cookiecutter.source_name}}, built with the Meltano Singer SDK." +description = "Singer tap for {{cookiecutter.source_name}}, built with the Meltano Singer SDK." readme = "README.md" authors = ["{{ cookiecutter.admin_name }} <{{ cookiecutter.admin_email }}>"] keywords = [ @@ -30,27 +30,30 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8" -importlib-resources = { version = "==6.1.*", python = "<3.9" } -singer-sdk = { version="~=0.37.0", extras = [ +importlib-resources = { version = "==6.4.*", python = "<3.9" } +singer-sdk = { version="~=0.39.1", extras = [ {%- if cookiecutter.auth_method == "JWT" -%}"jwt", {% endif -%} {%- if cookiecutter.faker_extra -%}"faker",{%- endif -%} ] } fs-s3fs = { version = "~=1.1.1", optional = true } {%- if cookiecutter.stream_type in ["REST", "GraphQL"] %} -requests = "~=2.31.0" +requests = "~=2.32.3" {%- endif %} [tool.poetry.group.dev.dependencies] -pytest = ">=7.4.0" +pytest = ">=8" {%- if cookiecutter.auth_method == "JWT" %} -singer-sdk = { version="~=0.37.0", extras = ["jwt", "testing"] } +singer-sdk = { version="~=0.39.1", extras = ["jwt", "testing"] } {%- else %} -singer-sdk = { version="~=0.37.0", extras = ["testing"] } +singer-sdk = { version="~=0.39.1", extras = ["testing"] } {%- endif %} [tool.poetry.extras] s3 = ["fs-s3fs"] +[tool.pytest.ini_options] +addopts = '--durations=10' + [tool.mypy] python_version = "3.12" warn_unused_configs = true diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/graphql-client.py b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/graphql-client.py index 66505556d..4e878cb23 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/graphql-client.py +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/graphql-client.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Iterable +from typing import TYPE_CHECKING, Iterable import requests # noqa: TCH002 from singer_sdk.streams import {{ cookiecutter.stream_type }}Stream @@ -12,6 +12,9 @@ from {{ cookiecutter.library_name }}.auth import {{ cookiecutter.source_name }}Authenticator {%- endif %} +if TYPE_CHECKING: + from singer_sdk.helpers.types import Context + class {{ cookiecutter.source_name }}Stream({{ cookiecutter.stream_type }}Stream): """{{ cookiecutter.source_name }} stream class.""" @@ -67,7 +70,7 @@ def parse_response(self, response: requests.Response) -> Iterable[dict]: def post_process( self, row: dict, - context: dict | None = None, # noqa: ARG002 + context: Context | None = None, # noqa: ARG002 ) -> dict | None: """As needed, append or transform raw data to match expected structure. diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/other-client.py b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/other-client.py index c2def6322..1952579d7 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/other-client.py +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/other-client.py @@ -2,17 +2,20 @@ from __future__ import annotations -from typing import Iterable +from typing import TYPE_CHECKING, Iterable from singer_sdk.streams import Stream +if TYPE_CHECKING: + from singer_sdk.helpers.types import Context + class {{ cookiecutter.source_name }}Stream(Stream): """Stream class for {{ cookiecutter.source_name }} streams.""" def get_records( self, - context: dict | None, # noqa: ARG002 + context: Context | None, # noqa: ARG002 ) -> Iterable[dict]: """Return a generator of record-type dictionary objects. diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/rest-client.py b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/rest-client.py index 16ad9d23f..fb805ad55 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/rest-client.py +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/rest-client.py @@ -6,9 +6,8 @@ {%- if cookiecutter.auth_method in ("OAuth2", "JWT") %} from functools import cached_property {%- endif %} -from typing import Any, Callable, Iterable +from typing import TYPE_CHECKING, Any, Iterable -import requests {% if cookiecutter.auth_method == "API Key" -%} from singer_sdk.authenticators import APIKeyAuthenticator from singer_sdk.helpers.jsonpath import extract_jsonpath @@ -46,7 +45,14 @@ else: import importlib_resources -_Auth = Callable[[requests.PreparedRequest], requests.PreparedRequest] +if TYPE_CHECKING: + import requests + {%- if cookiecutter.auth_method in ("OAuth2", "JWT") %} + from singer_sdk.helpers.types import Auth, Context + {%- else %} + from singer_sdk.helpers.types import Context + {%- endif %} + # TODO: Delete this is if not using json files for schema definition SCHEMAS_DIR = importlib_resources.files(__package__) / "schemas" @@ -55,21 +61,22 @@ class {{ cookiecutter.source_name }}Stream({{ cookiecutter.stream_type }}Stream): """{{ cookiecutter.source_name }} stream class.""" + # Update this value if necessary or override `parse_response`. + records_jsonpath = "$[*]" + + # Update this value if necessary or override `get_new_paginator`. + next_page_token_jsonpath = "$.next_page" # noqa: S105 + @property def url_base(self) -> str: """Return the API URL root, configurable via tap settings.""" # TODO: hardcode a value here, or retrieve it from self.config return "https://api.mysample.com" - records_jsonpath = "$[*]" # Or override `parse_response`. - - # Set this value or override `get_new_paginator`. - next_page_token_jsonpath = "$.next_page" # noqa: S105 - {%- if cookiecutter.auth_method in ("OAuth2", "JWT") %} @cached_property - def authenticator(self) -> _Auth: + def authenticator(self) -> Auth: """Return a new authenticator object. Returns: @@ -156,7 +163,7 @@ def get_new_paginator(self) -> BaseAPIPaginator: def get_url_params( self, - context: dict | None, # noqa: ARG002 + context: Context | None, # noqa: ARG002 next_page_token: Any | None, # noqa: ANN401 ) -> dict[str, Any]: """Return a dictionary of values to be used in URL parameterization. @@ -178,7 +185,7 @@ def get_url_params( def prepare_request_payload( self, - context: dict | None, # noqa: ARG002 + context: Context | None, # noqa: ARG002 next_page_token: Any | None, # noqa: ARG002, ANN401 ) -> dict | None: """Prepare the data payload for the REST API request. @@ -210,7 +217,7 @@ def parse_response(self, response: requests.Response) -> Iterable[dict]: def post_process( self, row: dict, - context: dict | None = None, # noqa: ARG002 + context: Context | None = None, # noqa: ARG002 ) -> dict | None: """As needed, append or transform raw data to match expected structure. diff --git a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/tap.py b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/tap.py index df3f9f754..74c8927e9 100644 --- a/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/tap.py +++ b/cookiecutter/tap-template/{{cookiecutter.tap_id}}/{{cookiecutter.library_name}}/tap.py @@ -50,6 +50,16 @@ class Tap{{ cookiecutter.source_name }}({{ 'SQL' if cookiecutter.stream_type == default="https://api.mysample.com", description="The url for the API service", ), + {%- if cookiecutter.stream_type in ("GraphQL", "REST") %} + th.Property( + "user_agent", + th.StringType, + description=( + "A custom User-Agent header to send with each request. Default is " + "'/'" + ), + ), + {%- endif %} ).to_dict() {%- if cookiecutter.stream_type in ("GraphQL", "REST", "Other") %} diff --git a/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/dependabot.yml b/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/dependabot.yml index 933e6b1c2..0660ffdd4 100644 --- a/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/dependabot.yml +++ b/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/dependabot.yml @@ -8,19 +8,34 @@ updates: - package-ecosystem: pip directory: "/" schedule: - interval: "daily" + interval: weekly commit-message: prefix: "chore(deps): " prefix-development: "chore(deps-dev): " + groups: + development-dependencies: + dependency-type: development + runtime-dependencies: + dependency-type: production + update-types: + - "patch" - package-ecosystem: pip directory: "/.github/workflows" schedule: - interval: daily + interval: weekly commit-message: prefix: "ci: " + groups: + ci: + patterns: + - "*" - package-ecosystem: github-actions directory: "/" schedule: - interval: "weekly" + interval: weekly commit-message: prefix: "ci: " + groups: + actions: + patterns: + - "*" diff --git a/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/workflows/build.yml b/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/workflows/build.yml new file mode 100644 index 000000000..f9503baf6 --- /dev/null +++ b/cookiecutter/target-template/{{cookiecutter.target_id}}/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: Release + +on: + push: + +permissions: + contents: write # Needed to upload artifacts to the release + id-token: write # Needed for OIDC PyPI publishing + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: hynek/build-and-inspect-python-package@v2 + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [build] + ## TODO: optionally provide the name of the environment for the trusted + ## publisher on PyPI + ## https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment + # environment: pypi + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Upload wheel to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: {{ '${{secrets.GITHUB_TOKEN}}' }} + file: dist/*.whl + tag: {{ '${{github.ref}}' }} + overwrite: true + file_glob: true + + - name: Publish + ## TODO: create a trusted publisher on PyPI + ## https://docs.pypi.org/trusted-publishers/ + uses: pypa/gh-action-pypi-publish@v1.9.0 diff --git a/cookiecutter/target-template/{{cookiecutter.target_id}}/.pre-commit-config.yaml b/cookiecutter/target-template/{{cookiecutter.target_id}}/.pre-commit-config.yaml index 6e41ca63e..3cab6f92b 100644 --- a/cookiecutter/target-template/{{cookiecutter.target_id}}/.pre-commit-config.yaml +++ b/cookiecutter/target-template/{{cookiecutter.target_id}}/.pre-commit-config.yaml @@ -18,20 +18,20 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.29.1 hooks: - id: check-dependabot - id: check-github-workflows - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.5.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.11.1 hooks: - id: mypy additional_dependencies: diff --git a/cookiecutter/target-template/{{cookiecutter.target_id}}/meltano.yml b/cookiecutter/target-template/{{cookiecutter.target_id}}/meltano.yml index c63837370..dab8bf213 100644 --- a/cookiecutter/target-template/{{cookiecutter.target_id}}/meltano.yml +++ b/cookiecutter/target-template/{{cookiecutter.target_id}}/meltano.yml @@ -5,7 +5,15 @@ default_environment: test environments: - name: test plugins: - extractors: [] + extractors: + - name: tap-smoke-test + variant: meltano + pip_url: git+https://github.com/meltano/tap-smoke-test.git + config: + streams: + - stream_name: animals + input_filename: https://raw.githubusercontent.com/meltano/tap-smoke-test/main/demo-data/animals-data.jsonl + loaders: - name: "{{cookiecutter.target_id}}" namespace: "{{cookiecutter.library_name}}" @@ -14,13 +22,19 @@ plugins: - about - stream-maps - record-flattening - config: - start_date: '2010-01-01T00:00:00Z' + + # TODO: Declare settings and their types here: settings: - # TODO: To configure using Meltano, declare settings and their types here: - name: username + label: Username + description: The username to use for authentication + - name: password kind: password + label: Password + description: The password to use for authentication sensitive: true - - name: start_date - value: '2010-01-01T00:00:00Z' + + # TODO: Declare required settings here: + settings_group_validation: + - [username, password] diff --git a/cookiecutter/target-template/{{cookiecutter.target_id}}/pyproject.toml b/cookiecutter/target-template/{{cookiecutter.target_id}}/pyproject.toml index c61bd5f42..5aa268f7c 100644 --- a/cookiecutter/target-template/{{cookiecutter.target_id}}/pyproject.toml +++ b/cookiecutter/target-template/{{cookiecutter.target_id}}/pyproject.toml @@ -5,7 +5,7 @@ name = "{{cookiecutter.variant}}-{{cookiecutter.target_id}}" name = "{{cookiecutter.target_id}}" {%- endif %} version = "0.0.1" -description = "`{{cookiecutter.target_id}}` is a Singer target for {{cookiecutter.destination_name}}, built with the Meltano Singer SDK." +description = "Singer target for {{cookiecutter.destination_name}}, built with the Meltano Singer SDK." readme = "README.md" authors = ["{{ cookiecutter.admin_name }} <{{ cookiecutter.admin_email }}>"] keywords = [ @@ -30,19 +30,26 @@ packages = [ [tool.poetry.dependencies] python = ">=3.8" -singer-sdk = { version="~=0.37.0"{{ ', extras = ["faker"]' if cookiecutter.faker_extra }} } +singer-sdk = { version="~=0.39.1"{{ ', extras = ["faker"]' if cookiecutter.faker_extra }} } fs-s3fs = { version = "~=1.1.1", optional = true } {%- if cookiecutter.serialization_method != "SQL" %} -requests = "~=2.31.0" +requests = "~=2.32.3" {%- endif %} [tool.poetry.dev-dependencies] -pytest = ">=7.4.0" -singer-sdk = { version="~=0.37.0", extras = ["testing"] } +pytest = ">=8" +singer-sdk = { version="~=0.39.1", extras = ["testing"] } [tool.poetry.extras] s3 = ["fs-s3fs"] +[tool.pytest.ini_options] +addopts = '--durations=10' + +[tool.mypy] +python_version = "3.12" +warn_unused_configs = true + [tool.ruff] src = ["{{cookiecutter.library_name}}"] target-version = "py38" diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e01916e27..d147d775a 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -187,6 +187,7 @@ In general, PR titles should follow the format `: `, where type is a - `docs` - `feat` - `fix` +- `packaging` - `perf` - `refactor` - `revert` diff --git a/docs/code_samples.md b/docs/code_samples.md index 6d9efefd1..1783b30f8 100644 --- a/docs/code_samples.md +++ b/docs/code_samples.md @@ -4,18 +4,18 @@ Below you will find a collection of code samples which can be used for inspirati ## Project Samples -Below are full project samples, contributed by members in the community. Use these for inspiration -or to get more information on what an SDK-based tap or target will look like. - -- [tap-bamboohr by Auto IDM](https://gitlab.com/autoidm/tap-bamboohr) -- [tap-confluence by @edgarrmondragon](https://github.com/edgarrmondragon/tap-confluence) -- [tap-investing by @DouweM](https://gitlab.com/DouweM/tap-investing) -- [tap-parquet by AJ](https://github.com/dataops-tk/tap-parquet) -- [tap-powerbi-metadata by Slalom](https://github.com/dataops-tk/tap-powerbi-metadata) -- [target-athena, Community Project led by Andrew Stewart](https://github.com/dataops-tk/target-athena) - -To add your project to this list, please -[submit an issue](https://github.com/meltano/sdk/issues/new). +The following are full project samples, contributed by members of the community: + +- A REST Stream: [MeltanoLabs/tap-pulumi-cloud](https://github.com/MeltanoLabs/tap-pulumi-cloud) +- A SQL Target: [MeltanoLabs/target-postgres](https://github.com/MeltanoLabs/target-postgres) +- A SQL Tap: [MeltanoLabs/tap-postgres](https://github.com/MeltanoLabs/tap-postgres) +- A Cloud Service: [MeltanoLabs/tap-cloudwatch](https://github.com/MeltanoLabs/tap-cloudwatch) +- A REST Stream with complex and varied auth options: [MeltanoLabs/tap-github](https://github.com/MeltanoLabs/tap-github) + +There are many more examples available: go to [Meltano Hub](https://hub.meltano.com) and type `sdk` in the searchbar to see a list of taps and targets created with the Singer SDK. + +To add your project to Meltano Hub, please +[submit an issue](https://github.com/meltano/hub/issues/new?assignees=edgarrmondragon%2Cpnadolny13&labels=valuestream%2FHub&projects=&template=new_plugin.yml&title=Add+Plugin%3A+%3Cinsert+plugin+name%3E). ## Reusable Code Snippets diff --git a/docs/conf.py b/docs/conf.py index 9a50bf165..447549f24 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,9 @@ # from __future__ import annotations +import os import sys +from datetime import datetime from pathlib import Path sys.path.insert(0, str(Path("..").resolve())) @@ -23,11 +25,11 @@ # -- Project information ----------------------------------------------------- project = "Meltano Singer SDK" -copyright = "2021, Meltano Core Team and Contributors" # noqa: A001 +copyright = f"{datetime.now().year}, Arch Data, Inc and Contributors" # noqa: A001, DTZ005 author = "Meltano Core Team and Contributors" # The full version, including alpha/beta/rc tags -release = "0.37.0" +release = "0.39.1" # -- General configuration ------------------------------------------------------------- @@ -59,10 +61,19 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ----------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# + +# Define the canonical URL for sdk.meltano.com +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") +html_context = {} + +# Tell Jinja2 templates the build is running on Read the Docs +if os.environ.get("READTHEDOCS", "") == "True": + html_context["READTHEDOCS"] = True + html_logo = "_static/img/logo.svg" html_theme = "furo" html_theme_options = { @@ -154,6 +165,7 @@ intersphinx_mapping = { "requests": ("https://requests.readthedocs.io/en/latest/", None), "python": ("https://docs.python.org/3/", None), + "faker": ("https://faker.readthedocs.io/en/master/", None), } # -- Options for linkcode -------------------------------------------------------------- diff --git a/docs/dev_guide.md b/docs/dev_guide.md index 5d520916e..cc7cd9004 100644 --- a/docs/dev_guide.md +++ b/docs/dev_guide.md @@ -98,6 +98,10 @@ detailed guide. ``` ```` +### Application configuration + +The SDK lets you define a configuration schema for your tap or target with full support for JSON schema validation. Read the more in-depth guide on [defining a configuration schema](./guides/config-schema.md). + ### Using an existing library In some cases, there may already be a library that connects to the API and all you need the SDK for diff --git a/docs/guides/config-schema.md b/docs/guides/config-schema.md new file mode 100644 index 000000000..e5aad4378 --- /dev/null +++ b/docs/guides/config-schema.md @@ -0,0 +1,34 @@ +# Defining a configuration schema + +The Singer SDK provides a way to define a configuration schema for your tap or target. This schema is used to validate the configuration provided by the user and to provide a default configuration when the user does not provide one. + +The configuration schema is defined as a JSON object that describes the configuration options that your tap or target supports. We recommend using the [JSON Schema helpers](../typing.rst) provided by the SDK to define the schema. + +Here is an example of a configuration schema for a tap: + +```python +from singer_sdk import Tap +from singer_sdk import typing as th + + +class MyTap(Tap): + name = "my-tap" + + config_jsonschema = th.PropertiesList( + th.Property("api_key", th.StringType, required=True), + th.Property("base_url", th.StringType, default="https://api.example.com"), + th.Property("start_date", th.DateTimeType), + ).to_dict() +``` + +Explanation of the configuration schema defined above: + +- The `config_jsonschema` attribute is a JSON object that describes the configuration options that the tap supports. +- The `th.PropertiesList` helper is used to define a list of properties. +- The `th.Property` helper is used to define a property with a name, type, and other attributes. +- The `th.StringType`, `th.DateTimeType`, etc. helpers are used to define the type of the property. +- The `required` attribute is used to mark a property as required. The tap will throw an error if the user does not provide a value for a required property. +- The `default` attribute is used to provide a default value for a property. The tap will use this if the user does not provide a value, so this can be accessed in the tap or streams with square bracket syntax, i.e. `self.config["base_url"]`. +- The `to_dict()` method is used to convert the JSON object to a Python dictionary. + +See the full reference for the [typing module](../typing.rst) for more information on how to define a configuration schema. diff --git a/docs/guides/index.md b/docs/guides/index.md index e86aa149c..268f27957 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -8,4 +8,5 @@ The following pages contain useful information for developers building on top of porting pagination-classes custom-clis +config-schema ``` diff --git a/docs/implementation/logging.md b/docs/implementation/logging.md index da20a5780..adf6f2182 100644 --- a/docs/implementation/logging.md +++ b/docs/implementation/logging.md @@ -77,3 +77,21 @@ This will send metrics to a `metrics.log`: 2022-09-29 00:48:53,302 INFO METRIC: {"metric_type": "timer", "metric": "sync_duration", "value": 0.5258760452270508, "tags": {"stream": "countries", "context": {}, "status": "succeeded"}} 2022-09-29 00:48:53,303 INFO METRIC: {"metric_type": "counter", "metric": "record_count", "value": 250, "tags": {"stream": "countries", "context": {}}} ``` + +## For package developers + +If you're developing a tap or target package and would like to customize its logging configuration, you can put a `default_loggging.yml` file in the package root to set the default logging configuration for your package. This file will be used if the `SINGER_SDK_LOG_CONFIG` environment variable is not set: + +``` +. +├── README.md +├── poetry.lock +├── pyproject.toml +└── tap_example +    ├── __init__.py +    ├── __main__.py +    ├── default_logging.yml # <-- This file will be used if SINGER_SDK_LOG_CONFIG is not set +    ├── client.py +    ├── streams.py +    └── tap.py +``` diff --git a/docs/implementation/metrics.md b/docs/implementation/metrics.md index 87678cbe5..3b2a2a6a5 100644 --- a/docs/implementation/metrics.md +++ b/docs/implementation/metrics.md @@ -1,8 +1,14 @@ -# Tap Metrics +# Tap and Target Metrics Metrics logging is specified in the -[Singer Spec](https://hub.meltano.com/singer/spec#metrics). The SDK will automatically -emit metrics for `record_count`, `http_request_duration` and `sync_duration`. +[Singer Spec](https://hub.meltano.com/singer/spec#metrics). + +The SDK will automatically emit the following metrics: + +- `record_count`: The number of records processed by the tap or target. +- `http_request_duration`: The duration of HTTP requests made by the tap. +- `sync_duration`: The duration of the sync operation. +- `batch_processing_time`: The duration of processing a batch of records. ## Customization options diff --git a/docs/parent_streams.md b/docs/parent_streams.md index fec6d8fb9..7ad63d1b3 100644 --- a/docs/parent_streams.md +++ b/docs/parent_streams.md @@ -69,6 +69,10 @@ class EpicIssuesStream(GitlabStream): # ... ``` +```{note} +All the keys in the `context` dictionary are added to the child's record, but they will be automatically removed if they are not present in the child's schema. If you wish to preserve these keys in the child's record, you must add them to the child's schema. +``` + ## Additional Parent-Child References - [Singer State in SDK Taps](./implementation/state.md) diff --git a/docs/partitioning.md b/docs/partitioning.md index 95103dc45..144d210cf 100644 --- a/docs/partitioning.md +++ b/docs/partitioning.md @@ -3,6 +3,10 @@ The Tap SDK supports stream partitioning, meaning a set of substreams which each have their own state and their own distinct queryable domain. +You can read more about state partitioning in the +[State Implemetation](./implementation/state.md#partitioned-state) explanation +document. + ## If you do not require partitioning In general, developers can simply ignore the [`context`](./context_object.md) arguments diff --git a/docs/stream_maps.md b/docs/stream_maps.md index b7bffd36b..9d4a7d8a3 100644 --- a/docs/stream_maps.md +++ b/docs/stream_maps.md @@ -228,12 +228,16 @@ can be referenced directly by mapping expressions. #### Built-In Functions -- `md5()` - returns an inline MD5 hash of any string, outputting the string representation - of the hash's hex digest. +- [`md5()`](inv:python:py:module:#hashlib) - returns an inline MD5 hash of any string, outputting + the string representation of the hash's hex digest. - This is defined by the SDK internally with native python: - `hashlib.md5(.encode("utf-8")).hexdigest()`. -- `datetime` - This is the datetime module object from the Python standard library. You can access - datetime.datetime, datetime.timedelta, etc. + [`hashlib.md5(.encode("utf-8")).hexdigest()`](inv:python:py:method:#hashlib.hash.hexdigest). +- [`datetime`](inv:python:py:module:#datetime) - This is the datetime module object from the Python + standard library. You can access [`datetime.datetime`](inv:python:py:class:#datetime.datetime), + [`datetime.timedelta`](inv:python:py:class:#datetime.timedelta), etc. +- [`json`](inv:python:py:module:#json) - This is the json module object from the Python standard + library. Primarily used for calling [`json.dumps()`](inv:python:py:function:#json.dumps) + and [`json.loads()`](inv:python:py:function:#json.loads). #### Built-in Variable Names @@ -242,10 +246,14 @@ can be referenced directly by mapping expressions. - `record` - an alias for the record values dictionary in the current stream. - `_` - same as `record` but shorter to type - `self` - the existing property value if the property already exists -- `fake` - a [`Faker`](https://faker.readthedocs.io/en/master/) instance, configurable via `faker_config` (see previous example) - see the built-in [standard providers](https://faker.readthedocs.io/en/master/providers.html) for available methods +- `fake` - a [`Faker`](inv:faker:std:doc#index) instance, configurable via `faker_config` + (see previous example) - see the built-in [standard providers](inv:faker:std:doc#providers) + for available methods +- `Faker` - the [`Faker`](inv:faker:std:doc#fakerclass) class. This was made available to enable consistent data + masking by allowing users to call `Faker.seed()`. ```{tip} - The `fake` object is only available if the plugin specifies `faker` as an addtional dependency (through the `singer-sdk` `faker` extra, or directly). + The `fake` object is only available if the plugin specifies `faker` as an additional dependency (through the `singer-sdk` `faker` extra, or directly). ``` #### Automatic Schema Detection @@ -401,7 +409,7 @@ stream_maps: Notes: -- To sync the stream as if it did not contain a primary key, simply set `__key_properties__` to `null`. +- To sync the stream as if it did not contain a primary key, simply set `__key_properties__` to `null` or an empty list. - Key properties _must_ be present in the transformed stream result. Otherwise, an error will be raised. ### Add a property with a string literal value @@ -429,21 +437,47 @@ stream_maps: ``` ```` -#### Q: What is the difference between `primary_keys` and `key_properties`? +### Masking data with Faker -**A:** These two are _generally_ identical - and will only differ in cases like the above where `key_properties` is manually -overridden or nullified by the user of the tap. Developers will specify `primary_keys` for each stream in the tap, -but they do not control if the user will override `key_properties` behavior when initializing the stream. Primary keys -describe the nature of the upstream data as known by the source system. However, either through manual catalog manipulation and/or by -setting stream map transformations, the in-flight dedupe keys (`key_properties`) may be overridden or nullified by the user at any time. +It is best practice (or even a legal requirement) to mask PII/PHI in lower environments. Stream mappers have access to the `Faker` library, which can be used to generate random data in various forms/formats. -Additionally, some targets do not support primary key distinctions, and there are valid use cases to intentionally unset -the `key_properties` in an extract-load pipeline. For instance, it is common to intentionally nullify key properties to trigger -"append-only" loading behavior in certain targets, as may be required for historical reporting. This does not change the -underlying nature of the `primary_key` configuration in the upstream source data, only how it will be landed or deduped -in the downstream source. +```yaml +stream_maps: + customers: + # IMPORTANT: the `fake` variable name will only be available if faker_config is defined + first_name: fake.first_name() # generates a new random name each time +faker_config: + # set specific seed + seed: 0 + # set specific locales + locale: + - en_US + - en_GB +``` + +Be sure to checkout the [`faker` documentation](https://faker.readthedocs.io/en/master/) for all the fake data generation possibilities. + +Note that in the example above, `faker` will generate a new random value each time the `first_name()` function is invoked. This means if 3 records have a `first_name` value of `Mike`, then they will each have a different name after being mapped (for example, `Alistair`, `Debra`, `Scooby`). This can actually lead to issues when developing in the lower environments. + +Some users require consistent masking (for example, the first name `Mike` is always masked as `Debra`). Consistent masking preserves the relationship between tables and rows, while still hiding the real value. When a random mask is generated every time, relationships between tables/rows are effectively lost, making it impossible to test things like sql `JOIN`s. This can cause highly unpredictable behavior when running the same code in lower environments vs production. -## Aliasing a stream using `__alias__` +To generate consistent masked values, you must provide the **same seed each time** before invoking the faker function. + +```yaml +stream_maps: + customers: + # will always generate the same value for the same seed + first_name: Faker.seed(_['first_name']) or fake.first_name() +faker_config: + # IMPORTANT: `fake` and `Faker` names are only available if faker_config is defined. + locale: en_US +``` + +Remember, these expressions are evaluated by the [`simpleval`](https://github.com/danthedeckie/simpleeval) expression library, which only allows a single python expression (which is the reason for the `or` syntax above). + +This means if you require more advanced masking logic, which cannot be defined in a single python expression, you may need to consider a custom stream mapper. + +### Aliasing a stream using `__alias__` To alias a stream, simply add the operation `"__alias__": "new_name"` to the stream definition. For example, to alias the `customers` stream as `customer_v2`, use the @@ -469,7 +503,7 @@ stream_maps: ``` ```` -## Duplicating or splitting a stream using `__source__` +### Duplicating or splitting a stream using `__source__` To create a new stream as a copy of the original, specify the operation `"__source__": "stream_name"`. For example, you can create a copy of the `customers` stream @@ -513,9 +547,9 @@ stream_maps: ``` ```` -## Filtering out records from a stream using `__filter__` operation +### Filtering out records from a stream using `__filter__` operation -The `__filter__` operation accept a string expression which must evaluate to `true` or +The `__filter__` operation accepts a string expression which must evaluate to `true` or `false`. Filter expressions should be wrapped in `bool()` to ensure proper type conversion. For example, to only include customers with emails from the `example.com` company domain: @@ -540,6 +574,62 @@ stream_maps: ``` ```` +### Aliasing properties + +This uses a "copy-and-delete" approach with the help of `__NULL__`: + +````{tab} meltano.yml +```yaml +stream_maps: + customers: + new_field: old_field + old_field: __NULL__ +``` +```` + +````{tab} JSON +```json +{ + "stream_maps": { + "customers": { + "new_field": "old_field", + "old_field": "__NULL__" + } + } +} +``` +```` + +### Applying a mapping across two or more streams + +You can use glob expressions to apply a stream map configuration to more than one stream: + +````{tab} meltano.yml +```yaml +stream_maps: + "*": + name: first_name + first_name: __NULL__ +``` +```` + +````{tab} JSON +```json +{ + "stream_maps": { + "*": { + "name": "first_name", + "first_name": "__NULL__" + } + } +} +``` +```` + +:::{versionadded} 0.37.0 +Support for stream glob expressions. +::: + ### Understanding Filters' Affects on Parent-Child Streams Nested child streams iterations will be skipped if their parent stream has a record-level @@ -619,3 +709,17 @@ Additionally, plugins are generally expected to fail if they receive unexpected arguments. The intended use cases for stream map config values are user-defined in nature (such as the hashing use case defined above), and are unlikely to overlap with the plugin's already-existing settings. + +### Q: What is the difference between `primary_keys` and `key_properties`? + +**Answer:** These two are _generally_ identical - and will only differ in cases like the above where `key_properties` is manually +overridden or nullified by the user of the tap. Developers will specify `primary_keys` for each stream in the tap, +but they do not control if the user will override `key_properties` behavior when initializing the stream. Primary keys +describe the nature of the upstream data as known by the source system. However, either through manual catalog manipulation and/or by +setting stream map transformations, the in-flight dedupe keys (`key_properties`) may be overridden or nullified by the user at any time. + +Additionally, some targets do not support primary key distinctions, and there are valid use cases to intentionally unset +the `key_properties` in an extract-load pipeline. For instance, it is common to intentionally nullify key properties to trigger +"append-only" loading behavior in certain targets, as may be required for historical reporting. This does not change the +underlying nature of the `primary_key` configuration in the upstream source data, only how it will be landed or deduped +in the downstream source. diff --git a/noxfile.py b/noxfile.py index fdf99d5aa..a07c953ab 100644 --- a/noxfile.py +++ b/noxfile.py @@ -25,6 +25,8 @@ RUFF_OVERRIDES = """\ extend = "./pyproject.toml" + +[lint] extend-ignore = ["TD002", "TD003", "FIX002"] """ @@ -124,7 +126,7 @@ def update_snapshots(session: Session) -> None: """Update pytest snapshots.""" args = session.posargs or ["-m", "snapshot"] - session.install(".[faker,jwt]") + session.install(".[faker,jwt,parquet]") session.install(*test_dependencies) session.run("pytest", "--snapshot-update", *args) @@ -186,7 +188,7 @@ def docs_serve(session: Session) -> None: "build", "-W", ] - session.install(".[docs]") + session.install(".[docs]", "sphinx-autobuild") build_dir = Path("build") if build_dir.exists(): @@ -240,7 +242,7 @@ def test_cookiecutter(session: Session, replay_file_path: str) -> None: ) session.chdir(cc_test_output) - with Path("ruff.toml").open("w") as ruff_toml: + with Path("ruff.toml").open("w", encoding="utf-8") as ruff_toml: ruff_toml.write(RUFF_OVERRIDES) session.run( @@ -254,7 +256,7 @@ def test_cookiecutter(session: Session, replay_file_path: str) -> None: session.run("poetry", "lock", external=True) session.run("poetry", "install", external=True) - session.run("git", "init", external=True) + session.run("git", "init", "-b", "main", external=True) session.run("git", "add", ".", external=True) session.run("pre-commit", "run", "--all-files", external=True) @@ -279,3 +281,18 @@ def version_bump(session: Session) -> None: "bump", *args, ) + + +@nox.session(name="api") +def api_changes(session: nox.Session) -> None: + """Check for API changes.""" + args = [ + "griffe", + "check", + "singer_sdk", + ] + + if session.posargs: + args.append(f"-a={session.posargs[0]}") + + session.run(*args, external=True) diff --git a/poetry.lock b/poetry.lock index 8363c718d..b5e2b63bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" +version = "0.7.16" +description = "A light, configurable Sphinx theme" optional = true -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] @@ -24,37 +24,34 @@ files = [ [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = true python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] @@ -109,34 +106,6 @@ files = [ {file = "backports_datetime_fromisoformat-2.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91042b53de903e3725209ad6d69b6994ae4819614c0decd62d05dfea23f35e2b"}, ] -[[package]] -name = "backports-zoneinfo" -version = "0.2.1" -description = "Backport of the standard library zoneinfo module" -optional = false -python-versions = ">=3.6" -files = [ - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, - {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, - {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, -] - -[package.extras] -tzdata = ["tzdata"] - [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -160,17 +129,17 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.34.100" +version = "1.34.157" description = "The AWS SDK for Python" optional = true python-versions = ">=3.8" files = [ - {file = "boto3-1.34.100-py3-none-any.whl", hash = "sha256:bbe2bb0dfcd92380da2a2fa2c2f586ba06c118b796380b2d0f3d0ebd103ec28d"}, - {file = "boto3-1.34.100.tar.gz", hash = "sha256:016f6d66900bb1a835dea2063f1e91fc7057dbf7fb7df8add0706f0da9492631"}, + {file = "boto3-1.34.157-py3-none-any.whl", hash = "sha256:3cc357156df5482154a016f138d1953061a181b4c594f8b6302c9d6c024bd950"}, + {file = "boto3-1.34.157.tar.gz", hash = "sha256:7ef19ed38cba9863b58430fb4a66a72a5c250304f234bd1c16b860f9bf25677b"}, ] [package.dependencies] -botocore = ">=1.34.100,<1.35.0" +botocore = ">=1.34.157,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -179,13 +148,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.100" +version = "1.34.157" description = "Low-level, data-driven core of boto 3." optional = true python-versions = ">=3.8" files = [ - {file = "botocore-1.34.100-py3-none-any.whl", hash = "sha256:ee516fb9e9e906d311f2a9921afaf79c594db239a5b4b626e89e6960401aad0b"}, - {file = "botocore-1.34.100.tar.gz", hash = "sha256:513bea60c6531af8e1ae1fdb2947e3ef99712f39c58f4656b5efef9cb6f75a13"}, + {file = "botocore-1.34.157-py3-none-any.whl", hash = "sha256:c6cba6de8eb86ca4d2f934e009b37adbe1e7fdcfa52fbab74783f4c30676e07d"}, + {file = "botocore-1.34.157.tar.gz", hash = "sha256:5628a36cec123cdc8c1158d05a7b06aa5e53649ad73796c50ef3fb51199785fb"}, ] [package.dependencies] @@ -197,78 +166,93 @@ urllib3 = [ ] [package.extras] -crt = ["awscrt (==0.20.9)"] +crt = ["awscrt (==0.21.2)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, ] [package.dependencies] @@ -400,63 +384,83 @@ files = [ [[package]] name = "coverage" -version = "7.5.1" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, - {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, - {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, - {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, - {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, - {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, - {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, - {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, - {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, - {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, - {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, - {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, - {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, - {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -467,43 +471,38 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "42.0.7" +version = "43.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false +optional = true python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, - {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, - {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, - {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, - {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, - {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, - {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, - {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, - {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, - {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, - {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, - {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, - {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, - {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, + {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, + {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, + {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, + {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, + {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, + {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, + {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, ] [package.dependencies] @@ -516,26 +515,28 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "deptry" -version = "0.16.1" +version = "0.19.1" description = "A command line utility to check for unused, missing and transitive dependencies in a Python project." optional = false python-versions = ">=3.8" files = [ - {file = "deptry-0.16.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:29ed8ae61b8f5664dd484717c79eef7ec66d965940efd828fca0d3c09220a1db"}, - {file = "deptry-0.16.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:738a772b538f51e9a7bb8d5cb9a61cfea8794a79371d171919b01cff0dc895bf"}, - {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56b78f7c860def8000e93f88345a24809f1b91e2f7836ac9a08285cb405e2762"}, - {file = "deptry-0.16.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3e86a04ea87ddece0f68ba204feb950f588205808c8320e6628300f03ff66dc"}, - {file = "deptry-0.16.1-cp38-abi3-win_amd64.whl", hash = "sha256:01b5098739a56c93f3e1e40efec5f20452f22a9a8436a59809d46201fcb94bcf"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e29dc4c1bbb933c9482e8cef85fafe2be7f46aeb90a8a07ba5f2b22af60876f"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8dfab68c247566c87a40f55f405be8549ffe4cea0b9b5384b7ae73a6f1d5cd1"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1228493926b6e59cd2df7cb6016e10c255553cc31db24edcf7fc8d5474b81be6"}, - {file = "deptry-0.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99c3ac60b78ad1b8fb9844c25393e7ebc969cc950601ce3c050f56d196da5a79"}, - {file = "deptry-0.16.1.tar.gz", hash = "sha256:39fb62da4a8f4d17ed282310f7bcaadec55a95a8c471b01e0fcdf5351a7ac323"}, + {file = "deptry-0.19.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3a20ef0dd1c737fb05553d1b9c2fa9f185d0c9d3d881d255334cef401ffdc599"}, + {file = "deptry-0.19.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:2c6b2df353e5113fd2f787c2f7e694657548d388929e988e8644bd178e19fc5c"}, + {file = "deptry-0.19.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a407bab3486e3844f93d702f1a381942873b2a46056c693b5634bbde219bb056"}, + {file = "deptry-0.19.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43f33789b97b47313609e92b62fabf8a71bba0d35a7476806da5d3d152e32345"}, + {file = "deptry-0.19.1-cp38-abi3-win_amd64.whl", hash = "sha256:0bad85a77b31360d0f52383b14783fdae4a201b597c0158fe10e91a779c67079"}, + {file = "deptry-0.19.1-cp38-abi3-win_arm64.whl", hash = "sha256:c59142d9dca8873325692fbb7aa1d2902fde87020dcc8102f75120ba95515172"}, + {file = "deptry-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1abc119f9c8536b8ab1ee2122d4130665f33225d00d8615256ce354eb2c11ba"}, + {file = "deptry-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7344c6cea032b549d86e156aa1e679fb94cd44deb7e93f25cb6d9c0ded5ea06f"}, + {file = "deptry-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff7d8954265c48ea334fdd508339c51d3fba05e2d4a8be47712c69d1c8d35c94"}, + {file = "deptry-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023073247e5dac21254bf7b600ca2e2b71560652d2dfbe11535445ee912ca059"}, + {file = "deptry-0.19.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:af8a0a9c42f8f92dfbc048e724fa89b9131f032f7e245812260560c214395abf"}, + {file = "deptry-0.19.1.tar.gz", hash = "sha256:1c12fea1d2301f42c7035c5636e4b9421457fde256fe7a241245662d20b4c841"}, ] [package.dependencies] @@ -545,80 +546,79 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "docutils" -version = "0.20.1" +version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = true -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] name = "duckdb" -version = "0.10.2" +version = "1.0.0" description = "DuckDB in-process database" optional = false python-versions = ">=3.7.0" files = [ - {file = "duckdb-0.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3891d3ac03e12a3e5c43afa3020fe701f64060f52d25f429a1ed7b5d914368d3"}, - {file = "duckdb-0.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f63877651f1fb940e049dc53038eb763856616319acf4f892b1c3ed074f5ab0"}, - {file = "duckdb-0.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:06e3a36f04f4d98d2c0bbdd63e517cfbe114a795306e26ec855e62e076af5043"}, - {file = "duckdb-0.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf5f95ad5b75c8e65c6508b4df02043dd0b9d97712b9a33236ad77c388ce7861"}, - {file = "duckdb-0.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ff62bc98278c98fecbd6eecec5d698ad41ebd654110feaadbf8ac8bb59b1ecf"}, - {file = "duckdb-0.10.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cceede13fde095c23cf9a53adf7c414c7bfb21b9a7aa6a4836014fdbecbfca70"}, - {file = "duckdb-0.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:acdfff60b7efccd7f731213a9795851256249dfacf80367074b2b2e144f716dd"}, - {file = "duckdb-0.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a5d5655cf0bdaf664a6f332afe465e02b08cef715548a0983bb7aef48da06a6"}, - {file = "duckdb-0.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9d15842876d18763e085648656cccc7660a215d16254906db5c4471be2c7732"}, - {file = "duckdb-0.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c88cdcdc8452c910e4298223e7d9fca291534ff5aa36090aa49c9e6557550b13"}, - {file = "duckdb-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:364cd6f5dc8a1010d144d08c410ba9a74c521336ee5bda84fabc6616216a6d6a"}, - {file = "duckdb-0.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c57c11d1060296f5e9ebfb5bb7e5521e0d77912e8f9ff43c90240c3311e9de9"}, - {file = "duckdb-0.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:186d86b8dda8e1076170eb770bb2bb73ea88ca907d92885c9695d6515207b205"}, - {file = "duckdb-0.10.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f65b62f31c6bff21afc0261cfe28d238b8f34ec78f339546b12f4740c39552a"}, - {file = "duckdb-0.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a860d7466a5c93714cdd94559ce9e1db2ab91914f0941c25e5e93d4ebe36a5fa"}, - {file = "duckdb-0.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:33308190e9c7f05a3a0a2d46008a043effd4eae77011869d7c18fb37acdd9215"}, - {file = "duckdb-0.10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3a8b2f1229b4aecb79cd28ffdb99032b1497f0a805d0da1136a9b6115e1afc70"}, - {file = "duckdb-0.10.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d23a6dea61963733a0f45a0d0bbb1361fb2a47410ed5ff308b4a1f869d4eeb6f"}, - {file = "duckdb-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:20ee0aa27e688aa52a40b434ec41a50431d0b06edeab88edc2feaca18d82c62c"}, - {file = "duckdb-0.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80a6d43d9044f0997a15a92e0c0ff3afd21151a1e572a92f439cc4f56b7090e1"}, - {file = "duckdb-0.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6934758cacd06029a5c9f54556a43bd277a86757e22bf8d0dd11ca15c1813d1c"}, - {file = "duckdb-0.10.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a11e2d68bd79044eea5486b1cddb5b915115f537e5c74eeb94c768ce30f9f4b"}, - {file = "duckdb-0.10.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0bf58385c43b8e448a2fea7e8729054934bf73ea616d1d7ef8184eda07f975e2"}, - {file = "duckdb-0.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:eae75c7014597ded6e7f6dc51e32d48362a31608acd73e9f795748ee94335a54"}, - {file = "duckdb-0.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62e89deff778a7a86f651802b947a3466425f6cce41e9d7d412d39e492932943"}, - {file = "duckdb-0.10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f87e555fd36ec6da316b727a39fb24c53124a797dfa9b451bdea87b2f20a351f"}, - {file = "duckdb-0.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41e8b34b1a944590ebcf82f8cc59d67b084fe99479f048892d60da6c1402c386"}, - {file = "duckdb-0.10.2-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c68c6dde2773774cf2371522a3959ea2716fc2b3a4891d4066f0e426455fe19"}, - {file = "duckdb-0.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ff6a8a0980d0f9398fa461deffa59465dac190d707468478011ea8a5fe1f2c81"}, - {file = "duckdb-0.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:728dd4ff0efda387a424754e5508d4f8c72a272c2d3ccb036a83286f60b46002"}, - {file = "duckdb-0.10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c461d6b4619e80170044a9eb999bbf4097e330d3a4974ced0a7eaeb79c7c39f6"}, - {file = "duckdb-0.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:909351ff72eb3b50b89761251148d8a186594d8a438e12dcf5494794caff6693"}, - {file = "duckdb-0.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d9eeb8393d69abafd355b869669957eb85b89e4df677e420b9ef0693b7aa6cb4"}, - {file = "duckdb-0.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3102bcf5011e8f82ea3c2bde43108774fe5a283a410d292c0843610ea13e2237"}, - {file = "duckdb-0.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d64d443613e5f16caf7d67102733538c90f7715867c1a98597efd3babca068e3"}, - {file = "duckdb-0.10.2-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb31398826d1b7473344e5ee8e0f826370c9752549469ba1327042ace9041f80"}, - {file = "duckdb-0.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d09dcec467cd6127d5cc1fb0ce4efbd77e761882d9d772b0f64fc2f79a2a1cde"}, - {file = "duckdb-0.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:82fab1a24faf7c33d8a7afed08b57ee36e8821a3a68a2f1574cd238ea440bba0"}, - {file = "duckdb-0.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38607e6e6618e8ea28c8d9b67aa9e22cfd6d6d673f2e8ab328bd6e867b697f69"}, - {file = "duckdb-0.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb0c23bc8c09615bff38aebcf8e92e6ae74959c67b3c9e5b00edddc730bf22be"}, - {file = "duckdb-0.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:00576c11c78c83830ab483bad968e07cd9b5f730e7ffaf5aa5fadee5ac4f71e9"}, - {file = "duckdb-0.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077db692cdda50c4684ef87dc2a68507665804caa90e539dbe819116bda722ad"}, - {file = "duckdb-0.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca25984ad9f9a04e46e8359f852668c11569534e3bb8424b80be711303ad2314"}, - {file = "duckdb-0.10.2-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a72cc40982c7b92cf555e574618fc711033b013bf258b611ba18d7654c89d8c"}, - {file = "duckdb-0.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27b9efd6e788eb561535fdc0cbc7c74aca1ff39f748b7cfc27aa49b00e22da1"}, - {file = "duckdb-0.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:4800469489bc262dda61a7f1d40acedf67cf2454874e9d8bbf07920dc2b147e6"}, - {file = "duckdb-0.10.2.tar.gz", hash = "sha256:0f609c9d5f941f1ecde810f010dd9321cd406a552c1df20318a13fa64247f67f"}, + {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4a8ce2d1f9e1c23b9bab3ae4ca7997e9822e21563ff8f646992663f66d050211"}, + {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:19797670f20f430196e48d25d082a264b66150c264c1e8eae8e22c64c2c5f3f5"}, + {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b71c342090fe117b35d866a91ad6bffce61cd6ff3e0cff4003f93fc1506da0d8"}, + {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25dd69f44ad212c35ae2ea736b0e643ea2b70f204b8dff483af1491b0e2a4cec"}, + {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8da5f293ecb4f99daa9a9352c5fd1312a6ab02b464653a0c3a25ab7065c45d4d"}, + {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3207936da9967ddbb60644ec291eb934d5819b08169bc35d08b2dedbe7068c60"}, + {file = "duckdb-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1128d6c9c33e883b1f5df6b57c1eb46b7ab1baf2650912d77ee769aaa05111f9"}, + {file = "duckdb-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:02310d263474d0ac238646677feff47190ffb82544c018b2ff732a4cb462c6ef"}, + {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:75586791ab2702719c284157b65ecefe12d0cca9041da474391896ddd9aa71a4"}, + {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:83bb415fc7994e641344f3489e40430ce083b78963cb1057bf714ac3a58da3ba"}, + {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:bee2e0b415074e84c5a2cefd91f6b5ebeb4283e7196ba4ef65175a7cef298b57"}, + {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa5a4110d2a499312609544ad0be61e85a5cdad90e5b6d75ad16b300bf075b90"}, + {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa389e6a382d4707b5f3d1bc2087895925ebb92b77e9fe3bfb23c9b98372fdc"}, + {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ede6f5277dd851f1a4586b0c78dc93f6c26da45e12b23ee0e88c76519cbdbe0"}, + {file = "duckdb-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b88cdbc0d5c3e3d7545a341784dc6cafd90fc035f17b2f04bf1e870c68456e5"}, + {file = "duckdb-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd1693cdd15375156f7fff4745debc14e5c54928589f67b87fb8eace9880c370"}, + {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c65a7fe8a8ce21b985356ee3ec0c3d3b3b2234e288e64b4cfb03356dbe6e5583"}, + {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:e5a8eda554379b3a43b07bad00968acc14dd3e518c9fbe8f128b484cf95e3d16"}, + {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:a1b6acdd54c4a7b43bd7cb584975a1b2ff88ea1a31607a2b734b17960e7d3088"}, + {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a677bb1b6a8e7cab4a19874249d8144296e6e39dae38fce66a80f26d15e670df"}, + {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:752e9d412b0a2871bf615a2ede54be494c6dc289d076974eefbf3af28129c759"}, + {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3aadb99d098c5e32d00dc09421bc63a47134a6a0de9d7cd6abf21780b678663c"}, + {file = "duckdb-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83b7091d4da3e9301c4f9378833f5ffe934fb1ad2b387b439ee067b2c10c8bb0"}, + {file = "duckdb-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:6a8058d0148b544694cb5ea331db44f6c2a00a7b03776cc4dd1470735c3d5ff7"}, + {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40cb20e5ee19d44bc66ec99969af791702a049079dc5f248c33b1c56af055f4"}, + {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7bce1bc0de9af9f47328e24e6e7e39da30093179b1c031897c042dd94a59c8e"}, + {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8355507f7a04bc0a3666958f4414a58e06141d603e91c0fa5a7c50e49867fb6d"}, + {file = "duckdb-1.0.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:39f1a46f5a45ad2886dc9b02ce5b484f437f90de66c327f86606d9ba4479d475"}, + {file = "duckdb-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d29ba477b27ae41676b62c8fae8d04ee7cbe458127a44f6049888231ca58fa"}, + {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:1bea713c1925918714328da76e79a1f7651b2b503511498ccf5e007a7e67d49e"}, + {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_universal2.whl", hash = "sha256:bfe67f3bcf181edbf6f918b8c963eb060e6aa26697d86590da4edc5707205450"}, + {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:dbc6093a75242f002be1d96a6ace3fdf1d002c813e67baff52112e899de9292f"}, + {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba1881a2b11c507cee18f8fd9ef10100be066fddaa2c20fba1f9a664245cd6d8"}, + {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:445d0bb35087c522705c724a75f9f1c13f1eb017305b694d2686218d653c8142"}, + {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:224553432e84432ffb9684f33206572477049b371ce68cc313a01e214f2fbdda"}, + {file = "duckdb-1.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d3914032e47c4e76636ad986d466b63fdea65e37be8a6dfc484ed3f462c4fde4"}, + {file = "duckdb-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:af9128a2eb7e1bb50cd2c2020d825fb2946fdad0a2558920cd5411d998999334"}, + {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dd2659a5dbc0df0de68f617a605bf12fe4da85ba24f67c08730984a0892087e8"}, + {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_universal2.whl", hash = "sha256:ac5a4afb0bc20725e734e0b2c17e99a274de4801aff0d4e765d276b99dad6d90"}, + {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c5a53bee3668d6e84c0536164589d5127b23d298e4c443d83f55e4150fafe61"}, + {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b980713244d7708b25ee0a73de0c65f0e5521c47a0e907f5e1b933d79d972ef6"}, + {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cbd4f9fe7b7a56eff96c3f4d6778770dd370469ca2212eddbae5dd63749db5"}, + {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed228167c5d49888c5ef36f6f9cbf65011c2daf9dcb53ea8aa7a041ce567b3e4"}, + {file = "duckdb-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46d8395fbcea7231fd5032a250b673cc99352fef349b718a23dea2c0dd2b8dec"}, + {file = "duckdb-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6ad1fc1a4d57e7616944166a5f9417bdbca1ea65c490797e3786e3a42e162d8a"}, + {file = "duckdb-1.0.0.tar.gz", hash = "sha256:a2a059b77bc7d5b76ae9d88e267372deff19c291048d59450c431e166233d453"}, ] [[package]] name = "duckdb-engine" -version = "0.12.0" +version = "0.13.1" description = "SQLAlchemy driver for duckdb" optional = false python-versions = "<4,>=3.8" files = [ - {file = "duckdb_engine-0.12.0-py3-none-any.whl", hash = "sha256:8dadb6065ebfaf83378644d0289ffc9b55ae3ee6ace3b0b732abcfb8f5b03cd7"}, - {file = "duckdb_engine-0.12.0.tar.gz", hash = "sha256:7e2fec35bbc1b794d64942f2102746482dae25dccfb459a10c70b08ccea55583"}, + {file = "duckdb_engine-0.13.1-py3-none-any.whl", hash = "sha256:ddcb173cb7bc088561aadccd2416ddaf83c61430f82983b4fc362fa16000af74"}, + {file = "duckdb_engine-0.13.1.tar.gz", hash = "sha256:883510ff0ce6fdbf0ef595c16c92873fa500bcee9a70ec92ab5f511e8542fc6f"}, ] [package.dependencies] @@ -628,13 +628,13 @@ sqlalchemy = ">=1.3.22" [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -642,13 +642,13 @@ test = ["pytest (>=6)"] [[package]] name = "faker" -version = "25.0.1" +version = "26.3.0" description = "Faker is a Python package that generates fake data for you." optional = true python-versions = ">=3.8" files = [ - {file = "Faker-25.0.1-py3-none-any.whl", hash = "sha256:6737cc6d591cd83421fdc5e494f6e2c1a6e32266214f158b745aa9fa15687c98"}, - {file = "Faker-25.0.1.tar.gz", hash = "sha256:c153505618801f1704807b258a6010ea8cabf91f66f4788939bfdba83b887e76"}, + {file = "Faker-26.3.0-py3-none-any.whl", hash = "sha256:97fe1e7e953dd640ca2cd4dfac4db7c4d2432dd1b7a244a3313517707f3b54e9"}, + {file = "Faker-26.3.0.tar.gz", hash = "sha256:7c10ebdf74aaa0cc4fe6ec6db5a71e8598ec33503524bd4b5f4494785a5670dd"}, ] [package.dependencies] @@ -656,13 +656,13 @@ python-dateutil = ">=2.4" [[package]] name = "fastjsonschema" -version = "2.19.1" +version = "2.20.0" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" files = [ - {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, - {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, + {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"}, + {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"}, ] [package.extras] @@ -670,18 +670,18 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.14.0" +version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -721,19 +721,19 @@ six = ">=1.10,<2.0" [[package]] name = "furo" -version = "2024.5.6" +version = "2024.8.6" description = "A clean customisable Sphinx documentation theme." optional = true python-versions = ">=3.8" files = [ - {file = "furo-2024.5.6-py3-none-any.whl", hash = "sha256:490a00d08c0a37ecc90de03ae9227e8eb5d6f7f750edf9807f398a2bdf2358de"}, - {file = "furo-2024.5.6.tar.gz", hash = "sha256:81f205a6605ebccbb883350432b4831c0196dd3d1bc92f61e1f459045b3d2b0b"}, + {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, + {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" -sphinx = ">=6.0,<8.0" +sphinx = ">=6.0,<9.0" sphinx-basic-ng = ">=1.0.0.beta2" [[package]] @@ -831,22 +831,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.2.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, + {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "importlib-resources" @@ -943,13 +943,13 @@ ply = "*" [[package]] name = "jsonschema" -version = "4.22.0" +version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, - {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, ] [package.dependencies] @@ -962,7 +962,7 @@ rpds-py = ">=0.7.1" [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-specifications" @@ -979,21 +979,6 @@ files = [ importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} referencing = ">=0.31.0" -[[package]] -name = "livereload" -version = "2.6.3" -description = "Python LiveReload is an awesome tool for web developers" -optional = true -python-versions = "*" -files = [ - {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, - {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, -] - -[package.dependencies] -six = "*" -tornado = {version = "*", markers = "python_version > \"2.7\""} - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1089,13 +1074,13 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.4.0" +version = "0.4.1" description = "Collection of plugins for markdown-it-py" optional = true python-versions = ">=3.8" files = [ - {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, - {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, + {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, + {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, ] [package.dependencies] @@ -1119,44 +1104,44 @@ files = [ [[package]] name = "mypy" -version = "1.10.0" +version = "1.11.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -1240,161 +1225,69 @@ files = [ [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.1" description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, + {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, + {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, + {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, + {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, + {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, + {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, + {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, + {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, + {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, ] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "pendulum" -version = "3.0.0" -description = "Python datetimes made easy" -optional = false python-versions = ">=3.8" files = [ - {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, - {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, - {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, - {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, - {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, - {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, - {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, - {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, - {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, - {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, - {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[package.dependencies] -"backports.zoneinfo" = {version = ">=0.2.1", markers = "python_version < \"3.9\""} -importlib-resources = {version = ">=5.9.0", markers = "python_version < \"3.9\""} -python-dateutil = ">=2.6" -tzdata = ">=2020.1" - -[package.extras] -test = ["time-machine (>=2.6.0)"] - [[package]] name = "pkgutil-resolve-name" version = "1.3.10" @@ -1445,52 +1338,55 @@ files = [ [[package]] name = "pyarrow" -version = "16.0.0" +version = "17.0.0" description = "Python library for Apache Arrow" optional = true python-versions = ">=3.8" files = [ - {file = "pyarrow-16.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:22a1fdb1254e5095d629e29cd1ea98ed04b4bbfd8e42cc670a6b639ccc208b60"}, - {file = "pyarrow-16.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:574a00260a4ed9d118a14770edbd440b848fcae5a3024128be9d0274dbcaf858"}, - {file = "pyarrow-16.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0815d0ddb733b8c1b53a05827a91f1b8bde6240f3b20bf9ba5d650eb9b89cdf"}, - {file = "pyarrow-16.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df0080339387b5d30de31e0a149c0c11a827a10c82f0c67d9afae3981d1aabb7"}, - {file = "pyarrow-16.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:edf38cce0bf0dcf726e074159c60516447e4474904c0033f018c1f33d7dac6c5"}, - {file = "pyarrow-16.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91d28f9a40f1264eab2af7905a4d95320ac2f287891e9c8b0035f264fe3c3a4b"}, - {file = "pyarrow-16.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:99af421ee451a78884d7faea23816c429e263bd3618b22d38e7992c9ce2a7ad9"}, - {file = "pyarrow-16.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d22d0941e6c7bafddf5f4c0662e46f2075850f1c044bf1a03150dd9e189427ce"}, - {file = "pyarrow-16.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:266ddb7e823f03733c15adc8b5078db2df6980f9aa93d6bb57ece615df4e0ba7"}, - {file = "pyarrow-16.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cc23090224b6594f5a92d26ad47465af47c1d9c079dd4a0061ae39551889efe"}, - {file = "pyarrow-16.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56850a0afe9ef37249d5387355449c0f94d12ff7994af88f16803a26d38f2016"}, - {file = "pyarrow-16.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:705db70d3e2293c2f6f8e84874b5b775f690465798f66e94bb2c07bab0a6bb55"}, - {file = "pyarrow-16.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5448564754c154997bc09e95a44b81b9e31ae918a86c0fcb35c4aa4922756f55"}, - {file = "pyarrow-16.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:729f7b262aa620c9df8b9967db96c1575e4cfc8c25d078a06968e527b8d6ec05"}, - {file = "pyarrow-16.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:fb8065dbc0d051bf2ae2453af0484d99a43135cadabacf0af588a3be81fbbb9b"}, - {file = "pyarrow-16.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:20ce707d9aa390593ea93218b19d0eadab56390311cb87aad32c9a869b0e958c"}, - {file = "pyarrow-16.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5823275c8addbbb50cd4e6a6839952682a33255b447277e37a6f518d6972f4e1"}, - {file = "pyarrow-16.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab8b9050752b16a8b53fcd9853bf07d8daf19093533e990085168f40c64d978"}, - {file = "pyarrow-16.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:42e56557bc7c5c10d3e42c3b32f6cff649a29d637e8f4e8b311d334cc4326730"}, - {file = "pyarrow-16.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2a7abdee4a4a7cfa239e2e8d721224c4b34ffe69a0ca7981354fe03c1328789b"}, - {file = "pyarrow-16.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ef2f309b68396bcc5a354106741d333494d6a0d3e1951271849787109f0229a6"}, - {file = "pyarrow-16.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ed66e5217b4526fa3585b5e39b0b82f501b88a10d36bd0d2a4d8aa7b5a48e2df"}, - {file = "pyarrow-16.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc8814310486f2a73c661ba8354540f17eef51e1b6dd090b93e3419d3a097b3a"}, - {file = "pyarrow-16.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c2f5e239db7ed43e0ad2baf46a6465f89c824cc703f38ef0fde927d8e0955f7"}, - {file = "pyarrow-16.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f293e92d1db251447cb028ae12f7bc47526e4649c3a9924c8376cab4ad6b98bd"}, - {file = "pyarrow-16.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:dd9334a07b6dc21afe0857aa31842365a62eca664e415a3f9536e3a8bb832c07"}, - {file = "pyarrow-16.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d91073d1e2fef2c121154680e2ba7e35ecf8d4969cc0af1fa6f14a8675858159"}, - {file = "pyarrow-16.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:71d52561cd7aefd22cf52538f262850b0cc9e4ec50af2aaa601da3a16ef48877"}, - {file = "pyarrow-16.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b93c9a50b965ee0bf4fef65e53b758a7e8dcc0c2d86cebcc037aaaf1b306ecc0"}, - {file = "pyarrow-16.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d831690844706e374c455fba2fb8cfcb7b797bfe53ceda4b54334316e1ac4fa4"}, - {file = "pyarrow-16.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35692ce8ad0b8c666aa60f83950957096d92f2a9d8d7deda93fb835e6053307e"}, - {file = "pyarrow-16.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dd3151d098e56f16a8389c1247137f9e4c22720b01c6f3aa6dec29a99b74d80"}, - {file = "pyarrow-16.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:bd40467bdb3cbaf2044ed7a6f7f251c8f941c8b31275aaaf88e746c4f3ca4a7a"}, - {file = "pyarrow-16.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:00a1dcb22ad4ceb8af87f7bd30cc3354788776c417f493089e0a0af981bc8d80"}, - {file = "pyarrow-16.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:fda9a7cebd1b1d46c97b511f60f73a5b766a6de4c5236f144f41a5d5afec1f35"}, - {file = "pyarrow-16.0.0.tar.gz", hash = "sha256:59bb1f1edbbf4114c72415f039f1359f1a57d166a331c3229788ccbfbb31689a"}, + {file = "pyarrow-17.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a5c8b238d47e48812ee577ee20c9a2779e6a5904f1708ae240f53ecbee7c9f07"}, + {file = "pyarrow-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db023dc4c6cae1015de9e198d41250688383c3f9af8f565370ab2b4cb5f62655"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da1e060b3876faa11cee287839f9cc7cdc00649f475714b8680a05fd9071d545"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c06d4624c0ad6674364bb46ef38c3132768139ddec1c56582dbac54f2663e2"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:fa3c246cc58cb5a4a5cb407a18f193354ea47dd0648194e6265bd24177982fe8"}, + {file = "pyarrow-17.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f7ae2de664e0b158d1607699a16a488de3d008ba99b3a7aa5de1cbc13574d047"}, + {file = "pyarrow-17.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5984f416552eea15fd9cee03da53542bf4cddaef5afecefb9aa8d1010c335087"}, + {file = "pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977"}, + {file = "pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420"}, + {file = "pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4"}, + {file = "pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03"}, + {file = "pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22"}, + {file = "pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a"}, + {file = "pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b"}, + {file = "pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7"}, + {file = "pyarrow-17.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:af5ff82a04b2171415f1410cff7ebb79861afc5dae50be73ce06d6e870615204"}, + {file = "pyarrow-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:edca18eaca89cd6382dfbcff3dd2d87633433043650c07375d095cd3517561d8"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c7916bff914ac5d4a8fe25b7a25e432ff921e72f6f2b7547d1e325c1ad9d155"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f553ca691b9e94b202ff741bdd40f6ccb70cdd5fbf65c187af132f1317de6145"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0cdb0e627c86c373205a2f94a510ac4376fdc523f8bb36beab2e7f204416163c"}, + {file = "pyarrow-17.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d7d192305d9d8bc9082d10f361fc70a73590a4c65cf31c3e6926cd72b76bc35c"}, + {file = "pyarrow-17.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:02dae06ce212d8b3244dd3e7d12d9c4d3046945a5933d28026598e9dbbda1fca"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:13d7a460b412f31e4c0efa1148e1d29bdf18ad1411eb6757d38f8fbdcc8645fb"}, + {file = "pyarrow-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b564a51fbccfab5a04a80453e5ac6c9954a9c5ef2890d1bcf63741909c3f8df"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32503827abbc5aadedfa235f5ece8c4f8f8b0a3cf01066bc8d29de7539532687"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a155acc7f154b9ffcc85497509bcd0d43efb80d6f733b0dc3bb14e281f131c8b"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:dec8d129254d0188a49f8a1fc99e0560dc1b85f60af729f47de4046015f9b0a5"}, + {file = "pyarrow-17.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:a48ddf5c3c6a6c505904545c25a4ae13646ae1f8ba703c4df4a1bfe4f4006bda"}, + {file = "pyarrow-17.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:42bf93249a083aca230ba7e2786c5f673507fa97bbd9725a1e2754715151a204"}, + {file = "pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28"}, ] [package.dependencies] numpy = ">=1.16.6" +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + [[package]] name = "pycparser" version = "2.22" @@ -1518,30 +1414,30 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.8.0" +version = "2.9.0" description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" +optional = true +python-versions = ">=3.8" files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pytest" -version = "8.2.0" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -1549,7 +1445,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.5,<2.0" +pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] @@ -1597,20 +1493,6 @@ compat = ["pytest-benchmark (>=4.0.0,<4.1.0)", "pytest-xdist (>=2.0.0,<2.1.0)"] lint = ["mypy (>=1.3.0,<1.4.0)", "ruff (>=0.3.3,<0.4.0)"] test = ["pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)"] -[[package]] -name = "pytest-durations" -version = "1.2.0" -description = "Pytest plugin reporting fixtures and test functions execution time." -optional = true -python-versions = ">=3.6.2" -files = [ - {file = "pytest-durations-1.2.0.tar.gz", hash = "sha256:75793f7c2c393a947de4a92cc205e8dcb3d7fcde492628926cca97eb8e87077d"}, - {file = "pytest_durations-1.2.0-py3-none-any.whl", hash = "sha256:210c649d989fdf8e864b7f614966ca2c8be5b58a5224d60089a43618c146d7fb"}, -] - -[package.dependencies] -pytest = ">=4.6" - [[package]] name = "pytest-snapshot" version = "0.9.0" @@ -1666,62 +1548,64 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -1741,13 +1625,13 @@ rpds-py = ">=0.7.0" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1793,121 +1677,125 @@ six = "*" [[package]] name = "rpds-py" -version = "0.18.1" +version = "0.20.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53"}, - {file = "rpds_py-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0"}, - {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da"}, - {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1"}, - {file = "rpds_py-0.18.1-cp310-none-win32.whl", hash = "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333"}, - {file = "rpds_py-0.18.1-cp310-none-win_amd64.whl", hash = "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a"}, - {file = "rpds_py-0.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8"}, - {file = "rpds_py-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100"}, - {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e"}, - {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88"}, - {file = "rpds_py-0.18.1-cp311-none-win32.whl", hash = "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb"}, - {file = "rpds_py-0.18.1-cp311-none-win_amd64.whl", hash = "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2"}, - {file = "rpds_py-0.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3"}, - {file = "rpds_py-0.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8"}, - {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac"}, - {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a"}, - {file = "rpds_py-0.18.1-cp312-none-win32.whl", hash = "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6"}, - {file = "rpds_py-0.18.1-cp312-none-win_amd64.whl", hash = "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72"}, - {file = "rpds_py-0.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74"}, - {file = "rpds_py-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5"}, - {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e"}, - {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc"}, - {file = "rpds_py-0.18.1-cp38-none-win32.whl", hash = "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9"}, - {file = "rpds_py-0.18.1-cp38-none-win_amd64.whl", hash = "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2"}, - {file = "rpds_py-0.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93"}, - {file = "rpds_py-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab"}, - {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b"}, - {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26"}, - {file = "rpds_py-0.18.1-cp39-none-win32.whl", hash = "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360"}, - {file = "rpds_py-0.18.1-cp39-none-win_amd64.whl", hash = "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c"}, - {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909"}, - {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49"}, - {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e"}, - {file = "rpds_py-0.18.1.tar.gz", hash = "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, ] [[package]] name = "s3transfer" -version = "0.10.1" +version = "0.10.2" description = "An Amazon S3 Transfer Manager" optional = true -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, - {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, + {file = "s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"}, + {file = "s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6"}, ] [package.dependencies] @@ -1918,19 +1806,19 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "setuptools" -version = "69.5.1" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simpleeval" @@ -2085,57 +1973,39 @@ files = [ [[package]] name = "sphinx" -version = "7.1.2" +version = "7.4.7" description = "Python documentation generator" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, - {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" +importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinx-autobuild" -version = "2021.3.14" -description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -optional = true -python-versions = ">=3.6" -files = [ - {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, - {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, -] - -[package.dependencies] -colorama = "*" -livereload = "*" -sphinx = "*" - -[package.extras] -test = ["pytest", "pytest-cov"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-basic-ng" @@ -2192,13 +2062,13 @@ test = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "sphinx-notfound-page" -version = "1.0.0" +version = "1.0.4" description = "Sphinx extension to build a 404 page with absolute URLs" optional = true python-versions = ">=3.8" files = [ - {file = "sphinx_notfound_page-1.0.0-py3-none-any.whl", hash = "sha256:40a5741a6b07245a08fe55dbbd603ad6719e191b1419ab2e5337c706ebd16554"}, - {file = "sphinx_notfound_page-1.0.0.tar.gz", hash = "sha256:14cd388956de5cdf8710ab4ff31776ef8d85759c4f46014ee30f368e83bd3a3b"}, + {file = "sphinx_notfound_page-1.0.4-py3-none-any.whl", hash = "sha256:f7c26ae0df3cf3d6f38f56b068762e6203d0ebb7e1c804de1059598d7dd8b9d8"}, + {file = "sphinx_notfound_page-1.0.4.tar.gz", hash = "sha256:2a52f49cd367b5c4e64072de1591cc367714098500abf4ecb9a3ecb4fec25aae"}, ] [package.dependencies] @@ -2210,61 +2080,64 @@ test = ["tox"] [[package]] name = "sphinx-reredirects" -version = "0.1.3" +version = "0.1.5" description = "Handles redirects for moved pages in Sphinx documentation projects" optional = true python-versions = ">=3.5" files = [ - {file = "sphinx_reredirects-0.1.3-py3-none-any.whl", hash = "sha256:02c53437c467cf9ed89863eff3addedc01d129624b2f03ab6302518fb77a2c89"}, - {file = "sphinx_reredirects-0.1.3.tar.gz", hash = "sha256:56e222d285f76c944fd370f36ad3a1a66103a88b552e97d3d24a622bb9465de8"}, + {file = "sphinx_reredirects-0.1.5-py3-none-any.whl", hash = "sha256:444ae1438fba4418242ca76d6a6de3eaee82aaf0d8f2b0cac71a15d32ce6eba2"}, + {file = "sphinx_reredirects-0.1.5.tar.gz", hash = "sha256:cfa753b441020a22708ce8eb17d4fd553a28fc87a609330092917ada2a6da0d8"}, ] [package.dependencies] -sphinx = "*" +sphinx = ">=7.1" [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.4" +version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = true -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.1" +version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -2283,94 +2156,96 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = true -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = true -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.30" +version = "2.0.32" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, - {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, - {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, - {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"}, - {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, - {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, - {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, - {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, - {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d"}, + {file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28"}, + {file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d"}, + {file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da"}, + {file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65"}, + {file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78"}, + {file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84"}, + {file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202"}, + {file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -2400,67 +2275,78 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "time-machine" -version = "2.14.1" +version = "2.15.0" description = "Travel through time in your tests." optional = false python-versions = ">=3.8" files = [ - {file = "time-machine-2.14.1.tar.gz", hash = "sha256:57dc7efc1dde4331902d1bdefd34e8ee890a5c28533157e3b14a429c86b39533"}, - {file = "time_machine-2.14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:528d588d1e8ba83e45319a74acab4be0569eb141113fdf50368045d0a7d79cee"}, - {file = "time_machine-2.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06e913d570d7ee3e199e3316f10f10c8046287049141b0a101197712b4eac106"}, - {file = "time_machine-2.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbbba954e9a409e7d66d60df2b6b8daeb897f8338f909a92d9d20e431ec70d1"}, - {file = "time_machine-2.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72a153b085b4aee652d6b3bf9019ca897f1597ba9869b640b06f28736b267182"}, - {file = "time_machine-2.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b94274abe24b6a90d8a5c042167a9a7af2d3438b42ac8eb5ede50fbc73c08db"}, - {file = "time_machine-2.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:364353858708628655bf9fa4c2825febd679c729d9e1dd424ff86845828bac05"}, - {file = "time_machine-2.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b951b6f4b8a752ab8c441df422e21954a721a0a5276aa3814ce8cf7205aeb6da"}, - {file = "time_machine-2.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:be215eb63d74a3d580f7924bb4209c783fabcfb3253073f4dcb3424d57d0f518"}, - {file = "time_machine-2.14.1-cp310-cp310-win32.whl", hash = "sha256:0e120f95c17bf8e0c097fd8863a8eb24054f9b17d9b17c465694be50f8348a3a"}, - {file = "time_machine-2.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fb467d6c9e9ab615c8cf22d751d34296dacf801be323a57adeb4ff345cf72473"}, - {file = "time_machine-2.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:19db257117739b2dda1d57e149bb715a593313899b3902a7e6d752c5f1d22542"}, - {file = "time_machine-2.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:442d42f1b0ef006f03a5a34905829a1d3ac569a5bcda64d29706e6dc60832f94"}, - {file = "time_machine-2.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0312b47f220e46f1bbfaded7fc1469882d9c2a27c6daf44e119aea7006b595cc"}, - {file = "time_machine-2.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a39dba3033d9c28347d2db16bcb16041bbf4e9032e2b70023686b6f95deac9d"}, - {file = "time_machine-2.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e030d2051bb515251d7f6edd9bbcf79b2b47811e2c402aba9c126af713843d26"}, - {file = "time_machine-2.14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:993ab140eb5678d1ee7f1197f08e4499dc8ea883ad6b8858737de70d509ec5b5"}, - {file = "time_machine-2.14.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:90725f936ad8b123149bc82a46394dd7057e63157ee11ba878164053fa5bd8ad"}, - {file = "time_machine-2.14.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:59a02c3d3b3b29e2dc3a708e775c5d6b951b0024c4013fed883f0d2205305c9e"}, - {file = "time_machine-2.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f00f67d532da82538c4dfbbddc587e70c82664f168c11e1c2915d0c85ec2fc8"}, - {file = "time_machine-2.14.1-cp311-cp311-win32.whl", hash = "sha256:27f735cba4c6352ad7bc53ce2d86b715379261a634e690b79fac329081e26fb6"}, - {file = "time_machine-2.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee68597bd3fa5ab94633c8a9d3ebd4032091559610e078381818a732910002bc"}, - {file = "time_machine-2.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ced9de5eff1fb37efb12984ab7b63f31f0aeadeedec4be6d0404ec4fa91f2e7"}, - {file = "time_machine-2.14.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:30a4a18357fa6cf089eeefcb37e9549b42523aebb5933894770a8919e6c398e1"}, - {file = "time_machine-2.14.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d45bd60bea85869615b117667f10a821e3b0d3603c47bfd105b45d1f67156fc8"}, - {file = "time_machine-2.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:39de6d37a14ff8882d4f1cbd50c53268b54e1cf4ef9be2bfe590d10a51ccd314"}, - {file = "time_machine-2.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fd7d188b4f9d358c6bd477daf93b460d9b244a4c296ddd065945f2b6193c2bd"}, - {file = "time_machine-2.14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99e6f013e67c4f74a9d8f57e34173b2047f2ad48f764e44c38f3ee5344a38c01"}, - {file = "time_machine-2.14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a927d87501da8b053a27e80f5d0e1e58fbde4b50d70df2d3853ed67e89a731cf"}, - {file = "time_machine-2.14.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77a616561dd4c7c442e9eee8cbb915750496e9a5a7fca6bcb11a9860226d2d0"}, - {file = "time_machine-2.14.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e7fa70a6bdca40cc4a8386fd85bc1bae0a23ab11e49604ef853ab3ce92be127f"}, - {file = "time_machine-2.14.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d63ef00d389fa6d2c76c863af580b3e4a8f0ccc6a9aea8e64590588e37f13c00"}, - {file = "time_machine-2.14.1-cp312-cp312-win32.whl", hash = "sha256:6706eb06487354a5e219cacea709fb3ec44dec3842c6218237d5069fa5f1ad64"}, - {file = "time_machine-2.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:36aa4f17adcd73a6064bf4991a29126cac93521f0690805edb91db837c4e1453"}, - {file = "time_machine-2.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:edea570f3835a036e8860bb8d6eb8d08473c59313db86e36e3b207f796fd7b14"}, - {file = "time_machine-2.14.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e80408e6b6670e9ce33f94b1cc6b72b1a9b646f5e19f586908129871f74b40"}, - {file = "time_machine-2.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c69c0cb498c86ef843cd15964714e76465cc25d64464da57d5d1318f499de099"}, - {file = "time_machine-2.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc48d3934109b0bdbbdc5e9ce577213f7148a92fed378420ee13453503fe4db9"}, - {file = "time_machine-2.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7161cea2ff3244cc6075e365fab89000df70ead63a3da9d473983d580558d2de"}, - {file = "time_machine-2.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39fceeb131e6c07b386de042ce1016be771576e9516124b78e75cbab94ae5041"}, - {file = "time_machine-2.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fe508a6c43fb72fa4f66b50b14684cf58d3db95fed617177ec197a7a90427bae"}, - {file = "time_machine-2.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5f3d5c21884aee10e13b00ef45fab893a43db9d59ec27271573528bd359b0ef5"}, - {file = "time_machine-2.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a75e24e59f58059bbbc50e7f97aa6d126bbc2f603a8a5cd1e884beffcf130d8f"}, - {file = "time_machine-2.14.1-cp38-cp38-win32.whl", hash = "sha256:b0f8ba70fbb71d7fbc6d6adb90bed72a83db15b3318c7af0060467539b2f1b63"}, - {file = "time_machine-2.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:15cf3623a4ba2bb4fce4529295570acd5f6c6b44bcbfd1b8d0756ce56c38fe82"}, - {file = "time_machine-2.14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bb3a2518c52aa944989b541e5297b833388eb3fe72d91eb875b21fe771597b04"}, - {file = "time_machine-2.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:416d94eab7723c7d8a37fe6b3b1882046fdbf3c31b9abec3cac87cf35dbb8230"}, - {file = "time_machine-2.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adfbfa796dd96383400b44681eacc5ab06d3cbfad39c30878e5ead0bfdca808a"}, - {file = "time_machine-2.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31e6e9bff89b7c6e4cbc169ba1d00d6c107b3abc43173b2799352b6995cf7cb2"}, - {file = "time_machine-2.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107caed387438d689180b692e8d84aa1ebe8918790df83dc5e2146e60e5e0859"}, - {file = "time_machine-2.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cab4abf4d1490a7da35db5a321ff8a4d4a2195f4832a792c75b626ffc4a5584c"}, - {file = "time_machine-2.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd8645b820f7895fdafbc4412d1ce376956e36ad4fd05a43269aa06c3132afc3"}, - {file = "time_machine-2.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd26039a9ffea2d5ee1309f2ec9b656d4925371c65563822d52e4037a4186eca"}, - {file = "time_machine-2.14.1-cp39-cp39-win32.whl", hash = "sha256:5e19b19d20bfbff8c97949e06e150998cf9d0a676e1641fb90597e59a9d7d5e2"}, - {file = "time_machine-2.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:f5d371a5218318121a6b44c21438258b6408b8bfe7ccccb754cf8eb880505576"}, - {file = "time_machine-2.14.1-cp39-cp39-win_arm64.whl", hash = "sha256:2c774f4b603a36ca2611327c57aa8ce0d5042298da008238ee5234b31ce7b22c"}, + {file = "time_machine-2.15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:892d016789b59950989b2db188dcd46cf16d34e8daf2343e33b679b0c5fd1001"}, + {file = "time_machine-2.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4428bdae507996aa3fdeb4727bca09e26306fa64a502e7335207252684516cbf"}, + {file = "time_machine-2.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0302568338c8bd333ed0698231dbb781b70ead1a5579b4ac734b9bf88313229f"}, + {file = "time_machine-2.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18fc4740073e67071472c48355775ec6d1b93af5c675524b7de2474e0dcd8741"}, + {file = "time_machine-2.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:768d33b484a35da93731cc99bdc926b539240a78673216cdc6306833d9072350"}, + {file = "time_machine-2.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73a8c8160d2a170dadcad5b82fb5ee53236a19cec0996651cf4d21da0a2574d5"}, + {file = "time_machine-2.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09fd839a321a92aa8183206c383b9725eaf4e0a28a70e4cb87db292b352eeefb"}, + {file = "time_machine-2.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:838a6d117739f1ae6ecc45ec630fa694f41a85c0d07b1f3b1db2a6cc52c1808b"}, + {file = "time_machine-2.15.0-cp310-cp310-win32.whl", hash = "sha256:d24d2ec74923b49bce7618e3e7762baa6be74e624d9829d5632321de102bf386"}, + {file = "time_machine-2.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:95c8e7036cf442480d0bf6f5fde371e1eb6dbbf5391d7bdb8db73bd8a732b538"}, + {file = "time_machine-2.15.0-cp310-cp310-win_arm64.whl", hash = "sha256:660810cd27a8a94cb5e845e8f28a95e70b01ff0c45466d394c4a0cba5a0ae279"}, + {file = "time_machine-2.15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:674097dd54a0bbd555e7927092c74428c4c07268ad52bca38cfccc3214707e50"}, + {file = "time_machine-2.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e83fd6112808d1d14d1a57397c6fa3bd71bb2f3b8800036e12366e3680819b9"}, + {file = "time_machine-2.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b095a1de40ca1afaeae8df3f45e26b645094a1912e6e6871e725fcf06ecdb74a"}, + {file = "time_machine-2.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4601fe7a6b74c6fd9207e614d9db2a20dd4befd4d314677a0feac13a67189707"}, + {file = "time_machine-2.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245ef73f9927b7d4909d554a6a0284dbc5dee9730adea599e430b37c9e9fa203"}, + {file = "time_machine-2.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:704abc7f3403584cca9c01c5809812e0bd70632ea4251389fae4f45e11aad94f"}, + {file = "time_machine-2.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6425001e50a0c82108caed438233066cea04d42a8fc9c49bfcf081a5b96e5b4e"}, + {file = "time_machine-2.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d4073b754f90b19f28d036ec5143d3fca3a75e4d4241d78790a6178b00bb373"}, + {file = "time_machine-2.15.0-cp311-cp311-win32.whl", hash = "sha256:8817b0f7d7830215261b18db83c9c3ef1da6bb64da5c292d7c70b9a46e5a6745"}, + {file = "time_machine-2.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:ddad27a62df2ea47b7b483009fbfcf167a71d702cbd8e2eefd9ddc1c93146658"}, + {file = "time_machine-2.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f021aa2dbd8fbfe54d3fa2258518129108b7496922b3bcff2cf5991078eec67"}, + {file = "time_machine-2.15.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a22f47c34ee1fcf7d93a8c5c93135499aac879d9d5d8f820bd28571a30fdabcd"}, + {file = "time_machine-2.15.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b684f8ecdeacd6baabc17b15ac1b054ca62029193e6c5367ef00b3516671de80"}, + {file = "time_machine-2.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f7add997684bc6141e1c80f6ba0c38ffe316ba277a4074e61b1b7b4f5a172bf"}, + {file = "time_machine-2.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31af56399bf7c9ef76a3f7b6d9471dffa8f06ee373c194a374b69523f9061de9"}, + {file = "time_machine-2.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b94cba3edfc54bcb3ab5be616a2f50fa48be438e5af970824efdf882d1bc31"}, + {file = "time_machine-2.15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3862dda89bdb05f9d521b08fdcb24b19a7dd9f559ae324f4301ba7a07b6eea64"}, + {file = "time_machine-2.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1790481a6b9ce38888f22ce30710244067898c3ac4805a0e061e381f3db3506"}, + {file = "time_machine-2.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a731c03bc00552ee6cc685a59616d36003124e7e04c6ddf65c2c47f1c3d85480"}, + {file = "time_machine-2.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e6776840aea3ff5ab6924b50117957da62db51b109b3b491c0d5817a804b1a8e"}, + {file = "time_machine-2.15.0-cp312-cp312-win32.whl", hash = "sha256:9479530e3fce65f6149058071fa4df8150025f15b43b103445f619842981a87c"}, + {file = "time_machine-2.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5f3ab4185c1f72010846ca9fccb08349e23a2b52982a18d9870e848ce9f1c86"}, + {file = "time_machine-2.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:c0473dfa8f17c6a9a250b2bd6a5b62af3aa7d22518f701649115f1085d5e35ab"}, + {file = "time_machine-2.15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f50f10058b884d45cd8a50423bf561b1f9f9df7058abeb8b318700c8bcf4bb54"}, + {file = "time_machine-2.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:df6f618b98f0848fd8d07039541e10f23db679d8283f8719e870a98e1ef8e639"}, + {file = "time_machine-2.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52468a0784544eba708c0ae6bc5e8c5dcfd685495a60f7f74028662c984bd9cd"}, + {file = "time_machine-2.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08800c28160f4d32ca510128b4e201a43c813e7a2dd53178fa79ebe050eba13"}, + {file = "time_machine-2.15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d395211736d9844537a530287a7c64b9fda1d353e899a0e1723986a0859154"}, + {file = "time_machine-2.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b177d334a35bf2ce103bfe4e0e416e4ee824dd33386ea73fa7491c17cc61897"}, + {file = "time_machine-2.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9a6a9342fae113b12aab42c790880c549d9ba695b8deff27ee08096eedd67569"}, + {file = "time_machine-2.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bcbb25029ee8756f10c6473cea5ef21707a1d9a8752cdf29fad3a5f34aa4a313"}, + {file = "time_machine-2.15.0-cp313-cp313-win32.whl", hash = "sha256:29b988b1f09f2a083b12b6b054787b799ae91ee15bb0e9de3e48f880e4d68674"}, + {file = "time_machine-2.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:d828721dcbcb94b904a6b25df67c2513ecd24cd9e36694f38b9f0fa71c7c6103"}, + {file = "time_machine-2.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:008bd668d933b1a029c81805bcdc0132390c2545b103cf8e6709e3adbc37989d"}, + {file = "time_machine-2.15.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e99689f6c6b9ca6e2fc7a75d140e38c5a7985dab61fe1f4e506268f7e9844e05"}, + {file = "time_machine-2.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:671e88a6209a1cf415dc0f8c67d2b2d3b55b436cc63801a518f9800ebd752959"}, + {file = "time_machine-2.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b2d28daf4cabc698aafb12135525d87dc1f2f893cbd29a8a6fe0d8d36d1342c"}, + {file = "time_machine-2.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cd9f057457d12604be18b623bcd5ae7d0b917ad66cb510ee1135d5f123666e2"}, + {file = "time_machine-2.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97dc6793e512a62ba9eab250134a2e67372c16ae9948e73d27c2ef355356e2e1"}, + {file = "time_machine-2.15.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0630a32e9ebcf2fac3704365b31e271fef6eabd6fedfa404cd8dbd244f7fc84d"}, + {file = "time_machine-2.15.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:617c9a92d8d8f60d5ef39e76596620503752a09f834a218e5b83be352fdd6c91"}, + {file = "time_machine-2.15.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3f7eadd820e792de33a9ec91f8178a2b9088e4e8b9a166953419ddc4ec5f7cfe"}, + {file = "time_machine-2.15.0-cp38-cp38-win32.whl", hash = "sha256:b7b647684eb2e1fd1e5e6b101249d5fe9d6117c117b5e336ad8dd75af48d2d1f"}, + {file = "time_machine-2.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b48abd7745caec1a78a16a048966cde14ff6ccb04d471a7201532648d3f77d14"}, + {file = "time_machine-2.15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c2b1c91b437133c672e374857eccb1dd2c2d9f8477ae3b35138382d5ef19846"}, + {file = "time_machine-2.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:79bf1ef6850182e09d86e61fa31717da56014a3b2234afb025fca1f2a43ac07b"}, + {file = "time_machine-2.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:658ea8477fa020f08435fb7277635eb0b50cd5206b9d4cbe10e9a5466b01f855"}, + {file = "time_machine-2.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c947135750d20f35acac290c34f1acf5771fc166a3fbc0e3816a97c756aaa5f5"}, + {file = "time_machine-2.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dee3a0dd1866988c49a5d00564404db9bcdf49ca92f9c4e8b6c99609d64e698"}, + {file = "time_machine-2.15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c596920d6017702a36e3a43fd8110a84e87d6229f30b84bd5640cbae9b5145da"}, + {file = "time_machine-2.15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:014589d0edd4aa14f8d63985745565e8cbbe48461d6c004a96000b47f6b44e78"}, + {file = "time_machine-2.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5ff655716cd13a242eef8cf5d368074e8b396ff86508a5933e7cff4f2b3eb3c2"}, + {file = "time_machine-2.15.0-cp39-cp39-win32.whl", hash = "sha256:1168eebd7af7e6e3e2fd378c16ca917b97dd81c89a1f1f9e1daa985c81699d90"}, + {file = "time_machine-2.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:c344eb09fcfbf71e5b5847d4f188fec98e1c3a976125ef571eac5f1c39e7a5e5"}, + {file = "time_machine-2.15.0-cp39-cp39-win_arm64.whl", hash = "sha256:899f1a856b3bebb82b6cbc3c0014834b583b83f246b28e462a031ec1b766130b"}, + {file = "time_machine-2.15.0.tar.gz", hash = "sha256:ebd2e63baa117ded04b978813fcd1279d3fc6be2149c9cac75c716b6f1db774c"}, ] [package.dependencies] @@ -2477,51 +2363,20 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "tornado" -version = "6.4" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = true -python-versions = ">= 3.8" -files = [ - {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, - {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, - {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, - {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, - {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, - {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, - {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, -] - [[package]] name = "types-jsonschema" -version = "4.22.0.20240501" +version = "4.23.0.20240712" description = "Typing stubs for jsonschema" optional = false python-versions = ">=3.8" files = [ - {file = "types-jsonschema-4.22.0.20240501.tar.gz", hash = "sha256:51c4ec05640909206551c8f57e630be570c4e0d86abda75d9d947521dffef6db"}, - {file = "types_jsonschema-4.22.0.20240501-py3-none-any.whl", hash = "sha256:5cacc80cffef3cbbdd928954feb0f3d218d062b47333ca50841c27039efae29d"}, + {file = "types-jsonschema-4.23.0.20240712.tar.gz", hash = "sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217"}, + {file = "types_jsonschema-4.23.0.20240712-py3-none-any.whl", hash = "sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4"}, ] [package.dependencies] referencing = "*" -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20240316" -description = "Typing stubs for python-dateutil" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, -] - [[package]] name = "types-pytz" version = "2024.1.0.20240417" @@ -2535,13 +2390,13 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20240311" +version = "6.0.12.20240808" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.8" files = [ - {file = "types-PyYAML-6.0.12.20240311.tar.gz", hash = "sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342"}, - {file = "types_PyYAML-6.0.12.20240311-py3-none-any.whl", hash = "sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6"}, + {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"}, + {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"}, ] [[package]] @@ -2560,13 +2415,13 @@ types-urllib3 = "*" [[package]] name = "types-simplejson" -version = "3.19.0.20240310" +version = "3.19.0.20240801" description = "Typing stubs for simplejson" optional = false python-versions = ">=3.8" files = [ - {file = "types-simplejson-3.19.0.20240310.tar.gz", hash = "sha256:2831366f70d5d55832c3d978dff7c989575d55fb38621d086dbfee7a30c2b500"}, - {file = "types_simplejson-3.19.0.20240310-py3-none-any.whl", hash = "sha256:6f06d8e50112bc931863a40e2cf463a99223d55fe07f275826fa969962bb56a4"}, + {file = "types-simplejson-3.19.0.20240801.tar.gz", hash = "sha256:ef90cc81dd915f26c452fa2b5e0cbd3a36af81074ae63878fcf8a477e7594e4d"}, + {file = "types_simplejson-3.19.0.20240801-py3-none-any.whl", hash = "sha256:37f1b33c8626d7f072ea87737629310674845d54d187f12387fe33d31c938288"}, ] [[package]] @@ -2582,35 +2437,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" -version = "1.26.18" +version = "1.26.19" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, + {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, + {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, ] [package.extras] @@ -2620,13 +2464,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "xdoctest" -version = "1.1.3" +version = "1.1.6" description = "A rewrite of the builtin doctest module" optional = false python-versions = ">=3.6" files = [ - {file = "xdoctest-1.1.3-py3-none-any.whl", hash = "sha256:9360535bd1a971ffc216d9613898cedceb81d0fd024587cc3c03c74d14c00a31"}, - {file = "xdoctest-1.1.3.tar.gz", hash = "sha256:84e76a42a11a5926ff66d9d84c616bc101821099672550481ad96549cbdd02ae"}, + {file = "xdoctest-1.1.6-py3-none-any.whl", hash = "sha256:a6f673df8c82b8fe0adc536f14c523464f25c6d2b733ed78888b8f8d6c46012e"}, + {file = "xdoctest-1.1.6.tar.gz", hash = "sha256:00ec7bde36addbedf5d1db0db57b6b669a7a4b29ad2d16480950556644f02109"}, ] [package.extras] @@ -2643,28 +2487,28 @@ tests-strict = ["pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pyt [[package]] name = "zipp" -version = "3.18.1" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] -docs = ["furo", "myst-parser", "pytest", "sphinx", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects"] +docs = ["furo", "myst-parser", "pytest", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects"] faker = ["faker"] -jwt = [] +jwt = ["PyJWT", "cryptography"] parquet = ["numpy", "numpy", "pyarrow"] s3 = ["fs-s3fs"] -testing = ["pytest", "pytest-durations"] +testing = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "6ce3b4821061e0e086e451a5d652882a18f669bb5685ad8029188f54b17243fd" +content-hash = "8d0665d7e5397609e616976d470dca9863a925a12f513c0b78439f055aeb664a" diff --git a/pyproject.toml b/pyproject.toml index 221ca67e2..68faa36ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,18 +42,14 @@ python = ">=3.8" backoff = { version = ">=2.0.0", python = "<4" } backports-datetime-fromisoformat = { version = ">=2.0.1", python = "<3.11" } click = "~=8.0" -cryptography = ">=3.4.6" fs = ">=2.4.16" -importlib-metadata = {version = "<8.0.0", python = "<3.12"} -importlib-resources = {version = ">=5.12.0,!=6.2.0,!=6.3.0,!=6.3.1", python = "<3.9"} +importlib-metadata = {version = "<9.0.0", python = "<3.12"} +importlib-resources = {version = ">=5.12.0,!=6.2.0,!=6.3.0,!=6.3.1", python = "<3.10"} inflection = ">=0.5.1" joblib = ">=1.3.0" jsonpath-ng = ">=1.5.3" jsonschema = ">=4.16.0" packaging = ">=23.1" -pendulum = ">=2.1.0,<4" -PyJWT = "~=2.4" -python-dateutil = ">=2.8.2" python-dotenv = ">=0.20" PyYAML = ">=6.0" referencing = ">=0.30.0" @@ -67,14 +63,13 @@ urllib3 = ">=1.26,<2" # Sphinx dependencies installed as optional 'docs' extras # https://github.com/readthedocs/readthedocs.org/issues/4912#issuecomment-664002569 -sphinx = {version = ">=4.5", optional = true} -furo = {version = ">=2022.12.7", optional = true} -sphinx-copybutton = {version = ">=0.3.1", optional = true} -myst-parser = {version = ">=1", optional = true} -sphinx-autobuild = {version = ">=2021.3.14", optional = true} -sphinx-inline-tabs = {version = ">=2023.4.21", optional = true} -sphinx-notfound-page = {version = ">=1.0.0", optional = true} -sphinx-reredirects = {version = ">=0.1.1", optional = true} +furo = {version = ">=2024.5.6", python = ">=3.9", optional = true} +myst-parser = {version = ">=3", python = ">=3.9", optional = true} +sphinx = {version = ">=7", python = ">=3.9", optional = true} +sphinx-copybutton = {version = ">=0.5.2", python = ">=3.9", optional = true} +sphinx-inline-tabs = {version = ">=2023.4.21", python = ">=3.9", optional = true} +sphinx-notfound-page = {version = ">=1.0.0", python = ">=3.9", optional = true} +sphinx-reredirects = {version = ">=0.1.5", python = ">=3.9", optional = true} # File storage dependencies installed as optional 'filesystem' extras fs-s3fs = {version = ">=1.1.1", optional = true} @@ -88,22 +83,25 @@ pyarrow = { version = ">=13", optional = true } # Testing dependencies installed as optional 'testing' extras pytest = {version=">=7.2.1", optional = true} -pytest-durations = {version = ">=1.2.0", optional = true} # installed as optional 'faker' extra -faker = {version = ">=22.5,<26.0", optional = true} +faker = {version = ">=22.5,<27.0", optional = true} + +# Crypto extras +cryptography = { version = ">=3.4.6", optional = true } +PyJWT = { version = "~=2.4", optional = true } [tool.poetry.extras] -# TODO: Add 'cryptography' and 'PyJWT' to the 'jwt' when we want to remove them -# from the main dependencies -jwt = [] +jwt = [ + "cryptography", + "PyJWT", +] docs = [ "furo", "myst-parser", "pytest", "sphinx", "sphinx-copybutton", - "sphinx-autobuild", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects", @@ -111,7 +109,6 @@ docs = [ s3 = ["fs-s3fs"] testing = [ "pytest", - "pytest-durations" ] parquet = ["numpy", "pyarrow"] faker = ["faker"] @@ -120,9 +117,8 @@ faker = ["faker"] coverage = {extras = ["toml"], version = ">=7.4"} deptry = ">=0.15.0" -# TODO: Remove the Python 3.12 marker when DuckDB supports it -duckdb = { version = ">=0.8.0", python = "<3.12" } -duckdb-engine = { version = ">=0.9.4", python = "<3.12" } +duckdb = ">=0.8.0" +duckdb-engine = { version = ">=0.9.4", python = "<4" } fastjsonschema = ">=2.19.1" pytest-benchmark = ">=4.0.0" @@ -136,7 +132,6 @@ xdoctest = ">=1.1.1" [tool.poetry.group.typing.dependencies] mypy = ">=1.9" types-jsonschema = ">=4.17.0.6" -types-python-dateutil = ">=2.8.19" types-pytz = ">=2022.7.1.2" types-requests = ">=2.28.11" types-simplejson = ">=3.18.0" @@ -146,7 +141,7 @@ types-PyYAML = ">=6.0.12" pytest-codspeed = ">=2.2.0" [tool.pytest.ini_options] -addopts = '--ignore=singer_sdk/helpers/_simpleeval.py -m "not external"' +addopts = '--durations=10 --ignore=singer_sdk/helpers/_simpleeval.py -m "not external"' filterwarnings = [ "error", "ignore:Could not configure external gitlab tests:UserWarning", @@ -177,7 +172,7 @@ norecursedirs = "cookiecutter" [tool.commitizen] name = "cz_version_bump" -version = "0.37.0" +version = "0.39.1" changelog_merge_prerelease = true prerelease_offset = 1 tag_format = "v$major.$minor.$patch$prerelease" @@ -207,6 +202,7 @@ omit = [ "tests/*", "samples/*", "singer_sdk/helpers/_compat.py", + "singer_sdk/helpers/types.py", ] [tool.coverage.report] @@ -238,8 +234,6 @@ types-requests = "requests" [tool.deptry.per_rule_ignores] DEP002 = [ - # Deprecated dependencies - "python-dateutil", # Transitive constraints "numpy", "urllib3", @@ -249,14 +243,12 @@ DEP002 = [ "furo", "myst-parser", "sphinx", - "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects", # Plugins "fs-s3fs", - "pytest-durations", ] [tool.mypy] @@ -272,9 +264,9 @@ warn_unused_ignores = true ignore_missing_imports = true module = [ "backports.datetime_fromisoformat.*", - "joblib.*", # TODO: Remove when https://github.com/joblib/joblib/issues/1516 is shipped - "jsonpath_ng.*", - "pyarrow.*", # TODO: Remove when https://github.com/apache/arrow/issues/32609 if implemented and released + "joblib.*", # TODO: Remove when https://github.com/joblib/joblib/issues/1516 is shipped + "jsonpath_ng.*", # TODO: Remove when https://github.com/h2non/jsonpath-ng/issues/152 is implemented and released + "pyarrow.*", # TODO: Remove when https://github.com/apache/arrow/issues/32609 if implemented and released ] [tool.poetry-dynamic-versioning] @@ -282,7 +274,7 @@ enable = true style = "pep440" [build-system] -requires = ["poetry-core==1.9.0", "poetry-dynamic-versioning==1.2.0"] +requires = ["poetry-core==1.9.0", "poetry-dynamic-versioning==1.4.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry.plugins."pytest11"] @@ -319,6 +311,7 @@ select = [ "E", # pycodestyle (error) "W", # pycodestyle (warning) "C90", # mccabe + "DOC", # pydocstyle "I", # isort "N", # pep8-naming "D", # pydocstyle/flake8-docstrings @@ -378,6 +371,7 @@ unfixable = [ "ANN", "D1", "D2", + "DOC", "FBT001", "FBT003", "PLR2004", @@ -386,8 +380,10 @@ unfixable = [ "PLC2701", # Allow usage of private members in tests "PLR6301", # Don't suggest making test methods static, etc. ] +# Disabled some checks in helper modules +"singer_sdk/helpers/_*.py" = ["DOC"] # Disabled some checks in samples code -"samples/*" = ["ANN", "D"] +"samples/*" = ["ANN", "D", "DOC"] # Templates support a generic resource of type Any. "singer_sdk/testing/*.py" = ["S101"] "singer_sdk/testing/templates.py" = ["ANN401"] diff --git a/samples/sample_duckdb/__init__.py b/samples/sample_duckdb/__init__.py new file mode 100644 index 000000000..bbfa07726 --- /dev/null +++ b/samples/sample_duckdb/__init__.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from .connector import DuckDBConnector + +__all__ = [ + "DuckDBConnector", +] diff --git a/samples/sample_duckdb/connector.py b/samples/sample_duckdb/connector.py new file mode 100644 index 000000000..e8c6c251f --- /dev/null +++ b/samples/sample_duckdb/connector.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import sqlalchemy as sa + +from singer_sdk.connectors import SQLConnector + + +class DuckDBConnector(SQLConnector): + allow_column_alter = True + + @staticmethod + def get_column_alter_ddl( + table_name: str, + column_name: str, + column_type: sa.types.TypeEngine, + ) -> sa.DDL: + return sa.DDL( + "ALTER TABLE %(table_name)s ALTER COLUMN %(column_name)s TYPE %(column_type)s", # noqa: E501 + { + "table_name": table_name, + "column_name": column_name, + "column_type": column_type, + }, + ) diff --git a/samples/sample_tap_dummy_json/.github/dependabot.yml b/samples/sample_tap_dummy_json/.github/dependabot.yml new file mode 100644 index 000000000..0660ffdd4 --- /dev/null +++ b/samples/sample_tap_dummy_json/.github/dependabot.yml @@ -0,0 +1,41 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + commit-message: + prefix: "chore(deps): " + prefix-development: "chore(deps-dev): " + groups: + development-dependencies: + dependency-type: development + runtime-dependencies: + dependency-type: production + update-types: + - "patch" + - package-ecosystem: pip + directory: "/.github/workflows" + schedule: + interval: weekly + commit-message: + prefix: "ci: " + groups: + ci: + patterns: + - "*" + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + commit-message: + prefix: "ci: " + groups: + actions: + patterns: + - "*" diff --git a/samples/sample_tap_dummy_json/.github/workflows/build.yml b/samples/sample_tap_dummy_json/.github/workflows/build.yml new file mode 100644 index 000000000..6698b8b6a --- /dev/null +++ b/samples/sample_tap_dummy_json/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: Release + +on: + push: + +permissions: + contents: write # Needed to upload artifacts to the release + id-token: write # Needed for OIDC PyPI publishing + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: hynek/build-and-inspect-python-package@v2 + + publish: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: [build] + ## TODO: optionally provide the name of the environment for the trusted + ## publisher on PyPI + ## https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment + # environment: pypi + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Upload wheel to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{secrets.GITHUB_TOKEN}} + file: dist/*.whl + tag: ${{github.ref}} + overwrite: true + file_glob: true + + - name: Publish + ## TODO: create a trusted publisher on PyPI + ## https://docs.pypi.org/trusted-publishers/ + uses: pypa/gh-action-pypi-publish@v1.9.0 diff --git a/samples/sample_tap_dummy_json/.github/workflows/test.yml b/samples/sample_tap_dummy_json/.github/workflows/test.yml new file mode 100644 index 000000000..6e3d559c0 --- /dev/null +++ b/samples/sample_tap_dummy_json/.github/workflows/test.yml @@ -0,0 +1,32 @@ +### A CI workflow template that runs linting and python testing +### TODO: Modify as needed or as desired. + +name: Test tap-dummyjson + +on: [push] + +jobs: + pytest: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install Poetry + run: | + pip install poetry + - name: Install dependencies + run: | + poetry env use ${{ matrix.python-version }} + poetry install + - name: Test with pytest + run: | + poetry run pytest diff --git a/samples/sample_tap_dummy_json/.gitignore b/samples/sample_tap_dummy_json/.gitignore new file mode 100644 index 000000000..9fd224433 --- /dev/null +++ b/samples/sample_tap_dummy_json/.gitignore @@ -0,0 +1,139 @@ +# Poetry +poetry.lock + +# Secrets and internal config files +**/.secrets/* + +# Ignore meltano internal cache and sqlite systemdb + +.meltano/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/samples/sample_tap_dummy_json/.pre-commit-config.yaml b/samples/sample_tap_dummy_json/.pre-commit-config.yaml new file mode 100644 index 000000000..5cad9ea54 --- /dev/null +++ b/samples/sample_tap_dummy_json/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +ci: + autofix_prs: true + autoupdate_schedule: weekly + autoupdate_commit_msg: 'chore: pre-commit autoupdate' + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-json + exclude: | + (?x)^( + \.vscode/.*\.json + )$ + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.6 + hooks: + - id: check-dependabot + - id: check-github-workflows + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + - id: ruff-format + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.10.1 + hooks: + - id: mypy + additional_dependencies: + - types-requests diff --git a/samples/sample_tap_dummy_json/.secrets/.gitignore b/samples/sample_tap_dummy_json/.secrets/.gitignore new file mode 100644 index 000000000..33c6acd03 --- /dev/null +++ b/samples/sample_tap_dummy_json/.secrets/.gitignore @@ -0,0 +1,10 @@ +# IMPORTANT! This folder is hidden from git - if you need to store config files or other secrets, +# make sure those are never staged for commit into your git repo. You can store them here or another +# secure location. +# +# Note: This may be redundant with the global .gitignore for, and is provided +# for redundancy. If the `.secrets` folder is not needed, you may delete it +# from the project. + +* +!.gitignore diff --git a/samples/sample_tap_dummy_json/LICENSE b/samples/sample_tap_dummy_json/LICENSE new file mode 100644 index 000000000..20f02f1bc --- /dev/null +++ b/samples/sample_tap_dummy_json/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + + Copyright 2024 Edgar Ramírez-Mondragón + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/samples/sample_tap_dummy_json/README.md b/samples/sample_tap_dummy_json/README.md new file mode 100644 index 000000000..e154663d9 --- /dev/null +++ b/samples/sample_tap_dummy_json/README.md @@ -0,0 +1,131 @@ +# tap-dummyjson + +`tap-dummyjson` is a Singer tap for DummyJSON. + +Built with the [Meltano Tap SDK](https://sdk.meltano.com) for Singer Taps. + + + +## Configuration + +### Accepted Config Options + + + +A full list of supported settings and capabilities for this +tap is available by running: + +```bash +tap-dummyjson --about +``` + +### Configure using environment variables + +This Singer tap will automatically import any environment variables within the working directory's +`.env` if the `--config=ENV` is provided, such that config values will be considered if a matching +environment variable is set either in the terminal context or in the `.env` file. + +### Source Authentication and Authorization + + + +## Usage + +You can easily run `tap-dummyjson` by itself or in a pipeline using [Meltano](https://meltano.com/). + +### Executing the Tap Directly + +```bash +tap-dummyjson --version +tap-dummyjson --help +tap-dummyjson --config CONFIG --discover > ./catalog.json +``` + +## Developer Resources + +Follow these instructions to contribute to this project. + +### Initialize your Development Environment + +```bash +pipx install poetry +poetry install +``` + +### Create and Run Tests + +Create tests within the `tests` subfolder and + then run: + +```bash +poetry run pytest +``` + +You can also test the `tap-dummyjson` CLI interface directly using `poetry run`: + +```bash +poetry run tap-dummyjson --help +``` + +### Testing with [Meltano](https://www.meltano.com) + +_**Note:** This tap will work in any Singer environment and does not require Meltano. +Examples here are for convenience and to streamline end-to-end orchestration scenarios._ + + + +Next, install Meltano (if you haven't already) and any needed plugins: + +```bash +# Install meltano +pipx install meltano +# Initialize meltano within this directory +cd tap-dummyjson +meltano install +``` + +Now you can test and orchestrate using Meltano: + +```bash +# Test invocation: +meltano invoke tap-dummyjson --version +# OR run a test `elt` pipeline: +meltano elt tap-dummyjson target-jsonl +``` + +### SDK Dev Guide + +See the [dev guide](https://sdk.meltano.com/en/latest/dev_guide.html) for more instructions on how to use the SDK to +develop your own taps and targets. diff --git a/samples/sample_tap_dummy_json/__init__.py b/samples/sample_tap_dummy_json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/samples/sample_tap_dummy_json/meltano.yml b/samples/sample_tap_dummy_json/meltano.yml new file mode 100644 index 000000000..02a2120e2 --- /dev/null +++ b/samples/sample_tap_dummy_json/meltano.yml @@ -0,0 +1,43 @@ +version: 1 +send_anonymous_usage_stats: true +project_id: "tap-dummyjson" +default_environment: test +environments: +- name: test +plugins: + extractors: + - name: "tap-dummyjson" + namespace: "tap_dummyjson" + + pip_url: -e . + + capabilities: + - state + - catalog + - discover + - about + - stream-maps + + settings: + - name: username + label: Username + - name: password + label: Password + kind: password + sensitive: true + - name: api_url + label: API URL + description: The base URL for the API + - name: start_date + kind: date_iso8601 + + settings_group_validation: + - [username, password] + + config: + start_date: '2024-01-01T00:00:00Z' + + loaders: + - name: target-jsonl + variant: andyh1203 + pip_url: target-jsonl diff --git a/samples/sample_tap_dummy_json/output/.gitignore b/samples/sample_tap_dummy_json/output/.gitignore new file mode 100644 index 000000000..80ff9d2a6 --- /dev/null +++ b/samples/sample_tap_dummy_json/output/.gitignore @@ -0,0 +1,4 @@ +# This directory is used as a target by target-jsonl, so ignore all files + +* +!.gitignore diff --git a/samples/sample_tap_dummy_json/plugins/loaders/target-jsonl--andyh1203.lock b/samples/sample_tap_dummy_json/plugins/loaders/target-jsonl--andyh1203.lock new file mode 100644 index 000000000..11fa0ba2e --- /dev/null +++ b/samples/sample_tap_dummy_json/plugins/loaders/target-jsonl--andyh1203.lock @@ -0,0 +1,34 @@ +{ + "plugin_type": "loaders", + "name": "target-jsonl", + "namespace": "target_jsonl", + "variant": "andyh1203", + "label": "JSON Lines (JSONL)", + "docs": "https://hub.meltano.com/loaders/target-jsonl--andyh1203", + "repo": "https://github.com/andyh1203/target-jsonl", + "pip_url": "target-jsonl", + "description": "JSONL loader", + "logo_url": "https://hub.meltano.com/assets/logos/loaders/jsonl.png", + "settings": [ + { + "name": "destination_path", + "kind": "string", + "value": "output", + "label": "Destination Path", + "description": "Sets the destination path the JSONL files are written to, relative\nto the project root.\n\nThe directory needs to exist already, it will not be created\nautomatically.\n\nTo write JSONL files to the project root, set an empty string (`\"\"`).\n" + }, + { + "name": "do_timestamp_file", + "kind": "boolean", + "value": false, + "label": "Include Timestamp in File Names", + "description": "Specifies if the files should get timestamped.\n\nBy default, the resulting file will not have a timestamp in the file name (i.e. `exchange_rate.jsonl`).\n\nIf this option gets set to `true`, the resulting file will have a timestamp associated with it (i.e. `exchange_rate-{timestamp}.jsonl`).\n" + }, + { + "name": "custom_name", + "kind": "string", + "label": "Custom File Name Override", + "description": "Specifies a custom name for the filename, instead of the stream name.\n\nThe file name will be `{custom_name}-{timestamp}.jsonl`, if `do_timestamp_file` is `true`.\nOtherwise the file name will be `{custom_name}.jsonl`.\n\nIf custom name is not provided, the stream name will be used.\n" + } + ] +} diff --git a/samples/sample_tap_dummy_json/pyproject.toml b/samples/sample_tap_dummy_json/pyproject.toml new file mode 100644 index 000000000..1263b3ca4 --- /dev/null +++ b/samples/sample_tap_dummy_json/pyproject.toml @@ -0,0 +1,44 @@ +[tool.poetry] +name = "tap-dummyjson" +version = "0.0.1" +description = "Singer tap for DummyJSON, built with the Meltano Singer SDK." +readme = "README.md" +authors = ["Edgar Ramírez-Mondragón "] +keywords = [ + "ELT", + "DummyJSON", +] +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +license = "Apache-2.0" + +[tool.poetry.dependencies] +python = ">=3.8" +requests = "~=2.32.3" +singer-sdk = { version="~=0.38.0", extras = [] } + +[tool.poetry.group.dev.dependencies] +pytest = ">=8" +singer-sdk = { version="~=0.38.0", extras = ["testing"] } + +[tool.poetry.extras] +s3 = ["fs-s3fs"] + +[tool.mypy] +python_version = "3.12" +warn_unused_configs = true + +[build-system] +requires = ["poetry-core==1.9.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +# CLI declaration +tap-dummyjson = 'tap_dummyjson.tap:TapDummyJSON.cli' diff --git a/samples/sample_tap_dummy_json/ruff.toml b/samples/sample_tap_dummy_json/ruff.toml new file mode 100644 index 000000000..dd2f13459 --- /dev/null +++ b/samples/sample_tap_dummy_json/ruff.toml @@ -0,0 +1,24 @@ +src = ["tap_dummyjson"] +target-version = "py38" + +[lint] +ignore = [ + "ANN", + "ARG", + "D", + "COM812", # missing-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "S113", # request-without-timeout + "PLR2004", # magic-value-comparison + "RUF012", # mutable-class-default +] +select = ["ALL"] + +[lint.flake8-annotations] +allow-star-arg-any = true + +[lint.isort] +known-first-party = ["tap_dummyjson"] + +[lint.pydocstyle] +convention = "google" diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/__init__.py b/samples/sample_tap_dummy_json/tap_dummyjson/__init__.py new file mode 100644 index 000000000..81bcb2663 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/__init__.py @@ -0,0 +1 @@ +"""Tap for DummyJSON.""" diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/__main__.py b/samples/sample_tap_dummy_json/tap_dummyjson/__main__.py new file mode 100644 index 000000000..a51459e64 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/__main__.py @@ -0,0 +1,7 @@ +"""DummyJSON entry point.""" + +from __future__ import annotations + +from tap_dummyjson.tap import TapDummyJSON + +TapDummyJSON.cli() diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/auth.py b/samples/sample_tap_dummy_json/tap_dummyjson/auth.py new file mode 100644 index 000000000..4f21f432f --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/auth.py @@ -0,0 +1,72 @@ +import logging +import time + +import requests +from singer_sdk.authenticators import SingletonMeta + +logger = logging.getLogger(__name__) + +EXPIRES_IN_MINS = 30 + + +class DummyJSONAuthenticator(metaclass=SingletonMeta): + def __init__(self, auth_url, refresh_token_url, username, password): + self.auth_url = auth_url + self.refresh_token_url = refresh_token_url + self.username = username + self.password = password + + self.token = None + self.refresh_token = None + + self.expires = 0 + + def __call__(self, request): + if not self.refresh_token: + logger.info("Retrieving token") + self.auth() + request.headers["Authorization"] = f"Bearer {self.token}" + return request + + if self.needs_refresh(): + logger.info("Refreshing token") + self.refresh() + request.headers["Authorization"] = f"Bearer {self.token}" + return request + + return request + + def needs_refresh(self) -> bool: + return True if self.expires is None else self.expires < time.time() + + def _handle_response(self, response): + if response.status_code != 200: + logger.error("Error: %s", response.text) + response.raise_for_status() + + data = response.json() + self.token = data["token"] + self.refresh_token = data["refreshToken"] + self.expires = time.time() + EXPIRES_IN_MINS * 60 + logger.info("Authenticated") + + def refresh(self): + response = requests.post( + self.refresh_token_url, + json={ + "refreshToken": self.refresh_token, + "expiresInMins": EXPIRES_IN_MINS, + }, + ) + self._handle_response(response) + + def auth(self): + response = requests.post( + self.auth_url, + json={ + "username": self.username, + "password": self.password, + "expiresInMins": EXPIRES_IN_MINS, + }, + ) + self._handle_response(response) diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/client.py b/samples/sample_tap_dummy_json/tap_dummyjson/client.py new file mode 100644 index 000000000..3f0ba2a91 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/client.py @@ -0,0 +1,41 @@ +"""REST client handling, including DummyJSONStream base class.""" + +from __future__ import annotations + +from singer_sdk.pagination import BaseOffsetPaginator +from singer_sdk.streams import RESTStream + +from .auth import DummyJSONAuthenticator + +PAGE_SIZE = 25 + + +class DummyJSONStream(RESTStream): + """DummyJSON stream class.""" + + records_jsonpath: str = ... + + @property + def url_base(self): + return self.config["api_url"] + + @property + def authenticator(self): + return DummyJSONAuthenticator( + auth_url=f"{self.url_base}/auth/login", + refresh_token_url=f"{self.url_base}/refresh", + username=self.config["username"], + password=self.config["password"], + ) + + def get_new_paginator(self): + return BaseOffsetPaginator(start_value=0, page_size=PAGE_SIZE) + + def get_url_params(self, context, next_page_token): + return { + "skip": next_page_token, + "limit": PAGE_SIZE, + } + + def post_process(self, row, context=None): + return row diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/schemas/__init__.py b/samples/sample_tap_dummy_json/tap_dummyjson/schemas/__init__.py new file mode 100644 index 000000000..06c0a1988 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/schemas/__init__.py @@ -0,0 +1 @@ +"""JSON schema files for the REST API.""" diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/streams.py b/samples/sample_tap_dummy_json/tap_dummyjson/streams.py new file mode 100644 index 000000000..b9867bf84 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/streams.py @@ -0,0 +1,67 @@ +from singer_sdk import typing as th + +from .client import DummyJSONStream + + +class Products(DummyJSONStream): + """Define custom stream.""" + + name = "products" + path = "/products" + + records_jsonpath = "$.products[*]" + + primary_keys = ["id"] + + replication_key = None + + schema = th.PropertiesList( + th.Property("id", th.IntegerType), + th.Property("title", th.StringType), + th.Property("description", th.StringType), + th.Property("category", th.StringType), + th.Property("price", th.NumberType), + th.Property("discountPercentage", th.NumberType), + th.Property("rating", th.NumberType), + th.Property("stock", th.NumberType), + th.Property("tags", th.ArrayType(th.StringType)), + th.Property("brand", th.StringType), + th.Property("sku", th.StringType), + th.Property("weight", th.NumberType), + th.Property( + "dimensions", + th.ObjectType( + th.Property("width", th.NumberType), + th.Property("height", th.NumberType), + th.Property("depth", th.NumberType), + ), + ), + th.Property("warrantyInformation", th.StringType), + th.Property("shippingInformation", th.StringType), + th.Property("availabilityStatus", th.StringType), + th.Property( + "reviews", + th.ArrayType( + th.ObjectType( + th.Property("rating", th.NumberType), + th.Property("comment", th.StringType), + th.Property("date", th.DateTimeType), + th.Property("reviewerName", th.StringType), + th.Property("reviewerEmail", th.StringType), + ) + ), + ), + th.Property("returnPolicy", th.StringType), + th.Property("minimumOrderQuantity", th.NumberType), + th.Property( + "meta", + th.ObjectType( + th.Property("createdAt", th.DateTimeType), + th.Property("updatedAt", th.DateTimeType), + th.Property("barcode", th.StringType), + th.Property("qrCode", th.StringType), + ), + ), + th.Property("images", th.ArrayType(th.StringType)), + th.Property("thumbnail", th.StringType), + ).to_dict() diff --git a/samples/sample_tap_dummy_json/tap_dummyjson/tap.py b/samples/sample_tap_dummy_json/tap_dummyjson/tap.py new file mode 100644 index 000000000..b352d0172 --- /dev/null +++ b/samples/sample_tap_dummy_json/tap_dummyjson/tap.py @@ -0,0 +1,51 @@ +from singer_sdk import Tap +from singer_sdk import typing as th # JSON schema typing helpers + +from . import streams + + +class TapDummyJSON(Tap): + """DummyJSON tap class.""" + + name = "tap-dummyjson" + + config_jsonschema = th.PropertiesList( + th.Property( + "username", + th.StringType, + required=True, + description="Username for the API service", + ), + th.Property( + "password", + th.StringType, + required=True, + secret=True, # Flag config as protected. + description="Password for the API service", + ), + th.Property( + "start_date", + th.DateTimeType, + description="The earliest record date to sync", + ), + th.Property( + "api_url", + th.StringType, + default="https://dummyjson.com", + description="The base url for the API service", + ), + ).to_dict() + + def discover_streams(self): + """Return a list of discovered streams. + + Returns: + A list of discovered streams. + """ + return [ + streams.Products(self), + ] + + +if __name__ == "__main__": + TapDummyJSON.cli() diff --git a/samples/sample_tap_dummy_json/tests/__init__.py b/samples/sample_tap_dummy_json/tests/__init__.py new file mode 100644 index 000000000..1c65c2337 --- /dev/null +++ b/samples/sample_tap_dummy_json/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for tap-dummyjson.""" diff --git a/samples/sample_tap_dummy_json/tests/test_core.py b/samples/sample_tap_dummy_json/tests/test_core.py new file mode 100644 index 000000000..a9f180afc --- /dev/null +++ b/samples/sample_tap_dummy_json/tests/test_core.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from samples.sample_tap_dummy_json.tap_dummyjson.tap import TapDummyJSON +from singer_sdk.testing import get_tap_test_class + +CONFIG = { + "username": "emilys", + "password": "emilyspass", +} + +TestTapDummyJSON = get_tap_test_class(tap_class=TapDummyJSON, config=CONFIG) diff --git a/samples/sample_tap_dummy_json/tox.ini b/samples/sample_tap_dummy_json/tox.ini new file mode 100644 index 000000000..6be1c116a --- /dev/null +++ b/samples/sample_tap_dummy_json/tox.ini @@ -0,0 +1,19 @@ +# This file can be used to customize tox tests as well as other test frameworks like flake8 and mypy + +[tox] +envlist = py{38,39,310,311,312} +isolated_build = true + +[testenv] +allowlist_externals = poetry +commands = + poetry install -v + poetry run pytest + +[testenv:pytest] +# Run the python tests. +# To execute, run `tox -e pytest` +envlist = py{38,39,310,311,312} +commands = + poetry install -v + poetry run pytest diff --git a/samples/sample_tap_gitlab/schemas/epic_issues.json b/samples/sample_tap_gitlab/schemas/epic_issues.json index 36b4e14a5..6401d892e 100644 --- a/samples/sample_tap_gitlab/schemas/epic_issues.json +++ b/samples/sample_tap_gitlab/schemas/epic_issues.json @@ -115,7 +115,7 @@ "type": "string" }, "discussion_locked": { - "type": "null" + "type": ["boolean", "null"] }, "downvotes": { "type": "integer" diff --git a/samples/sample_tap_gitlab/schemas/projects.json b/samples/sample_tap_gitlab/schemas/projects.json index 2f9ececc4..bd7ca0582 100644 --- a/samples/sample_tap_gitlab/schemas/projects.json +++ b/samples/sample_tap_gitlab/schemas/projects.json @@ -1,29 +1,16 @@ { "type": "object", "properties": { - "approvals_before_merge": { - "type": ["null", "integer"] - }, "archived": { "type": ["null", "boolean"] }, "avatar_url": { "type": ["null", "string"] }, - "builds_access_level": { - "type": ["null", "string"], - "enum": ["disabled", "private", "enabled"] - }, - "container_registry_enabled": { - "type": ["null", "boolean"] - }, "created_at": { "type": ["string", "null"], "format": "date-time" }, - "creator_id": { - "type": ["null", "integer"] - }, "default_branch": { "type": ["null", "string"] }, @@ -39,22 +26,10 @@ "id": { "type": ["null", "integer"] }, - "issues_enabled": { - "type": ["null", "boolean"] - }, "last_activity_at": { "type": ["string", "null"], "format": "date-time" }, - "lfs_enabled": { - "type": ["null", "boolean"] - }, - "merge_requests_enabled": { - "type": ["null", "boolean"] - }, - "merge_method": { - "type": ["null", "string"] - }, "name": { "type": ["null", "string"] }, @@ -84,12 +59,6 @@ } } }, - "only_allow_merge_if_all_discussions_are_resolved": { - "type": ["null", "boolean"] - }, - "only_allow_merge_if_pipeline_succeeds": { - "type": ["null", "boolean"] - }, "open_issues_count": { "type": ["null", "integer"] }, @@ -126,15 +95,6 @@ } } }, - "public_jobs": { - "type": ["null", "boolean"] - }, - "request_access_enabled": { - "type": ["null", "boolean"] - }, - "shared_runners_enabled": { - "type": ["null", "boolean"] - }, "shared_with_groups": { "type": ["array", "null"], "items": { @@ -152,9 +112,6 @@ } } }, - "snippets_enabled": { - "type": ["null", "boolean"] - }, "ssh_url_to_repo": { "type": ["null", "string"] }, @@ -173,9 +130,6 @@ }, "web_url": { "type": ["null", "string"] - }, - "wiki_enabled": { - "type": ["null", "boolean"] } } } diff --git a/samples/sample_tap_google_analytics/__init__.py b/samples/sample_tap_google_analytics/__init__.py deleted file mode 100644 index dbe8aec91..000000000 --- a/samples/sample_tap_google_analytics/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Google Analytics sample.""" - -from __future__ import annotations diff --git a/samples/sample_tap_google_analytics/__main__.py b/samples/sample_tap_google_analytics/__main__.py deleted file mode 100644 index e274833e6..000000000 --- a/samples/sample_tap_google_analytics/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import annotations - -from samples.sample_tap_google_analytics.ga_tap import SampleTapGoogleAnalytics - -SampleTapGoogleAnalytics.cli() diff --git a/samples/sample_tap_google_analytics/ga_tap.py b/samples/sample_tap_google_analytics/ga_tap.py deleted file mode 100644 index 0bade4370..000000000 --- a/samples/sample_tap_google_analytics/ga_tap.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Sample tap test for tap-google-analytics.""" - -from __future__ import annotations - -import json -from pathlib import Path - -from samples.sample_tap_google_analytics.ga_tap_stream import ( - GASimpleSampleStream, - SampleGoogleAnalyticsStream, -) -from singer_sdk.tap_base import Tap -from singer_sdk.typing import PropertiesList, Property, StringType - -REPORT_DEFS_FILE = ( - "samples/sample_tap_google_analytics/resources/default_report_definitions.json" -) -REPORT_DEFS = json.loads(Path(REPORT_DEFS_FILE).read_text()) - - -class SampleTapGoogleAnalytics(Tap): - """Sample tap for GoogleAnalytics.""" - - name: str = "sample-tap-google-analytics" - config_jsonschema = PropertiesList( - Property("view_id", StringType(), required=True), - Property( - "client_email", - StringType(), - required=True, - examples=["me@example.com"], - ), - Property("private_key", StringType(), required=True, secret=True), - ).to_dict() - - def discover_streams(self) -> list[SampleGoogleAnalyticsStream]: - """Return a list of all streams.""" - return [GASimpleSampleStream(tap=self)] diff --git a/samples/sample_tap_google_analytics/ga_tap_stream.py b/samples/sample_tap_google_analytics/ga_tap_stream.py deleted file mode 100644 index 75cdb4c47..000000000 --- a/samples/sample_tap_google_analytics/ga_tap_stream.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Sample tap stream test for tap-google-analytics.""" - -from __future__ import annotations - -import datetime -import sys -import typing as t - -from singer_sdk.authenticators import OAuthJWTAuthenticator -from singer_sdk.streams import RESTStream - -if sys.version_info < (3, 9): - import importlib_resources -else: - from importlib import resources as importlib_resources - - -GOOGLE_OAUTH_ENDPOINT = "https://oauth2.googleapis.com/token" -GA_OAUTH_SCOPES = "https://www.googleapis.com/auth/analytics.readonly" -SCHEMAS_DIR = importlib_resources.files(__package__) / "schemas" - - -class GoogleJWTAuthenticator(OAuthJWTAuthenticator): - """Class responsible for Google Auth via JWT and OAuth.""" - - @property - def client_id(self) -> str: - """Override since Google auth uses email, not numeric client ID.""" - return t.cast(str, self.config["client_email"]) - - -class SampleGoogleAnalyticsStream(RESTStream): - """Sample tap test for google-analytics.""" - - url_base = "https://analyticsreporting.googleapis.com/v4" - path = "/reports:batchGet" - rest_method = "POST" - - # Child class overrides: - dimensions: tuple[str] = () - metrics: tuple[str] = () - - @property - def authenticator(self) -> GoogleJWTAuthenticator: - """Return authenticator for Google Analytics.""" - return GoogleJWTAuthenticator( - stream=self, - auth_endpoint=GOOGLE_OAUTH_ENDPOINT, - oauth_scopes=GA_OAUTH_SCOPES, - ) - - def prepare_request_payload( - self, - context: dict | None, # noqa: ARG002 - next_page_token: t.Any | None, # noqa: ARG002 - ) -> dict | None: - """Prepare the data payload for the REST API request.""" - request_def = { - "viewId": self.config["view_id"], - "metrics": [{"expression": m} for m in self.metrics], - "dimensions": [{"name": d} for d in self.dimensions], - } - if self.config.get("start_date"): - request_def["dateRanges"] = [ - { - "startDate": self.config.get("start_date"), - "endDate": datetime.datetime.now(datetime.timezone.utc), - }, - ] - return {"reportRequests": [request_def]} - - def parse_response(self, response) -> t.Iterable[dict]: - """Parse Google Analytics API response into individual records.""" - self.logger.info( - "Received raw Google Analytics query response: %s", - response.json(), - ) - report_data = response.json().get("reports", [{}])[0].get("data") - if not report_data: - self.logger.info( - "Received empty Google Analytics query response: %s", - response.json(), - ) - for total in report_data["totals"]: - yield {"totals": total["values"]} - - -class GASimpleSampleStream(SampleGoogleAnalyticsStream): - """A super simple sample report.""" - - name = "simple_sample" - schema_filepath = SCHEMAS_DIR / "simple-sample.json" - - dimensions = ("ga:date",) - metrics = ("ga:users", "ga:sessions") diff --git a/samples/sample_tap_google_analytics/resources/default_report_definitions.json b/samples/sample_tap_google_analytics/resources/default_report_definitions.json deleted file mode 100644 index 33978e57f..000000000 --- a/samples/sample_tap_google_analytics/resources/default_report_definitions.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "project_id": { - "type": "integer" - }, - "short_id": { - "type": "string" - }, - "title": { - "type": [ - "null", - "string" - ] - }, - "author_name": { - "type": [ - "null", - "string" - ] - }, - "author_email": { - "type": [ - "null", - "string" - ] - }, - "authored_date": { - "type": "string", - "format": "date-time" - }, - "committer_name": { - "type": [ - "null", - "string" - ] - }, - "committer_email": { - "type": [ - "null", - "string" - ] - }, - "committed_date": { - "type": "string", - "format": "date-time" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "message": { - "type": [ - "null", - "string" - ] - }, - "allow_failure": { - "type": [ - "null", - "boolean" - ] - }, - "parent_ids": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ] - }, - "stats": { - "type": "object", - "properties": { - "additions": { - "type": "integer" - }, - "deletions": { - "type": "integer" - }, - "total": { - "type": "integer" - } - } - } - } -} \ No newline at end of file diff --git a/samples/sample_tap_google_analytics/schemas/simple-sample.json b/samples/sample_tap_google_analytics/schemas/simple-sample.json deleted file mode 100644 index 0a5f97f4d..000000000 --- a/samples/sample_tap_google_analytics/schemas/simple-sample.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "object", - "properties": { - "totals": { - "type": "array", - "items": { - "type": "string" - } - } - } -} diff --git a/samples/sample_target_sqlite/__init__.py b/samples/sample_target_sqlite/__init__.py index 07e5172aa..d296c5fb0 100644 --- a/samples/sample_target_sqlite/__init__.py +++ b/samples/sample_target_sqlite/__init__.py @@ -26,7 +26,7 @@ def get_sqlalchemy_url(self, config: dict[str, t.Any]) -> str: # noqa: PLR6301 return f"sqlite:///{config[DB_PATH_CONFIG]}" -class SQLiteSink(SQLSink): +class SQLiteSink(SQLSink[SQLiteConnector]): """The Sink class for SQLite. This class allows developers to optionally override `get_records()` and other diff --git a/singer_sdk/_singerlib/__init__.py b/singer_sdk/_singerlib/__init__.py index bc0b4523b..8386399d6 100644 --- a/singer_sdk/_singerlib/__init__.py +++ b/singer_sdk/_singerlib/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +from singer_sdk._singerlib import exceptions from singer_sdk._singerlib.catalog import ( Catalog, CatalogEntry, @@ -16,6 +17,7 @@ SingerMessageType, StateMessage, exclude_null_dict, + format_message, write_message, ) from singer_sdk._singerlib.schema import Schema, resolve_schema_references @@ -35,7 +37,9 @@ "SingerMessageType", "StateMessage", "StreamMetadata", + "exceptions", "exclude_null_dict", + "format_message", "resolve_schema_references", "strftime", "strptime_to_utc", diff --git a/singer_sdk/_singerlib/catalog.py b/singer_sdk/_singerlib/catalog.py index 183224e2d..c39d46a1b 100644 --- a/singer_sdk/_singerlib/catalog.py +++ b/singer_sdk/_singerlib/catalog.py @@ -199,7 +199,7 @@ def get_standard_metadata( else: entry = Metadata(inclusion=Metadata.InclusionType.AVAILABLE) - mapping[("properties", field_name)] = entry + mapping["properties", field_name] = entry mapping[()] = root diff --git a/singer_sdk/_singerlib/encoding/__init__.py b/singer_sdk/_singerlib/encoding/__init__.py new file mode 100644 index 000000000..65ed5b767 --- /dev/null +++ b/singer_sdk/_singerlib/encoding/__init__.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from ._base import GenericSingerReader, GenericSingerWriter, SingerMessageType +from ._simple import SimpleSingerReader, SimpleSingerWriter + +__all__ = [ + "GenericSingerReader", + "GenericSingerWriter", + "SimpleSingerReader", + "SimpleSingerWriter", + "SingerMessageType", +] diff --git a/singer_sdk/_singerlib/encoding/_base.py b/singer_sdk/_singerlib/encoding/_base.py new file mode 100644 index 000000000..62793776b --- /dev/null +++ b/singer_sdk/_singerlib/encoding/_base.py @@ -0,0 +1,161 @@ +"""Abstract base classes for all Singer messages IO operations.""" + +from __future__ import annotations + +import abc +import enum +import logging +import sys +import typing as t +from collections import Counter, defaultdict + +from singer_sdk._singerlib import exceptions + +if sys.version_info < (3, 11): + from backports.datetime_fromisoformat import MonkeyPatch + + MonkeyPatch.patch_fromisoformat() + +logger = logging.getLogger(__name__) + + +# TODO: Use to default to 'str' here +# https://peps.python.org/pep-0696/ +T = t.TypeVar("T", str, bytes) +M = t.TypeVar("M") + + +class SingerMessageType(str, enum.Enum): + """Singer specification message types.""" + + RECORD = "RECORD" + SCHEMA = "SCHEMA" + STATE = "STATE" + ACTIVATE_VERSION = "ACTIVATE_VERSION" + BATCH = "BATCH" + + +class GenericSingerReader(t.Generic[T], metaclass=abc.ABCMeta): + """Interface for all plugins reading Singer messages as strings or bytes.""" + + @t.final + def listen(self, file_input: t.IO[T] | None = None) -> None: + """Read from input until all messages are processed. + + Args: + file_input: Readable stream of messages. Defaults to standard in. + """ + self._process_lines(file_input or self.default_input) + self._process_endofpipe() + + def _process_lines(self, file_input: t.IO[T]) -> t.Counter[str]: + """Internal method to process jsonl lines from a Singer tap. + + Args: + file_input: Readable stream of messages, each on a separate line. + + Returns: + A counter object for the processed lines. + """ + stats: dict[str, int] = defaultdict(int) + for line in file_input: + line_dict = self.deserialize_json(line) + self._assert_line_requires(line_dict, requires={"type"}) + + record_type: SingerMessageType = line_dict["type"] + if record_type == SingerMessageType.SCHEMA: + self._process_schema_message(line_dict) + + elif record_type == SingerMessageType.RECORD: + self._process_record_message(line_dict) + + elif record_type == SingerMessageType.ACTIVATE_VERSION: + self._process_activate_version_message(line_dict) + + elif record_type == SingerMessageType.STATE: + self._process_state_message(line_dict) + + elif record_type == SingerMessageType.BATCH: + self._process_batch_message(line_dict) + + else: + self._process_unknown_message(line_dict) + + stats[record_type] += 1 + + return Counter(**stats) + + @property + @abc.abstractmethod + def default_input(self) -> t.IO[T]: ... + + @staticmethod + def _assert_line_requires(line_dict: dict, requires: set[str]) -> None: + """Check if dictionary . + + Args: + line_dict: TODO + requires: TODO + + Raises: + InvalidInputLine: raised if any required keys are missing + """ + if not requires.issubset(line_dict): + missing = requires - set(line_dict) + msg = f"Line is missing required {', '.join(missing)} key(s): {line_dict}" + raise exceptions.InvalidInputLine(msg) + + @abc.abstractmethod + def deserialize_json(self, line: T) -> dict: ... + + @abc.abstractmethod + def _process_schema_message(self, message_dict: dict) -> None: ... + + @abc.abstractmethod + def _process_record_message(self, message_dict: dict) -> None: ... + + @abc.abstractmethod + def _process_state_message(self, message_dict: dict) -> None: ... + + @abc.abstractmethod + def _process_activate_version_message(self, message_dict: dict) -> None: ... + + @abc.abstractmethod + def _process_batch_message(self, message_dict: dict) -> None: ... + + def _process_unknown_message(self, message_dict: dict) -> None: # noqa: PLR6301 + """Internal method to process unknown message types from a Singer tap. + + Args: + message_dict: Dictionary representation of the Singer message. + + Raises: + ValueError: raised if a message type is not recognized + """ + record_type = message_dict["type"] + msg = f"Unknown message type '{record_type}' in message." + raise ValueError(msg) + + def _process_endofpipe(self) -> None: # noqa: PLR6301 + logger.debug("End of pipe reached") + + +class GenericSingerWriter(t.Generic[T, M], metaclass=abc.ABCMeta): + """Interface for all plugins writing Singer messages as strings or bytes.""" + + def format_message(self, message: M) -> T: + """Format a message as a JSON string. + + Args: + message: The message to format. + + Returns: + The formatted message. + """ + return self.serialize_message(message) + + @abc.abstractmethod + def serialize_message(self, message: M) -> T: ... + + @abc.abstractmethod + def write_message(self, message: M) -> None: ... diff --git a/singer_sdk/_singerlib/encoding/_simple.py b/singer_sdk/_singerlib/encoding/_simple.py new file mode 100644 index 000000000..7ce148fc3 --- /dev/null +++ b/singer_sdk/_singerlib/encoding/_simple.py @@ -0,0 +1,243 @@ +from __future__ import annotations + +import json +import logging +import sys +import typing as t +from dataclasses import asdict, dataclass, field +from datetime import datetime, timezone + +from singer_sdk._singerlib.exceptions import InvalidInputLine +from singer_sdk._singerlib.json import deserialize_json, serialize_json + +from ._base import GenericSingerReader, GenericSingerWriter, SingerMessageType + +logger = logging.getLogger(__name__) + + +def exclude_null_dict(pairs: list[tuple[str, t.Any]]) -> dict[str, t.Any]: + """Exclude null values from a dictionary. + + Args: + pairs: The dictionary key-value pairs. + + Returns: + The filtered key-value pairs. + """ + return {key: value for key, value in pairs if value is not None} + + +@dataclass +class Message: + """Singer base message.""" + + type: SingerMessageType = field(init=False) + """The message type.""" + + def to_dict(self) -> dict[str, t.Any]: + """Return a dictionary representation of the message. + + Returns: + A dictionary with the defined message fields. + """ + return asdict(self, dict_factory=exclude_null_dict) + + @classmethod + def from_dict( + cls: t.Type[Message], # noqa: UP006 + data: dict[str, t.Any], + ) -> Message: + """Create an encoding from a dictionary. + + Args: + data: The dictionary to create the message from. + + Returns: + The created message. + """ + data.pop("type") + return cls(**data) + + +@dataclass +class RecordMessage(Message): + """Singer record message.""" + + stream: str + """The stream name.""" + + record: dict[str, t.Any] + """The record data.""" + + version: int | None = None + """The record version.""" + + time_extracted: datetime | None = None + """The time the record was extracted.""" + + @classmethod + def from_dict(cls: type[RecordMessage], data: dict[str, t.Any]) -> RecordMessage: + """Create a record message from a dictionary. + + This overrides the default conversion logic, since it uses unnecessary + deep copying and is very slow. + + Args: + data: The dictionary to create the message from. + + Returns: + The created message. + """ + time_extracted = data.get("time_extracted") + return cls( + stream=data["stream"], + record=data["record"], + version=data.get("version"), + time_extracted=datetime.fromisoformat(time_extracted) + if time_extracted + else None, + ) + + def to_dict(self) -> dict[str, t.Any]: + """Return a dictionary representation of the message. + + This overrides the default conversion logic, since it uses unnecessary + deep copying and is very slow. + + Returns: + A dictionary with the defined message fields. + """ + result: dict[str, t.Any] = { + "type": "RECORD", + "stream": self.stream, + "record": self.record, + } + if self.version is not None: + result["version"] = self.version + if self.time_extracted is not None: + result["time_extracted"] = self.time_extracted + return result + + def __post_init__(self) -> None: + """Post-init processing. + + Raises: + ValueError: If the time_extracted is not timezone-aware. + """ + self.type = SingerMessageType.RECORD + if self.time_extracted and not self.time_extracted.tzinfo: + msg = ( + "'time_extracted' must be either None or an aware datetime (with a " + "time zone)" + ) + raise ValueError(msg) + + if self.time_extracted: + self.time_extracted = self.time_extracted.astimezone(timezone.utc) + + +@dataclass +class SchemaMessage(Message): + """Singer schema message.""" + + stream: str + """The stream name.""" + + schema: dict[str, t.Any] + """The schema definition.""" + + key_properties: t.Sequence[str] | None = None + """The key properties.""" + + bookmark_properties: list[str] | None = None + """The bookmark properties.""" + + def __post_init__(self) -> None: + """Post-init processing. + + Raises: + ValueError: If bookmark_properties is not a string or list of strings. + """ + self.type = SingerMessageType.SCHEMA + + if isinstance(self.bookmark_properties, (str, bytes)): + self.bookmark_properties = [self.bookmark_properties] + if self.bookmark_properties and not isinstance(self.bookmark_properties, list): + msg = "bookmark_properties must be a string or list of strings" + raise ValueError(msg) + + +@dataclass +class StateMessage(Message): + """Singer state message.""" + + value: dict[str, t.Any] + """The state value.""" + + def __post_init__(self) -> None: + """Post-init processing.""" + self.type = SingerMessageType.STATE + + +@dataclass +class ActivateVersionMessage(Message): + """Singer activate version message.""" + + stream: str + """The stream name.""" + + version: int + """The version to activate.""" + + def __post_init__(self) -> None: + """Post-init processing.""" + self.type = SingerMessageType.ACTIVATE_VERSION + + +class SimpleSingerReader(GenericSingerReader[str]): + """Base class for all plugins reading Singer messages as strings from stdin.""" + + default_input = sys.stdin + + def deserialize_json(self, line: str) -> dict: # noqa: PLR6301 + """Deserialize a line of json. + + Args: + line: A single line of json. + + Returns: + A dictionary of the deserialized json. + + Raises: + InvalidInputLine: If the line is not valid JSON. + """ + try: + return deserialize_json(line) + except json.decoder.JSONDecodeError as exc: + logger.exception("Unable to parse:\n%s", line) + msg = f"Unable to parse line as JSON: {line}" + raise InvalidInputLine(msg) from exc + + +class SimpleSingerWriter(GenericSingerWriter[str, Message]): + """Interface for all plugins writing Singer messages to stdout.""" + + def serialize_message(self, message: Message) -> str: # noqa: PLR6301 + """Serialize a dictionary into a line of json. + + Args: + message: A Singer message object. + + Returns: + A string of serialized json. + """ + return serialize_json(message.to_dict()) + + def write_message(self, message: Message) -> None: + """Write a message to stdout. + + Args: + message: The message to write. + """ + sys.stdout.write(self.format_message(message) + "\n") + sys.stdout.flush() diff --git a/singer_sdk/_singerlib/exceptions.py b/singer_sdk/_singerlib/exceptions.py new file mode 100644 index 000000000..e3726bb6a --- /dev/null +++ b/singer_sdk/_singerlib/exceptions.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +__all__ = [ + "InvalidInputLine", +] + + +class InvalidInputLine(Exception): + """Raised when an input line is not a valid Singer message.""" diff --git a/singer_sdk/_singerlib/json.py b/singer_sdk/_singerlib/json.py new file mode 100644 index 000000000..184f1e945 --- /dev/null +++ b/singer_sdk/_singerlib/json.py @@ -0,0 +1,61 @@ +from __future__ import annotations # noqa: A005 + +import datetime +import decimal +import json +import typing as t + +import simplejson + +__all__ = [ + "deserialize_json", + "serialize_json", +] + + +def _default_encoding(obj: t.Any) -> str: # noqa: ANN401 + """Default JSON encoder. + + Args: + obj: The object to encode. + + Returns: + The encoded object. + """ + return obj.isoformat(sep="T") if isinstance(obj, datetime.datetime) else str(obj) + + +def deserialize_json(json_str: str | bytes, **kwargs: t.Any) -> dict: + """Deserialize a line of json. + + Args: + json_str: A single line of json. + **kwargs: Optional key word arguments. + + Returns: + A dictionary of the deserialized json. + """ + return json.loads( # type: ignore[no-any-return] + json_str, + parse_float=decimal.Decimal, + **kwargs, + ) + + +def serialize_json(obj: object, **kwargs: t.Any) -> str: + """Serialize a dictionary into a line of json. + + Args: + obj: A Python object usually a dict. + **kwargs: Optional key word arguments. + + Returns: + A string of serialized json. + """ + return simplejson.dumps( + obj, + use_decimal=True, + default=_default_encoding, + separators=(",", ":"), + **kwargs, + ) diff --git a/singer_sdk/_singerlib/messages.py b/singer_sdk/_singerlib/messages.py index f9a93b76b..271c3bea3 100644 --- a/singer_sdk/_singerlib/messages.py +++ b/singer_sdk/_singerlib/messages.py @@ -2,243 +2,29 @@ from __future__ import annotations -import enum -import sys -import typing as t -from dataclasses import asdict, dataclass, field -from datetime import datetime, timezone - -import simplejson as json - -if sys.version_info < (3, 11): - from backports.datetime_fromisoformat import MonkeyPatch - - MonkeyPatch.patch_fromisoformat() - - -class SingerMessageType(str, enum.Enum): - """Singer specification message types.""" - - RECORD = "RECORD" - SCHEMA = "SCHEMA" - STATE = "STATE" - ACTIVATE_VERSION = "ACTIVATE_VERSION" - BATCH = "BATCH" - - -def _default_encoding(obj: t.Any) -> str: # noqa: ANN401 - """Default JSON encoder. - - Args: - obj: The object to encode. - - Returns: - The encoded object. - """ - return obj.isoformat(sep="T") if isinstance(obj, datetime) else str(obj) - - -def exclude_null_dict(pairs: list[tuple[str, t.Any]]) -> dict[str, t.Any]: - """Exclude null values from a dictionary. - - Args: - pairs: The dictionary key-value pairs. - - Returns: - The filtered key-value pairs. - """ - return {key: value for key, value in pairs if value is not None} - - -@dataclass -class Message: - """Singer base message.""" - - type: SingerMessageType = field(init=False) - """The message type.""" - - def to_dict(self) -> dict[str, t.Any]: - """Return a dictionary representation of the message. - - Returns: - A dictionary with the defined message fields. - """ - return asdict(self, dict_factory=exclude_null_dict) - - @classmethod - def from_dict( - cls: t.Type[Message], # noqa: UP006 - data: dict[str, t.Any], - ) -> Message: - """Create an encoding from a dictionary. - - Args: - data: The dictionary to create the message from. - - Returns: - The created message. - """ - data.pop("type") - return cls(**data) - - -@dataclass -class RecordMessage(Message): - """Singer record message.""" - - stream: str - """The stream name.""" - - record: dict[str, t.Any] - """The record data.""" - - version: int | None = None - """The record version.""" - - time_extracted: datetime | None = None - """The time the record was extracted.""" - - @classmethod - def from_dict(cls: type[RecordMessage], data: dict[str, t.Any]) -> RecordMessage: - """Create a record message from a dictionary. - - This overrides the default conversion logic, since it uses unnecessary - deep copying and is very slow. - - Args: - data: The dictionary to create the message from. - - Returns: - The created message. - """ - time_extracted = data.get("time_extracted") - return cls( - stream=data["stream"], - record=data["record"], - version=data.get("version"), - time_extracted=datetime.fromisoformat(time_extracted) - if time_extracted - else None, - ) - - def to_dict(self) -> dict[str, t.Any]: - """Return a dictionary representation of the message. - - This overrides the default conversion logic, since it uses unnecessary - deep copying and is very slow. - - Returns: - A dictionary with the defined message fields. - """ - result: dict[str, t.Any] = { - "type": "RECORD", - "stream": self.stream, - "record": self.record, - } - if self.version is not None: - result["version"] = self.version - if self.time_extracted is not None: - result["time_extracted"] = self.time_extracted - return result - - def __post_init__(self) -> None: - """Post-init processing. - - Raises: - ValueError: If the time_extracted is not timezone-aware. - """ - self.type = SingerMessageType.RECORD - if self.time_extracted and not self.time_extracted.tzinfo: - msg = ( - "'time_extracted' must be either None or an aware datetime (with a " - "time zone)" - ) - raise ValueError(msg) - - if self.time_extracted: - self.time_extracted = self.time_extracted.astimezone(timezone.utc) - - -@dataclass -class SchemaMessage(Message): - """Singer schema message.""" - - stream: str - """The stream name.""" - - schema: dict[str, t.Any] - """The schema definition.""" - - key_properties: t.Sequence[str] | None = None - """The key properties.""" - - bookmark_properties: list[str] | None = None - """The bookmark properties.""" - - def __post_init__(self) -> None: - """Post-init processing. - - Raises: - ValueError: If bookmark_properties is not a string or list of strings. - """ - self.type = SingerMessageType.SCHEMA - - if isinstance(self.bookmark_properties, (str, bytes)): - self.bookmark_properties = [self.bookmark_properties] - if self.bookmark_properties and not isinstance(self.bookmark_properties, list): - msg = "bookmark_properties must be a string or list of strings" - raise ValueError(msg) - - -@dataclass -class StateMessage(Message): - """Singer state message.""" - - value: dict[str, t.Any] - """The state value.""" - - def __post_init__(self) -> None: - """Post-init processing.""" - self.type = SingerMessageType.STATE - - -@dataclass -class ActivateVersionMessage(Message): - """Singer activate version message.""" - - stream: str - """The stream name.""" - - version: int - """The version to activate.""" - - def __post_init__(self) -> None: - """Post-init processing.""" - self.type = SingerMessageType.ACTIVATE_VERSION - - -def format_message(message: Message) -> str: - """Format a message as a JSON string. - - Args: - message: The message to format. - - Returns: - The formatted message. - """ - return json.dumps( - message.to_dict(), - use_decimal=True, - default=_default_encoding, - separators=(",", ":"), - ) - - -def write_message(message: Message) -> None: - """Write a message to stdout. - - Args: - message: The message to write. - """ - sys.stdout.write(format_message(message) + "\n") - sys.stdout.flush() +from .encoding import SimpleSingerWriter as SingerWriter +from .encoding._base import SingerMessageType +from .encoding._simple import ( + ActivateVersionMessage, + Message, + RecordMessage, + SchemaMessage, + StateMessage, + exclude_null_dict, +) + +__all__ = [ + "ActivateVersionMessage", + "Message", + "RecordMessage", + "SchemaMessage", + "SingerMessageType", + "StateMessage", + "exclude_null_dict", + "format_message", + "write_message", +] + +WRITER = SingerWriter() +format_message = WRITER.format_message +write_message = WRITER.write_message diff --git a/singer_sdk/about.py b/singer_sdk/about.py index 23c3eccee..ab3062295 100644 --- a/singer_sdk/about.py +++ b/singer_sdk/about.py @@ -67,6 +67,7 @@ class AboutInfo: capabilities: list[CapabilitiesEnum] settings: dict + env_var_prefix: str class AboutFormatter(abc.ABC): @@ -118,17 +119,33 @@ def format_about(self, about_info: AboutInfo) -> str: # noqa: PLR6301 Returns: A formatted string. """ - return dedent( + output = dedent( f"""\ Name: {about_info.name} Description: {about_info.description} Version: {about_info.version} - SDK Version: {about_info.sdk_version} - Supported Python Versions: {about_info.supported_python_versions} - Capabilities: {about_info.capabilities} - Settings: {about_info.settings}""", + SDK Version: {about_info.sdk_version}""" ) + if about_info.supported_python_versions: + output += "\nSupported Python Versions:\n" + output += "\n".join( + [f" - {v}" for v in about_info.supported_python_versions] + ) + + output += "\nCapabilities:\n" + output += "\n".join([f" - {c}" for c in about_info.capabilities]) + + output += "\nSettings:\n" + for setting, schema in about_info.settings.get("properties", {}).items(): + env_var = about_info.env_var_prefix + setting.upper().replace("-", "_") + json_type = schema.get("type") + output += f" - Name: {setting}\n" + output += f" Type: {json_type}\n" + output += f" Environment Variable: {env_var}\n" + + return output + class JSONFormatter(AboutFormatter, format_name="json"): """About formatter for JSON output.""" @@ -227,31 +244,34 @@ def format_about(self, about_info: AboutInfo) -> str: Returns: A formatted string. """ - # Empty list for string parts - md_list = [] - - # Iterate over Dict to set md - md_list.append( - f"# `{about_info.name}`\n\n" - f"{about_info.description}\n\n" - f"Built with the [Meltano Singer SDK](https://sdk.meltano.com).\n\n", - ) + # Header + output = dedent(f"""\ + # `{about_info.name}`\n + {about_info.description}\n + Built with the [Meltano Singer SDK](https://sdk.meltano.com).\n + """) + + # Process capabilities + output += "## Capabilities\n\n" + output += "\n".join([f"* `{v}`" for v in about_info.capabilities]) + output += "\n\n" - # Process capabilities and settings - - capabilities = "## Capabilities\n\n" - capabilities += "\n".join([f"* `{v}`" for v in about_info.capabilities]) - capabilities += "\n\n" - md_list.append(capabilities) + # Process Supported Python Versions + if about_info.supported_python_versions: + output += "## Supported Python Versions\n\n" + output += "\n".join( + [f"* {v}" for v in about_info.supported_python_versions], + ) + output += "\n\n" - setting = "## Settings\n\n" - settings_table = ( + # Process settings + output += "## Settings\n\n" + output += ( "| Setting | Required | Default | Description |\n" "|:--------|:--------:|:-------:|:------------|\n" ) - settings_table += "\n".join(self._generate_property_rows(about_info.settings)) - setting += settings_table - setting += ( + output += "\n".join(self._generate_property_rows(about_info.settings)) + output += ( "\n\n" + "\n".join( [ @@ -261,17 +281,5 @@ def format_about(self, about_info: AboutInfo) -> str: ) + "\n" ) - setting += "\n" - md_list.append(setting) - - # Process Supported Python Versions - - if about_info.supported_python_versions: - supported_python_versions = "## Supported Python Versions\n\n" - supported_python_versions += "\n".join( - [f"* {v}" for v in about_info.supported_python_versions], - ) - supported_python_versions += "\n" - md_list.append(supported_python_versions) - return "".join(md_list) + return output diff --git a/singer_sdk/authenticators.py b/singer_sdk/authenticators.py index 6d9c7303f..c6478cb92 100644 --- a/singer_sdk/authenticators.py +++ b/singer_sdk/authenticators.py @@ -3,10 +3,10 @@ from __future__ import annotations import base64 +import datetime import math import typing as t import warnings -from datetime import timedelta from types import MappingProxyType from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit @@ -17,8 +17,6 @@ if t.TYPE_CHECKING: import logging - from pendulum import DateTime - from singer_sdk.streams.rest import RESTStream @@ -81,7 +79,12 @@ def __call__(cls, *args: t.Any, **kwargs: t.Any) -> t.Any: # noqa: ANN401 class APIAuthenticatorBase: - """Base class for offloading API auth.""" + """Base class for offloading API auth. + + Attributes: + auth_headers: HTTP headers for authentication. + auth_params: URL query parameters for authentication. + """ def __init__(self, stream: RESTStream) -> None: """Init authenticator. @@ -91,8 +94,8 @@ def __init__(self, stream: RESTStream) -> None: """ self.tap_name: str = stream.tap_name self._config: dict[str, t.Any] = dict(stream.config) - self._auth_headers: dict[str, t.Any] = {} - self._auth_params: dict[str, t.Any] = {} + self.auth_headers: dict[str, t.Any] = {} + self.auth_params: dict[str, t.Any] = {} self.logger: logging.Logger = stream.logger @property @@ -104,24 +107,6 @@ def config(self) -> t.Mapping[str, t.Any]: """ return MappingProxyType(self._config) - @property - def auth_headers(self) -> dict: - """Get headers. - - Returns: - HTTP headers for authentication. - """ - return self._auth_headers or {} - - @property - def auth_params(self) -> dict: - """Get query parameters. - - Returns: - URL query parameters for authentication. - """ - return self._auth_params or {} - def authenticate_request( self, request: requests.PreparedRequest, @@ -179,10 +164,10 @@ def __init__( auth_headers: Authentication headers. """ super().__init__(stream=stream) - if self._auth_headers is None: - self._auth_headers = {} + if self.auth_headers is None: + self.auth_headers = {} if auth_headers: - self._auth_headers.update(auth_headers) + self.auth_headers.update(auth_headers) class APIKeyAuthenticator(APIAuthenticatorBase): @@ -220,13 +205,13 @@ def __init__( raise ValueError(msg) if location == "header": - if self._auth_headers is None: - self._auth_headers = {} - self._auth_headers.update(auth_credentials) + if self.auth_headers is None: + self.auth_headers = {} + self.auth_headers.update(auth_credentials) elif location == "params": - if self._auth_params is None: - self._auth_params = {} - self._auth_params.update(auth_credentials) + if self.auth_params is None: + self.auth_params = {} + self.auth_params.update(auth_credentials) @classmethod def create_for_stream( @@ -269,9 +254,9 @@ def __init__(self, stream: RESTStream, token: str) -> None: super().__init__(stream=stream) auth_credentials = {"Authorization": f"Bearer {token}"} - if self._auth_headers is None: - self._auth_headers = {} - self._auth_headers.update(auth_credentials) + if self.auth_headers is None: + self.auth_headers = {} + self.auth_headers.update(auth_credentials) @classmethod def create_for_stream( @@ -328,9 +313,9 @@ def __init__( auth_token = base64.b64encode(credentials).decode("ascii") auth_credentials = {"Authorization": f"Basic {auth_token}"} - if self._auth_headers is None: - self._auth_headers = {} - self._auth_headers.update(auth_credentials) + if self.auth_headers is None: + self.auth_headers = {} + self.auth_headers.update(auth_credentials) @classmethod def create_for_stream( @@ -382,23 +367,26 @@ def __init__( # Initialize internal tracking attributes self.access_token: str | None = None self.refresh_token: str | None = None - self.last_refreshed: DateTime | None = None + self.last_refreshed: datetime.datetime | None = None self.expires_in: int | None = None - @property - def auth_headers(self) -> dict: - """Return a dictionary of auth headers to be applied. + def authenticate_request( + self, + request: requests.PreparedRequest, + ) -> requests.PreparedRequest: + """Authenticate an OAuth request. - These will be merged with any `http_headers` specified in the stream. + Args: + request: A :class:`requests.PreparedRequest` object. Returns: - HTTP headers for authentication. + The authenticated request object. """ if not self.is_token_valid(): self.update_access_token() - result = super().auth_headers - result["Authorization"] = f"Bearer {self.access_token}" - return result + + self.auth_headers["Authorization"] = f"Bearer {self.access_token}" + return super().authenticate_request(request) @property def auth_endpoint(self) -> str: @@ -558,7 +546,7 @@ def oauth_request_body(self) -> dict: "iss": self.client_id, "scope": self.oauth_scopes, "aud": self.auth_endpoint, - "exp": math.floor((request_time + timedelta(hours=1)).timestamp()), + "exp": math.floor((request_time + datetime.timedelta(hours=1)).timestamp()), "iat": math.floor(request_time.timestamp()), } @@ -570,13 +558,18 @@ def oauth_request_payload(self) -> dict: Payload object for OAuth. Raises: + RuntimeError: If the JWT dependencies are not installed. ValueError: If the private key is not set. """ - import jwt # noqa: PLC0415 - from cryptography.hazmat.backends import default_backend # noqa: PLC0415 - from cryptography.hazmat.primitives import serialization # noqa: PLC0415 + try: + import jwt # noqa: PLC0415 + from cryptography.hazmat.backends import default_backend # noqa: PLC0415 + from cryptography.hazmat.primitives import serialization # noqa: PLC0415 + except ModuleNotFoundError as ex: # pragma: no cover + msg = "Install singer-sdk[jwt] to use OAuthJWTAuthenticator." + raise RuntimeError(msg) from ex - if not self.private_key: + if not self.private_key: # pragma: no cover msg = "Missing 'private_key' property for OAuth payload." raise ValueError(msg) diff --git a/singer_sdk/connectors/sql.py b/singer_sdk/connectors/sql.py index 71b64c52a..470d4e2cf 100644 --- a/singer_sdk/connectors/sql.py +++ b/singer_sdk/connectors/sql.py @@ -2,21 +2,20 @@ from __future__ import annotations -import decimal -import json import logging import typing as t import warnings +from collections import UserString from contextlib import contextmanager from datetime import datetime from functools import lru_cache -import simplejson import sqlalchemy as sa from singer_sdk import typing as th from singer_sdk._singerlib import CatalogEntry, MetadataMapping, Schema from singer_sdk.exceptions import ConfigValidationError +from singer_sdk.helpers._util import dump_json, load_json from singer_sdk.helpers.capabilities import TargetLoadMethods if t.TYPE_CHECKING: @@ -24,6 +23,86 @@ from sqlalchemy.engine.reflection import Inspector +class FullyQualifiedName(UserString): + """A fully qualified table name. + + This class provides a simple way to represent a fully qualified table name + as a single object. The string representation of this object is the fully + qualified table name, with the parts separated by periods. + + The parts of the fully qualified table name are: + - database + - schema + - table + + The database and schema are optional. If only the table name is provided, + the string representation of the object will be the table name alone. + + Example: + ``` + table_name = FullyQualifiedName("my_table", "my_schema", "my_db") + print(table_name) # my_db.my_schema.my_table + ``` + """ + + def __init__( + self, + *, + table: str = "", + schema: str | None = None, + database: str | None = None, + delimiter: str = ".", + ) -> None: + """Initialize the fully qualified table name. + + Args: + table: The name of the table. + schema: The name of the schema. Defaults to None. + database: The name of the database. Defaults to None. + delimiter: The delimiter to use between parts. Defaults to '.'. + + Raises: + ValueError: If the fully qualified name could not be generated. + """ + self.table = table + self.schema = schema + self.database = database + self.delimiter = delimiter + + parts = [] + if self.database: + parts.append(self.prepare_part(self.database)) + if self.schema: + parts.append(self.prepare_part(self.schema)) + if self.table: + parts.append(self.prepare_part(self.table)) + + if not parts: + raise ValueError( + "Could not generate fully qualified name: " + + ":".join( + [ + self.database or "(unknown-db)", + self.schema or "(unknown-schema)", + self.table or "(unknown-table-name)", + ], + ), + ) + + super().__init__(self.delimiter.join(parts)) + + def prepare_part(self, part: str) -> str: # noqa: PLR6301 + """Prepare a part of the fully qualified name. + + Args: + part: The part to prepare. + + Returns: + The prepared part. + """ + return part + + class SQLConnector: # noqa: PLR0904 """Base class for SQLAlchemy-based connectors. @@ -246,7 +325,7 @@ def get_fully_qualified_name( schema_name: str | None = None, db_name: str | None = None, delimiter: str = ".", - ) -> str: + ) -> FullyQualifiedName: """Concatenates a fully qualified name from the parts. Args: @@ -255,34 +334,15 @@ def get_fully_qualified_name( db_name: The name of the database. Defaults to None. delimiter: Generally: '.' for SQL names and '-' for Singer names. - Raises: - ValueError: If all 3 name parts not supplied. - Returns: The fully qualified name as a string. """ - parts = [] - - if db_name: - parts.append(db_name) - if schema_name: - parts.append(schema_name) - if table_name: - parts.append(table_name) - - if not parts: - raise ValueError( - "Could not generate fully qualified name: " - + ":".join( - [ - db_name or "(unknown-db)", - schema_name or "(unknown-schema)", - table_name or "(unknown-table-name)", - ], - ), - ) - - return delimiter.join(parts) + return FullyQualifiedName( + table=table_name, # type: ignore[arg-type] + schema=schema_name, + database=db_name, + delimiter=delimiter, + ) @property def _dialect(self) -> sa.engine.Dialect: @@ -431,12 +491,7 @@ def discover_catalog_entry( `CatalogEntry` object for the given table or a view """ # Initialize unique stream name - unique_stream_id = self.get_fully_qualified_name( - db_name=None, - schema_name=schema_name, - table_name=table_name, - delimiter="-", - ) + unique_stream_id = f"{schema_name}-{table_name}" # Detect key properties possible_primary_keys: list[list[str]] = [] @@ -530,7 +585,7 @@ def discover_catalog_entries(self) -> list[dict]: def parse_full_table_name( # noqa: PLR6301 self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, ) -> tuple[str | None, str | None, str]: """Parse a fully qualified table name into its parts. @@ -549,6 +604,13 @@ def parse_full_table_name( # noqa: PLR6301 A three part tuple (db_name, schema_name, table_name) with any unspecified or unused parts returned as None. """ + if isinstance(full_table_name, FullyQualifiedName): + return ( + full_table_name.database, + full_table_name.schema, + full_table_name.table, + ) + db_name: str | None = None schema_name: str | None = None @@ -562,7 +624,7 @@ def parse_full_table_name( # noqa: PLR6301 return db_name, schema_name, table_name - def table_exists(self, full_table_name: str) -> bool: + def table_exists(self, full_table_name: str | FullyQualifiedName) -> bool: """Determine if the target table already exists. Args: @@ -589,7 +651,7 @@ def schema_exists(self, schema_name: str) -> bool: def get_table_columns( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_names: list[str] | None = None, ) -> dict[str, sa.Column]: """Return a list of table columns. @@ -620,7 +682,7 @@ def get_table_columns( def get_table( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_names: list[str] | None = None, ) -> sa.Table: """Return a table object. @@ -645,7 +707,9 @@ def get_table( schema=schema_name, ) - def column_exists(self, full_table_name: str, column_name: str) -> bool: + def column_exists( + self, full_table_name: str | FullyQualifiedName, column_name: str + ) -> bool: """Determine if the target table already exists. Args: @@ -668,7 +732,7 @@ def create_schema(self, schema_name: str) -> None: def create_empty_table( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, schema: dict, primary_keys: t.Sequence[str] | None = None, partition_keys: list[str] | None = None, @@ -717,7 +781,7 @@ def create_empty_table( def _create_empty_column( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_name: str, sql_type: sa.types.TypeEngine, ) -> None: @@ -755,7 +819,7 @@ def prepare_schema(self, schema_name: str) -> None: def prepare_table( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, schema: dict, primary_keys: t.Sequence[str], partition_keys: list[str] | None = None, @@ -829,7 +893,7 @@ def prepare_table_columns( def prepare_column( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_name: str, sql_type: sa.types.TypeEngine, ) -> None: @@ -854,7 +918,9 @@ def prepare_column( sql_type=sql_type, ) - def rename_column(self, full_table_name: str, old_name: str, new_name: str) -> None: + def rename_column( + self, full_table_name: str | FullyQualifiedName, old_name: str, new_name: str + ) -> None: """Rename the provided columns. Args: @@ -983,7 +1049,7 @@ def _get_type_sort_key( def _get_column_type( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_name: str, ) -> sa.types.TypeEngine: """Get the SQL type of the declared column. @@ -1006,9 +1072,9 @@ def _get_column_type( return column.type - @staticmethod def get_column_add_ddl( - table_name: str, + self, + table_name: str | FullyQualifiedName, column_name: str, column_type: sa.types.TypeEngine, ) -> sa.DDL: @@ -1030,17 +1096,18 @@ def get_column_add_ddl( column_type, ), ) + compiled = create_column_clause.compile(self._engine).string return sa.DDL( "ALTER TABLE %(table_name)s ADD COLUMN %(create_column_clause)s", { "table_name": table_name, - "create_column_clause": create_column_clause, + "create_column_clause": compiled, }, ) @staticmethod def get_column_rename_ddl( - table_name: str, + table_name: str | FullyQualifiedName, column_name: str, new_column_name: str, ) -> sa.DDL: @@ -1068,7 +1135,7 @@ def get_column_rename_ddl( @staticmethod def get_column_alter_ddl( - table_name: str, + table_name: str | FullyQualifiedName, column_name: str, column_type: sa.types.TypeEngine, ) -> sa.DDL: @@ -1127,7 +1194,7 @@ def update_collation( def _adapt_column_type( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, column_name: str, sql_type: sa.types.TypeEngine, ) -> None: @@ -1197,7 +1264,7 @@ def serialize_json(self, obj: object) -> str: # noqa: PLR6301 .. versionadded:: 0.31.0 """ - return simplejson.dumps(obj, use_decimal=True) + return dump_json(obj) def deserialize_json(self, json_str: str) -> object: # noqa: PLR6301 """Deserialize a JSON string to an object. @@ -1213,12 +1280,12 @@ def deserialize_json(self, json_str: str) -> object: # noqa: PLR6301 .. versionadded:: 0.31.0 """ - return json.loads(json_str, parse_float=decimal.Decimal) + return load_json(json_str) def delete_old_versions( self, *, - full_table_name: str, + full_table_name: str | FullyQualifiedName, version_column_name: str, current_version: int, ) -> None: diff --git a/singer_sdk/contrib/batch_encoder_jsonl.py b/singer_sdk/contrib/batch_encoder_jsonl.py index 6ce4c8793..6f121f8d4 100644 --- a/singer_sdk/contrib/batch_encoder_jsonl.py +++ b/singer_sdk/contrib/batch_encoder_jsonl.py @@ -3,10 +3,10 @@ from __future__ import annotations import gzip -import json import typing as t from uuid import uuid4 +from singer_sdk._singerlib.json import serialize_json from singer_sdk.batch import BaseBatcher, lazy_chunked_generator __all__ = ["JSONLinesBatcher"] @@ -45,8 +45,7 @@ def get_batches( mode="wb", ) as gz: gz.writelines( - (json.dumps(record, default=str) + "\n").encode() - for record in chunk + (serialize_json(record) + "\n").encode() for record in chunk ) file_url = fs.geturl(filename) yield [file_url] diff --git a/singer_sdk/exceptions.py b/singer_sdk/exceptions.py index 20ec7ae65..a766952f9 100644 --- a/singer_sdk/exceptions.py +++ b/singer_sdk/exceptions.py @@ -5,6 +5,8 @@ import abc import typing as t +from singer_sdk._singerlib.exceptions import InvalidInputLine # noqa: F401 + if t.TYPE_CHECKING: import requests @@ -137,11 +139,7 @@ class ConformedNameClashException(Exception): class MissingKeyPropertiesError(Exception): - """Raised when a recieved (and/or transformed) record is missing key properties.""" - - -class InvalidInputLine(Exception): - """Raised when an input line is not a valid Singer message.""" + """Raised when a received (and/or transformed) record is missing key properties.""" class InvalidJSONSchema(Exception): diff --git a/singer_sdk/helpers/_classproperty.py b/singer_sdk/helpers/_classproperty.py index 2e3b43775..34eb5f487 100644 --- a/singer_sdk/helpers/_classproperty.py +++ b/singer_sdk/helpers/_classproperty.py @@ -1,18 +1,16 @@ -# flake8: noqa - """Defines the `classproperty` decorator.""" from __future__ import annotations -class classproperty(property): +class classproperty(property): # noqa: N801 """Class property decorator.""" - def __get__(self, obj, objtype=None): + def __get__(self, obj, objtype=None): # noqa: ANN001, ANN204 return super().__get__(objtype) - def __set__(self, obj, value): + def __set__(self, obj, value): # noqa: ANN001, ANN204 super().__set__(type(obj), value) - def __delete__(self, obj): + def __delete__(self, obj): # noqa: ANN001, ANN204 super().__delete__(type(obj)) diff --git a/singer_sdk/helpers/_flattening.py b/singer_sdk/helpers/_flattening.py index 145b76bb2..79ca50fdc 100644 --- a/singer_sdk/helpers/_flattening.py +++ b/singer_sdk/helpers/_flattening.py @@ -9,7 +9,8 @@ from copy import deepcopy import inflection -import simplejson as json + +from singer_sdk._singerlib.json import serialize_json DEFAULT_FLATTENING_SEPARATOR = "__" @@ -435,7 +436,7 @@ def _flatten_record( items.append( ( new_key, - json.dumps(v, use_decimal=True, default=str) + serialize_json(v) if _should_jsondump_value(k, v, flattened_schema) else v, ), diff --git a/singer_sdk/helpers/_resources.py b/singer_sdk/helpers/_resources.py index 89b3ed269..02f7b30ff 100644 --- a/singer_sdk/helpers/_resources.py +++ b/singer_sdk/helpers/_resources.py @@ -8,7 +8,7 @@ from singer_sdk.helpers._compat import Traversable -if sys.version_info < (3, 9): +if sys.version_info < (3, 10): import importlib_resources else: import importlib.resources as importlib_resources diff --git a/singer_sdk/helpers/_state.py b/singer_sdk/helpers/_state.py index f3a831108..a910bb71e 100644 --- a/singer_sdk/helpers/_state.py +++ b/singer_sdk/helpers/_state.py @@ -11,6 +11,8 @@ if t.TYPE_CHECKING: import datetime + from singer_sdk.helpers import types + _T = t.TypeVar("_T", datetime.datetime, str, int, float) PROGRESS_MARKERS = "progress_markers" @@ -70,7 +72,7 @@ def get_state_partitions_list(tap_state: dict, tap_stream_id: str) -> list[dict] def _find_in_partitions_list( partitions: list[dict], - state_partition_context: dict, + state_partition_context: types.Context, ) -> dict | None: found = [ partition_state @@ -88,7 +90,7 @@ def _find_in_partitions_list( def _create_in_partitions_list( partitions: list[dict], - state_partition_context: dict, + state_partition_context: types.Context, ) -> dict: # Existing partition not found. Creating new state entry in partitions list... new_partition_state = {"context": state_partition_context} @@ -99,7 +101,7 @@ def _create_in_partitions_list( def get_writeable_state_dict( tap_state: dict, tap_stream_id: str, - state_partition_context: dict | None = None, + state_partition_context: types.Context | None = None, ) -> dict: """Return the stream or partition state, creating a new one if it does not exist. @@ -218,6 +220,11 @@ def increment_state( progress_dict = stream_or_partition_state[PROGRESS_MARKERS] old_rk_value = to_json_compatible(progress_dict.get("replication_key_value")) new_rk_value = to_json_compatible(latest_record[replication_key]) + + if new_rk_value is None: + logger.warning("New replication value is null") + return + if old_rk_value is None or not check_sorted or new_rk_value >= old_rk_value: progress_dict["replication_key"] = replication_key progress_dict["replication_key_value"] = new_rk_value @@ -278,8 +285,8 @@ def log_sort_error( ex: Exception, log_fn: t.Callable, stream_name: str, - current_context: dict | None, - state_partition_context: dict | None, + current_context: types.Context | None, + state_partition_context: types.Context | None, record_count: int, partition_record_count: int, ) -> None: diff --git a/singer_sdk/helpers/_typing.py b/singer_sdk/helpers/_typing.py index a4d4f22d9..1e7370fd9 100644 --- a/singer_sdk/helpers/_typing.py +++ b/singer_sdk/helpers/_typing.py @@ -9,8 +9,6 @@ from enum import Enum from functools import lru_cache -import pendulum - _MAX_TIMESTAMP = "9999-12-31 23:59:59.999999" _MAX_TIME = "23:59:59.999999" JSONSCHEMA_ANNOTATION_SECRET = "secret" # noqa: S105 @@ -40,12 +38,10 @@ def __init__(self, *args: object) -> None: def to_json_compatible(val: t.Any) -> t.Any: # noqa: ANN401 - """Return as string if datetime. JSON does not support proper datetime types. - - If given a naive datetime object, pendulum automatically makes it utc - """ - if isinstance(val, (datetime.datetime, pendulum.DateTime)): - return pendulum.instance(val).isoformat("T") + """Return as string if datetime. JSON does not support proper datetime types.""" + if isinstance(val, (datetime.datetime,)): + # Make naive datetimes UTC + return (val.replace(tzinfo=UTC) if val.tzinfo is None else val).isoformat("T") return val @@ -512,10 +508,10 @@ def _conform_primitive_property( # noqa: PLR0911 property_schema: dict, ) -> t.Any: # noqa: ANN401 """Converts a primitive (i.e. not object or array) to a json compatible type.""" - if isinstance(elem, (datetime.datetime, pendulum.DateTime)): + if isinstance(elem, (datetime.datetime,)): return to_json_compatible(elem) if isinstance(elem, datetime.date): - return f"{elem.isoformat()}T00:00:00+00:00" + return elem.isoformat() if isinstance(elem, datetime.timedelta): epoch = datetime.datetime.fromtimestamp(0, UTC) timedelta_from_epoch = epoch + elem diff --git a/singer_sdk/helpers/_util.py b/singer_sdk/helpers/_util.py index d0079c40d..308fd7a30 100644 --- a/singer_sdk/helpers/_util.py +++ b/singer_sdk/helpers/_util.py @@ -2,11 +2,53 @@ from __future__ import annotations -import json +import datetime +import decimal import typing as t from pathlib import Path, PurePath -import pendulum +import simplejson + + +def dump_json(obj: t.Any, **kwargs: t.Any) -> str: # noqa: ANN401 + """Dump json data to a file. + + Args: + obj: A Python object, usually a dict. + **kwargs: Optional key word arguments. + + Returns: + A string of serialized json. + + .. warning:: Do not use this function to serialize Singer messages or bulk data. + Use the functions in ``singer_sdk._singerlib.json`` instead. + """ + return simplejson.dumps( + obj, + use_decimal=True, + separators=(",", ":"), + **kwargs, + ) + + +def load_json(json_str: str, **kwargs: t.Any) -> dict: + """Load json data from a file. + + Args: + json_str: A valid JSON string. + **kwargs: Optional key word arguments. + + Returns: + A Python object, usually a dict. + + .. warning:: Do not use this function to parse Singer messages or bulk data. + Use the functions in ``singer_sdk._singerlib.json`` instead. + """ + return simplejson.loads( # type: ignore[no-any-return] + json_str, + parse_float=decimal.Decimal, + **kwargs, + ) def read_json_file(path: PurePath | str) -> dict[str, t.Any]: @@ -22,9 +64,9 @@ def read_json_file(path: PurePath | str) -> dict[str, t.Any]: msg += f"\nFor more info, please see the sample template at: {template}" raise FileExistsError(msg) - return t.cast(dict, json.loads(Path(path).read_text())) + return load_json(Path(path).read_text(encoding="utf-8")) -def utc_now() -> pendulum.DateTime: +def utc_now() -> datetime.datetime: """Return current time in UTC.""" - return pendulum.now(tz="UTC") + return datetime.datetime.now(datetime.timezone.utc) diff --git a/singer_sdk/helpers/capabilities.py b/singer_sdk/helpers/capabilities.py index f76400c5a..3445c5bc6 100644 --- a/singer_sdk/helpers/capabilities.py +++ b/singer_sdk/helpers/capabilities.py @@ -239,7 +239,7 @@ def emit_warning(self) -> None: class DeprecatedEnumMeta(EnumMeta): """Metaclass for enumeration with deprecation support.""" - def __getitem__(self, name: str) -> t.Any: # noqa: ANN401 + def __getitem__(cls, name: str) -> t.Any: # noqa: ANN401 """Retrieve mapping item. Args: @@ -253,7 +253,7 @@ def __getitem__(self, name: str) -> t.Any: # noqa: ANN401 obj.emit_warning() return obj - def __getattribute__(cls, name: str) -> t.Any: # noqa: ANN401, N805 + def __getattribute__(cls, name: str) -> t.Any: # noqa: ANN401 """Retrieve enum attribute. Args: @@ -267,7 +267,7 @@ def __getattribute__(cls, name: str) -> t.Any: # noqa: ANN401, N805 obj.emit_warning() return obj - def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: # noqa: ANN401 + def __call__(cls, *args: t.Any, **kwargs: t.Any) -> t.Any: # noqa: ANN401 """Call enum member. Args: diff --git a/singer_sdk/helpers/types.py b/singer_sdk/helpers/types.py new file mode 100644 index 000000000..6d8c877ff --- /dev/null +++ b/singer_sdk/helpers/types.py @@ -0,0 +1,28 @@ +"""Type aliases for use in the SDK.""" # noqa: A005 + +from __future__ import annotations + +import sys +import typing as t + +import requests + +if sys.version_info < (3, 9): + from typing import Mapping # noqa: ICN003 +else: + from collections.abc import Mapping + +if sys.version_info < (3, 10): + from typing_extensions import TypeAlias +else: + from typing import TypeAlias # noqa: ICN003 + + +__all__ = [ + "Context", + "Record", +] + +Context: TypeAlias = Mapping[str, t.Any] +Record: TypeAlias = t.Dict[str, t.Any] +Auth: TypeAlias = t.Callable[[requests.PreparedRequest], requests.PreparedRequest] diff --git a/singer_sdk/io_base.py b/singer_sdk/io_base.py index 2c5698e29..f24188b4e 100644 --- a/singer_sdk/io_base.py +++ b/singer_sdk/io_base.py @@ -2,164 +2,18 @@ from __future__ import annotations -import abc -import decimal -import json -import logging -import sys -import typing as t -from collections import Counter, defaultdict - -from singer_sdk._singerlib.messages import Message, SingerMessageType -from singer_sdk._singerlib.messages import format_message as singer_format_message -from singer_sdk._singerlib.messages import write_message as singer_write_message -from singer_sdk.exceptions import InvalidInputLine - -logger = logging.getLogger(__name__) - - -class SingerReader(metaclass=abc.ABCMeta): - """Interface for all plugins reading Singer messages from stdin.""" - - @t.final - def listen(self, file_input: t.IO[str] | None = None) -> None: - """Read from input until all messages are processed. - - Args: - file_input: Readable stream of messages. Defaults to standard in. - - This method is internal to the SDK and should not need to be overridden. - """ - if not file_input: - file_input = sys.stdin - - self._process_lines(file_input) - self._process_endofpipe() - - @staticmethod - def _assert_line_requires(line_dict: dict, requires: set[str]) -> None: - """Check if dictionary . - - Args: - line_dict: TODO - requires: TODO - - Raises: - InvalidInputLine: raised if any required keys are missing - """ - if not requires.issubset(line_dict): - missing = requires - set(line_dict) - msg = f"Line is missing required {', '.join(missing)} key(s): {line_dict}" - raise InvalidInputLine(msg) - - def deserialize_json(self, line: str) -> dict: # noqa: PLR6301 - """Deserialize a line of json. - - Args: - line: A single line of json. - - Returns: - A dictionary of the deserialized json. - - Raises: - json.decoder.JSONDecodeError: raised if any lines are not valid json - """ - try: - return json.loads( # type: ignore[no-any-return] - line, - parse_float=decimal.Decimal, - ) - except json.decoder.JSONDecodeError as exc: - logger.exception("Unable to parse:\n%s", line, exc_info=exc) - raise - - def _process_lines(self, file_input: t.IO[str]) -> t.Counter[str]: - """Internal method to process jsonl lines from a Singer tap. - - Args: - file_input: Readable stream of messages, each on a separate line. - - Returns: - A counter object for the processed lines. - """ - stats: dict[str, int] = defaultdict(int) - for line in file_input: - line_dict = self.deserialize_json(line) - self._assert_line_requires(line_dict, requires={"type"}) - - record_type: SingerMessageType = line_dict["type"] - if record_type == SingerMessageType.SCHEMA: - self._process_schema_message(line_dict) - - elif record_type == SingerMessageType.RECORD: - self._process_record_message(line_dict) - - elif record_type == SingerMessageType.ACTIVATE_VERSION: - self._process_activate_version_message(line_dict) - - elif record_type == SingerMessageType.STATE: - self._process_state_message(line_dict) - - elif record_type == SingerMessageType.BATCH: - self._process_batch_message(line_dict) - - else: - self._process_unknown_message(line_dict) - - stats[record_type] += 1 - - return Counter(**stats) - - @abc.abstractmethod - def _process_schema_message(self, message_dict: dict) -> None: ... - - @abc.abstractmethod - def _process_record_message(self, message_dict: dict) -> None: ... - - @abc.abstractmethod - def _process_state_message(self, message_dict: dict) -> None: ... - - @abc.abstractmethod - def _process_activate_version_message(self, message_dict: dict) -> None: ... - - @abc.abstractmethod - def _process_batch_message(self, message_dict: dict) -> None: ... - - def _process_unknown_message(self, message_dict: dict) -> None: # noqa: PLR6301 - """Internal method to process unknown message types from a Singer tap. - - Args: - message_dict: Dictionary representation of the Singer message. - - Raises: - ValueError: raised if a message type is not recognized - """ - record_type = message_dict["type"] - msg = f"Unknown message type '{record_type}' in message." - raise ValueError(msg) - - def _process_endofpipe(self) -> None: # noqa: PLR6301 - logger.debug("End of pipe reached") - - -class SingerWriter: - """Interface for all plugins writting Singer messages to stdout.""" - - def format_message(self, message: Message) -> str: # noqa: PLR6301 - """Format a message as a JSON string. - - Args: - message: The message to format. - - Returns: - The formatted message. - """ - return singer_format_message(message) - - def write_message(self, message: Message) -> None: # noqa: PLR6301 - """Write a message to stdout. - - Args: - message: The message to write. - """ - singer_write_message(message) +from singer_sdk._singerlib.encoding import ( + GenericSingerReader, + GenericSingerWriter, + SingerMessageType, +) +from singer_sdk._singerlib.encoding import SimpleSingerReader as SingerReader +from singer_sdk._singerlib.encoding import SimpleSingerWriter as SingerWriter + +__all__ = [ + "GenericSingerReader", + "GenericSingerWriter", + "SingerMessageType", + "SingerReader", + "SingerWriter", +] diff --git a/singer_sdk/mapper.py b/singer_sdk/mapper.py index 8d7dd3322..a2e7bc956 100644 --- a/singer_sdk/mapper.py +++ b/singer_sdk/mapper.py @@ -12,6 +12,7 @@ import fnmatch import hashlib import importlib.util +import json import logging import typing as t @@ -306,6 +307,7 @@ def functions(self) -> dict[str, t.Callable]: funcs["md5"] = md5 funcs["datetime"] = datetime funcs["bool"] = bool + funcs["json"] = json return funcs def _eval( @@ -335,7 +337,10 @@ def _eval( names["config"] = self.map_config # Allow map config access within transform if self.fake: + from faker import Faker # noqa: PLC0415 + names["fake"] = self.fake + names["Faker"] = Faker if property_name and property_name in record: # Allow access to original property value if applicable diff --git a/singer_sdk/metrics.py b/singer_sdk/metrics.py index 990285ae0..fb97e693c 100644 --- a/singer_sdk/metrics.py +++ b/singer_sdk/metrics.py @@ -20,8 +20,10 @@ if t.TYPE_CHECKING: from types import TracebackType + from singer_sdk.helpers import types from singer_sdk.helpers._compat import Traversable + DEFAULT_LOG_INTERVAL = 60.0 METRICS_LOGGER_NAME = __name__ METRICS_LOG_LEVEL_SETTING = "metrics_log_level" @@ -45,6 +47,7 @@ class Tag(str, enum.Enum): JOB_TYPE = "job_type" HTTP_STATUS_CODE = "http_status_code" STATUS = "status" + PID = "pid" class Metric(str, enum.Enum): @@ -56,6 +59,7 @@ class Metric(str, enum.Enum): HTTP_REQUEST_COUNT = "http_request_count" JOB_DURATION = "job_duration" SYNC_DURATION = "sync_duration" + BATCH_PROCESSING_TIME = "batch_processing_time" @dataclass @@ -114,10 +118,11 @@ def __init__(self, metric: Metric, tags: dict | None = None) -> None: """ self.metric = metric self.tags = tags or {} + self.tags[Tag.PID] = os.getpid() self.logger = get_metrics_logger() @property - def context(self) -> dict | None: + def context(self) -> types.Context | None: """Get the context for this meter. Returns: @@ -126,7 +131,7 @@ def context(self) -> dict | None: return self.tags.get(Tag.CONTEXT) @context.setter - def context(self, value: dict | None) -> None: + def context(self, value: types.Context | None) -> None: """Set the context for this meter. Args: @@ -180,6 +185,10 @@ def __init__( self.log_interval = log_interval self.last_log_time = time() + def exit(self) -> None: + """Exit the counter context.""" + self._pop() + def __enter__(self) -> Counter: """Enter the counter context. @@ -202,7 +211,7 @@ def __exit__( exc_val: The exception value. exc_tb: The exception traceback. """ - self._pop() + self.exit() def _pop(self) -> None: """Log and reset the counter.""" @@ -392,23 +401,29 @@ def _load_yaml_logging_config(path: Traversable | Path) -> t.Any: # noqa: ANN40 return yaml.safe_load(f) -def _get_default_config() -> t.Any: # noqa: ANN401 +def _get_default_config_path(package: str) -> Traversable: """Get a logging configuration. + Args: + package: The package name to get the logging configuration for. + Returns: A logging configuration. """ - log_config_path = get_package_files("singer_sdk").joinpath("default_logging.yml") - return _load_yaml_logging_config(log_config_path) + filename = "default_logging.yml" + path = get_package_files(package) / filename + return path if path.is_file() else get_package_files("singer_sdk") / filename -def _setup_logging(config: t.Mapping[str, t.Any]) -> None: +def _setup_logging(config: t.Mapping[str, t.Any], *, package: str) -> None: """Setup logging. Args: + package: The package name to get the logging configuration for. config: A plugin configuration dictionary. """ - logging.config.dictConfig(_get_default_config()) + path = _get_default_config_path(package) + logging.config.dictConfig(_load_yaml_logging_config(path)) config = config or {} metrics_log_level = config.get(METRICS_LOG_LEVEL_SETTING, "INFO").upper() diff --git a/singer_sdk/pagination.py b/singer_sdk/pagination.py index 03fe0ab97..9dc630a30 100644 --- a/singer_sdk/pagination.py +++ b/singer_sdk/pagination.py @@ -2,14 +2,20 @@ from __future__ import annotations +import sys import typing as t from abc import ABCMeta, abstractmethod from urllib.parse import ParseResult, urlparse from singer_sdk.helpers.jsonpath import extract_jsonpath +if sys.version_info < (3, 12): + from typing_extensions import override +else: + from typing import override # noqa: ICN003 + if t.TYPE_CHECKING: - from requests import Response + import requests T = t.TypeVar("T") TPageToken = t.TypeVar("TPageToken") @@ -87,7 +93,7 @@ def __repr__(self) -> str: """ return str(self) - def advance(self, response: Response) -> None: + def advance(self, response: requests.Response) -> None: """Get a new page value and advance the current one. Args: @@ -118,7 +124,7 @@ def advance(self, response: Response) -> None: else: self._value = new_value - def has_more(self, response: Response) -> bool: # noqa: ARG002, PLR6301 + def has_more(self, response: requests.Response) -> bool: # noqa: ARG002, PLR6301 """Override this method to check if the endpoint has any pages left. Args: @@ -130,7 +136,7 @@ def has_more(self, response: Response) -> bool: # noqa: ARG002, PLR6301 return True @abstractmethod - def get_next(self, response: Response) -> TPageToken | None: + def get_next(self, response: requests.Response) -> TPageToken | None: """Get the next pagination token or index from the API response. Args: @@ -155,15 +161,12 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: """ super().__init__(None, *args, **kwargs) - def get_next(self, response: Response) -> None: # noqa: ARG002, PLR6301 - """Get the next pagination token or index from the API response. + @override + def get_next(self, response: requests.Response) -> None: + """Always return None to indicate pagination is complete after the first page. Args: response: API response object. - - Returns: - The next page token or index. Return `None` from this method to indicate - the end of pagination. """ return @@ -220,7 +223,7 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: super().__init__(None, *args, **kwargs) @abstractmethod - def get_next_url(self, response: Response) -> str | None: + def get_next_url(self, response: requests.Response) -> str | None: """Override this method to extract a HATEOAS link from the response. Args: @@ -228,7 +231,8 @@ def get_next_url(self, response: Response) -> str | None: """ ... - def get_next(self, response: Response) -> ParseResult | None: + @override + def get_next(self, response: requests.Response) -> ParseResult | None: """Get the next pagination token or index from the API response. Args: @@ -249,7 +253,8 @@ class HeaderLinkPaginator(BaseHATEOASPaginator): - https://datatracker.ietf.org/doc/html/rfc8288#section-3 """ - def get_next_url(self, response: Response) -> str | None: # noqa: PLR6301 + @override + def get_next_url(self, response: requests.Response) -> str | None: """Override this method to extract a HATEOAS link from the response. Args: @@ -281,7 +286,8 @@ def __init__( super().__init__(None, *args, **kwargs) self._jsonpath = jsonpath - def get_next(self, response: Response) -> str | None: + @override + def get_next(self, response: requests.Response) -> str | None: """Get the next page token. Args: @@ -313,7 +319,8 @@ def __init__( super().__init__(None, *args, **kwargs) self._key = key - def get_next(self, response: Response) -> str | None: + @override + def get_next(self, response: requests.Response) -> str | None: """Get the next page token. Args: @@ -328,7 +335,8 @@ def get_next(self, response: Response) -> str | None: class BasePageNumberPaginator(BaseAPIPaginator[int], metaclass=ABCMeta): """Paginator class for APIs that use page number.""" - def get_next(self, response: Response) -> int | None: # noqa: ARG002 + @override + def get_next(self, response: requests.Response) -> int | None: """Get the next page number. Args: @@ -361,7 +369,8 @@ def __init__( super().__init__(start_value, *args, **kwargs) self._page_size = page_size - def get_next(self, response: Response) -> int | None: # noqa: ARG002 + @override + def get_next(self, response: requests.Response) -> int | None: """Get the next page offset. Args: @@ -378,7 +387,7 @@ class LegacyPaginatedStreamProtocol(t.Protocol[TPageToken]): def get_next_page_token( self, - response: Response, + response: requests.Response, previous_token: TPageToken | None, ) -> TPageToken | None: """Get the next page token. @@ -411,7 +420,8 @@ def __init__( super().__init__(None, *args, **kwargs) self.stream = stream - def get_next(self, response: Response) -> TPageToken | None: + @override + def get_next(self, response: requests.Response) -> TPageToken | None: """Get next page value by calling the stream method. Args: diff --git a/singer_sdk/plugin_base.py b/singer_sdk/plugin_base.py index 1564558cf..eb5b39de3 100644 --- a/singer_sdk/plugin_base.py +++ b/singer_sdk/plugin_base.py @@ -13,7 +13,6 @@ from types import MappingProxyType import click -from jsonschema import Draft7Validator from singer_sdk import about, metrics from singer_sdk.cli import plugin_cli @@ -32,11 +31,14 @@ PluginCapabilities, ) from singer_sdk.mapper import PluginMapper -from singer_sdk.typing import extend_validator_with_defaults +from singer_sdk.typing import ( + DEFAULT_JSONSCHEMA_VALIDATOR, + extend_validator_with_defaults, +) SDK_PACKAGE_NAME = "singer_sdk" -JSONSchemaValidator = extend_validator_with_defaults(Draft7Validator) +JSONSchemaValidator = extend_validator_with_defaults(DEFAULT_JSONSCHEMA_VALIDATOR) class MapperNotInitialized(Exception): @@ -162,7 +164,10 @@ def __init__( if self._is_secret_config(k): config_dict[k] = SecretString(v) self._config = config_dict - metrics._setup_logging(self.config) # noqa: SLF001 + metrics._setup_logging( # noqa: SLF001 + self.config, + package=self.__module__.split(".", maxsplit=1)[0], + ) self.metrics_logger = metrics.get_metrics_logger() self._validate_config(raise_errors=validate_config) @@ -226,6 +231,10 @@ def capabilities(self) -> list[CapabilitiesEnum]: # noqa: PLR6301 PluginCapabilities.BATCH, ] + @classproperty + def _env_var_prefix(cls) -> str: # noqa: N805 + return f"{cls.name.upper().replace('-', '_')}_" + @classproperty def _env_var_config(cls) -> dict[str, t.Any]: # noqa: N805 """Return any config specified in environment variables. @@ -236,11 +245,10 @@ def _env_var_config(cls) -> dict[str, t.Any]: # noqa: N805 Returns: Dictionary of configuration parsed from the environment. """ - plugin_env_prefix = f"{cls.name.upper().replace('-', '_')}_" config_jsonschema = cls.config_jsonschema cls.append_builtin_config(config_jsonschema) - return parse_environment_config(config_jsonschema, plugin_env_prefix) + return parse_environment_config(config_jsonschema, cls._env_var_prefix) # Core plugin metadata: @@ -425,6 +433,7 @@ def _get_about_info(cls: type[PluginBase]) -> about.AboutInfo: supported_python_versions=cls.get_supported_python_versions(), capabilities=cls.capabilities, settings=config_jsonschema, + env_var_prefix=cls._env_var_prefix, ) @classmethod diff --git a/singer_sdk/sinks/core.py b/singer_sdk/sinks/core.py index d0a6801b8..5a936a634 100644 --- a/singer_sdk/sinks/core.py +++ b/singer_sdk/sinks/core.py @@ -6,7 +6,6 @@ import copy import datetime import importlib.util -import json import time import typing as t from functools import cached_property @@ -15,8 +14,11 @@ from types import MappingProxyType import jsonschema +import jsonschema.validators from typing_extensions import override +from singer_sdk import metrics +from singer_sdk._singerlib.json import deserialize_json from singer_sdk.exceptions import ( InvalidJSONSchema, InvalidRecord, @@ -38,6 +40,7 @@ get_datelike_property_type, handle_invalid_timestamp_in_record, ) +from singer_sdk.typing import DEFAULT_JSONSCHEMA_VALIDATOR if t.TYPE_CHECKING: from logging import Logger @@ -88,7 +91,10 @@ def __init__( Raises: InvalidJSONSchema: If the schema provided from tap or mapper is invalid. """ - jsonschema_validator = jsonschema.Draft7Validator + jsonschema_validator = jsonschema.validators.validator_for( + schema, + DEFAULT_JSONSCHEMA_VALIDATOR, + ) super().__init__(schema) if validate_formats: @@ -188,6 +194,31 @@ def __init__( ) self._validator: BaseJSONSchemaValidator | None = self.get_validator() + self._record_counter: metrics.Counter = metrics.record_counter(self.stream_name) + self._batch_timer = metrics.Timer( + metrics.Metric.BATCH_PROCESSING_TIME, + tags={ + metrics.Tag.STREAM: self.stream_name, + }, + ) + + @property + def record_counter_metric(self) -> metrics.Counter: + """Get the record counter for this sink. + + Returns: + The Meter instance for the record counter. + """ + return self._record_counter + + @property + def batch_processing_timer(self) -> metrics.Timer: + """Get the batch processing timer for this sink. + + Returns: + The Meter instance for the batch processing timer. + """ + return self._batch_timer @cached_property def validate_schema(self) -> bool: @@ -495,9 +526,9 @@ def _validate_and_parse(self, record: dict) -> dict: try: self._validator.validate(record) except InvalidRecord: + self.logger.exception("Record validation failed") if self.fail_on_record_validation_exception: raise - self.logger.exception("Record validation failed") self._parse_timestamps_in_record( record=record, @@ -680,6 +711,7 @@ def clean_up(self) -> None: should not be relied on, it's recommended to use a uuid as well. """ self.logger.info("Cleaning up %s", self.stream_name) + self.record_counter_metric.exit() def process_batch_files( self, @@ -714,7 +746,9 @@ def process_batch_files( context_file = ( gzip_open(file) if encoding.compression == "gzip" else file ) - context = {"records": [json.loads(line) for line in context_file]} # type: ignore[attr-defined] + context = { + "records": [deserialize_json(line) for line in context_file] # type: ignore[attr-defined] + } self.process_batch(context) elif ( importlib.util.find_spec("pyarrow") diff --git a/singer_sdk/sinks/sql.py b/singer_sdk/sinks/sql.py index f728cd64b..0f7695ef0 100644 --- a/singer_sdk/sinks/sql.py +++ b/singer_sdk/sinks/sql.py @@ -9,25 +9,28 @@ from textwrap import dedent import sqlalchemy as sa -from pendulum import now from sqlalchemy.sql import quoted_name from sqlalchemy.sql.expression import bindparam from singer_sdk.connectors import SQLConnector from singer_sdk.exceptions import ConformedNameClashException from singer_sdk.helpers._conformers import replace_leading_digit +from singer_sdk.helpers._util import utc_now from singer_sdk.sinks.batch import BatchSink if t.TYPE_CHECKING: from sqlalchemy.sql import Executable + from singer_sdk.connectors.sql import FullyQualifiedName from singer_sdk.target_base import Target +_C = t.TypeVar("_C", bound=SQLConnector) -class SQLSink(BatchSink): + +class SQLSink(BatchSink, t.Generic[_C]): """SQL-type sink type.""" - connector_class: type[SQLConnector] + connector_class: type[_C] soft_delete_column_name = "_sdc_deleted_at" version_column_name = "_sdc_table_version" @@ -37,7 +40,7 @@ def __init__( stream_name: str, schema: dict, key_properties: t.Sequence[str] | None, - connector: SQLConnector | None = None, + connector: _C | None = None, ) -> None: """Initialize SQL Sink. @@ -48,12 +51,12 @@ def __init__( key_properties: The primary key columns. connector: Optional connector to reuse. """ - self._connector: SQLConnector + self._connector: _C self._connector = connector or self.connector_class(dict(target.config)) super().__init__(target, stream_name, schema, key_properties) @property - def connector(self) -> SQLConnector: + def connector(self) -> _C: """The connector object. Returns: @@ -107,7 +110,7 @@ def database_name(self) -> str | None: # Assumes single-DB target context. @property - def full_table_name(self) -> str: + def full_table_name(self) -> FullyQualifiedName: """Return the fully qualified table name. Returns: @@ -120,7 +123,7 @@ def full_table_name(self) -> str: ) @property - def full_schema_name(self) -> str: + def full_schema_name(self) -> FullyQualifiedName: """Return the fully qualified schema name. Returns: @@ -267,7 +270,7 @@ def process_batch(self, context: dict) -> None: def generate_insert_statement( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, schema: dict, ) -> str | Executable: """Generate an insert statement for the given records. @@ -295,7 +298,7 @@ def generate_insert_statement( def bulk_insert_records( self, - full_table_name: str, + full_table_name: str | FullyQualifiedName, schema: dict, records: t.Iterable[dict[str, t.Any]], ) -> int | None: @@ -371,7 +374,7 @@ def activate_version(self, new_version: int) -> None: if not self.connector.table_exists(self.full_table_name): return - deleted_at = now() + deleted_at = utc_now() if not self.connector.column_exists( full_table_name=self.full_table_name, diff --git a/singer_sdk/streams/core.py b/singer_sdk/streams/core.py index 886466c5d..3f8ca6a72 100644 --- a/singer_sdk/streams/core.py +++ b/singer_sdk/streams/core.py @@ -6,14 +6,11 @@ import copy import datetime import json -import sys import typing as t from os import PathLike from pathlib import Path from types import MappingProxyType -import pendulum - import singer_sdk._singerlib as singer from singer_sdk import metrics from singer_sdk.batch import Batcher @@ -30,6 +27,7 @@ SDKBatchMessage, ) from singer_sdk.helpers._catalog import pop_deselected_record_properties +from singer_sdk.helpers._compat import datetime_fromisoformat from singer_sdk.helpers._flattening import get_flattening_options from singer_sdk.helpers._state import ( finalize_state_progress_markers, @@ -51,14 +49,10 @@ from singer_sdk.helpers._util import utc_now from singer_sdk.mapper import RemoveRecordTransform, SameRecordTransform, StreamMap -if sys.version_info < (3, 10): - from typing_extensions import TypeAlias -else: - from typing import TypeAlias # noqa: ICN003 - if t.TYPE_CHECKING: import logging + from singer_sdk.helpers import types from singer_sdk.helpers._compat import Traversable from singer_sdk.tap_base import Tap @@ -67,13 +61,15 @@ REPLICATION_INCREMENTAL = "INCREMENTAL" REPLICATION_LOG_BASED = "LOG_BASED" -FactoryType = t.TypeVar("FactoryType", bound="Stream") -Record: TypeAlias = t.Dict[str, t.Any] -Context: TypeAlias = t.Dict - class Stream(metaclass=abc.ABCMeta): # noqa: PLR0904 - """Abstract base class for tap streams.""" + """Abstract base class for tap streams. + + :ivar context: Stream partition or context dictionary. + + .. versionadded:: 0.39.0 + The ``context`` attribute. + """ STATE_MSG_FREQUENCY = 10000 """Number of records between state messages.""" @@ -135,6 +131,8 @@ def __init__( self.logger: logging.Logger = tap.logger.getChild(self.name) self.metrics_logger = tap.metrics_logger self.tap_name: str = tap.name + self.context: types.Context | None = None + self._config: dict = dict(tap.config) self._tap = tap self._tap_state = tap.state @@ -235,7 +233,7 @@ def is_timestamp_replication_key(self) -> bool: def get_starting_replication_key_value( self, - context: Context | None, + context: types.Context | None, ) -> t.Any | None: # noqa: ANN401 """Get starting replication key. @@ -251,6 +249,11 @@ def get_starting_replication_key_value( Returns: Starting replication value. + + .. note:: + + This method requires :attr:`~singer_sdk.Stream.replication_key` to be set + to a non-null value, indicating the stream should be synced incrementally. """ state = self.get_context_state(context) @@ -261,7 +264,8 @@ def get_starting_replication_key_value( ) def get_starting_timestamp( - self, context: Context | None + self, + context: types.Context | None, ) -> datetime.datetime | None: """Get starting replication timestamp. @@ -273,6 +277,11 @@ def get_starting_timestamp( and datetime replication keys. For non-datetime replication keys, use :meth:`~singer_sdk.Stream.get_starting_replication_key_value()` + .. note:: + + This method requires :attr:`~singer_sdk.Stream.replication_key` to be set + to a non-null value, indicating the stream should be synced incrementally. + Args: context: Stream partition or context dictionary. @@ -291,7 +300,8 @@ def get_starting_timestamp( msg = f"The replication key {self.replication_key} is not of timestamp type" raise ValueError(msg) - return t.cast(datetime.datetime, pendulum.parse(value)) + result = datetime_fromisoformat(value) + return result if result.tzinfo else result.replace(tzinfo=datetime.timezone.utc) @property def selected(self) -> bool: @@ -340,7 +350,7 @@ def descendent_streams(self) -> list[Stream]: def _write_replication_key_signpost( self, - context: Context | None, + context: types.Context | None, value: datetime.datetime | str | int | float, ) -> None: """Write the signpost value, if available. @@ -348,9 +358,6 @@ def _write_replication_key_signpost( Args: context: Stream partition or context dictionary. value: TODO - - Returns: - TODO """ if not value: return @@ -377,11 +384,11 @@ def compare_start_date(self, value: str, start_date_value: str) -> str: The most recent value between the bookmark and start date. """ if self.is_timestamp_replication_key: - return max(value, start_date_value, key=pendulum.parse) + return max(value, start_date_value, key=datetime_fromisoformat) return value - def _write_starting_replication_value(self, context: Context | None) -> None: + def _write_starting_replication_value(self, context: types.Context | None) -> None: """Write the starting replication value, if available. Args: @@ -409,7 +416,7 @@ def _write_starting_replication_value(self, context: Context | None) -> None: def get_replication_key_signpost( self, - context: Context | None, # noqa: ARG002 + context: types.Context | None, # noqa: ARG002 ) -> datetime.datetime | t.Any | None: # noqa: ANN401 """Get the replication signpost. @@ -656,7 +663,7 @@ def tap_state(self) -> dict: """ return self._tap_state - def get_context_state(self, context: Context | None) -> dict: + def get_context_state(self, context: types.Context | None) -> dict: """Return a writable state dict for the given context. Gives a partitioned context state if applicable; else returns stream state. @@ -711,7 +718,7 @@ def stream_state(self) -> dict: # Partitions @property - def partitions(self) -> list[Context] | None: + def partitions(self) -> list[types.Context] | None: """Get stream partitions. Developers may override this property to provide a default partitions list. @@ -722,7 +729,7 @@ def partitions(self) -> list[Context] | None: Returns: A list of partition key dicts (if applicable), otherwise `None`. """ - result: list[dict] = [ + result: list[types.Mapping] = [ partition_state["context"] for partition_state in ( get_state_partitions_list(self.tap_state, self.name) or [] @@ -734,9 +741,9 @@ def partitions(self) -> list[Context] | None: def _increment_stream_state( self, - latest_record: Record, + latest_record: types.Record, *, - context: Context | None = None, + context: types.Context | None = None, ) -> None: """Update state of stream or partition with data from the provided record. @@ -827,7 +834,7 @@ def mask(self) -> singer.SelectionMask: def _generate_record_messages( self, - record: Record, + record: types.Record, ) -> t.Generator[singer.RecordMessage, None, None]: """Write out a RECORD message. @@ -856,7 +863,7 @@ def _generate_record_messages( time_extracted=utc_now(), ) - def _write_record_message(self, record: Record) -> None: + def _write_record_message(self, record: types.Record) -> None: """Write out a RECORD message. Args: @@ -973,7 +980,7 @@ def reset_state_progress_markers(self, state: dict | None = None) -> None: state: State object to promote progress markers with. """ if state is None or state == {}: - context: Context | None + context: types.Context | None for context in self.partitions or [{}]: state = self.get_context_state(context or None) reset_state_progress_markers(state) @@ -1002,7 +1009,7 @@ def finalize_state_progress_markers(self, state: dict | None = None) -> None: for child_stream in self.child_streams or []: child_stream.finalize_state_progress_markers() - context: Context | None + context: types.Context | None for context in self.partitions or [{}]: state = self.get_context_state(context or None) self._finalize_state(state) @@ -1015,9 +1022,9 @@ def finalize_state_progress_markers(self, state: dict | None = None) -> None: def _process_record( self, - record: Record, - child_context: Context | None = None, - partition_context: Context | None = None, + record: types.Record, + child_context: types.Context | None = None, + partition_context: types.Context | None = None, ) -> None: """Process a record. @@ -1042,7 +1049,7 @@ def _process_record( def _sync_records( # noqa: C901 self, - context: Context | None = None, + context: types.Context | None = None, *, write_messages: bool = True, ) -> t.Generator[dict, t.Any, t.Any]: @@ -1064,8 +1071,8 @@ def _sync_records( # noqa: C901 timer = metrics.sync_timer(self.name) record_index = 0 - context_element: Context | None - context_list: list[dict] | None + context_element: types.Context | None + context_list: list[types.Context] | None context_list = [context] if context is not None else self.partitions selected = self.selected @@ -1080,7 +1087,7 @@ def _sync_records( # noqa: C901 current_context, ) self._write_starting_replication_value(current_context) - child_context: Context | None = ( + child_context: types.Context | None = ( None if current_context is None else copy.copy(current_context) ) @@ -1141,7 +1148,7 @@ def _sync_records( # noqa: C901 def _sync_batches( self, batch_config: BatchConfig, - context: Context | None = None, + context: types.Context | None = None, ) -> None: """Sync batches, emitting BATCH messages. @@ -1158,21 +1165,19 @@ def _sync_batches( # Public methods ("final", not recommended to be overridden) @t.final - def sync(self, context: Context | None = None) -> None: + def sync(self, context: types.Context | None = None) -> None: """Sync this stream. This method is internal to the SDK and should not need to be overridden. Args: context: Stream partition or context dictionary. - - Raises: - Exception: Any exception raised by the sync process. """ msg = f"Beginning {self.replication_method.lower()} sync of '{self.name}'" if context: msg += f" with context: {context}" self.logger.info("%s...", msg) + self.context = MappingProxyType(context) if context else None # Use a replication signpost, if available signpost = self.get_replication_key_signpost(context) @@ -1198,7 +1203,7 @@ def sync(self, context: Context | None = None) -> None: ) raise - def _sync_children(self, child_context: Context | None) -> None: + def _sync_children(self, child_context: types.Context | None) -> None: if child_context is None: self.logger.warning( "Context for child streams of '%s' is null, " @@ -1233,7 +1238,10 @@ def apply_catalog(self, catalog: singer.Catalog) -> None: if catalog_entry.replication_method: self.forced_replication_method = catalog_entry.replication_method - def _get_state_partition_context(self, context: Context | None) -> dict | None: + def _get_state_partition_context( + self, + context: types.Context | None, + ) -> types.Context | None: """Override state handling if Stream.state_partitioning_keys is specified. Args: @@ -1252,9 +1260,9 @@ def _get_state_partition_context(self, context: Context | None) -> dict | None: def get_child_context( self, - record: Record, - context: Context | None, - ) -> dict | None: + record: types.Record, + context: types.Context | None, + ) -> types.Context | None: """Return a child context object from the record and optional provided context. By default, will return context if provided and otherwise the record dict. @@ -1295,9 +1303,9 @@ def get_child_context( def generate_child_contexts( self, - record: Record, - context: Context | None, - ) -> t.Iterable[dict | None]: + record: types.Record, + context: types.Context | None, + ) -> t.Iterable[types.Context | None]: """Generate child contexts. Args: @@ -1314,7 +1322,7 @@ def generate_child_contexts( @abc.abstractmethod def get_records( self, - context: Context | None, + context: types.Context | None, ) -> t.Iterable[dict | tuple[dict, dict | None]]: """Abstract record generator function. Must be overridden by the child class. @@ -1360,7 +1368,7 @@ def get_batch_config(self, config: t.Mapping) -> BatchConfig | None: # noqa: PL def get_batches( self, batch_config: BatchConfig, - context: Context | None = None, + context: types.Context | None = None, ) -> t.Iterable[tuple[BaseBatchFileEncoding, list[str]]]: """Batch generator function. @@ -1385,8 +1393,8 @@ def get_batches( def post_process( # noqa: PLR6301 self, - row: Record, - context: Context | None = None, # noqa: ARG002 + row: types.Record, + context: types.Context | None = None, # noqa: ARG002 ) -> dict | None: """As needed, append or transform raw data to match expected structure. diff --git a/singer_sdk/streams/graphql.py b/singer_sdk/streams/graphql.py index 04a2e80d6..4e5455bc3 100644 --- a/singer_sdk/streams/graphql.py +++ b/singer_sdk/streams/graphql.py @@ -9,7 +9,7 @@ from singer_sdk.streams.rest import RESTStream if t.TYPE_CHECKING: - from singer_sdk.streams.core import Context + from singer_sdk.helpers.types import Context _TToken = t.TypeVar("_TToken") diff --git a/singer_sdk/streams/rest.py b/singer_sdk/streams/rest.py index e96537246..76e2abf5d 100644 --- a/singer_sdk/streams/rest.py +++ b/singer_sdk/streams/rest.py @@ -6,6 +6,7 @@ import copy import logging import typing as t +from functools import cached_property from http import HTTPStatus from urllib.parse import urlparse from warnings import warn @@ -26,25 +27,18 @@ from singer_sdk.streams.core import Stream if t.TYPE_CHECKING: - import sys from datetime import datetime from backoff.types import Details from singer_sdk._singerlib import Schema - from singer_sdk.streams.core import Context + from singer_sdk.helpers.types import Auth, Context from singer_sdk.tap_base import Tap - if sys.version_info >= (3, 10): - from typing import TypeAlias # noqa: ICN003 - else: - from typing_extensions import TypeAlias - DEFAULT_PAGE_SIZE = 1000 DEFAULT_REQUEST_TIMEOUT = 300 # 5 minutes _TToken = t.TypeVar("_TToken") -_Auth: TypeAlias = t.Callable[[requests.PreparedRequest], requests.PreparedRequest] class RESTStream(Stream, t.Generic[_TToken], metaclass=abc.ABCMeta): # noqa: PLR0904 @@ -66,6 +60,9 @@ class RESTStream(Stream, t.Generic[_TToken], metaclass=abc.ABCMeta): # noqa: PL #: Example: `"$.next_page"` next_page_token_jsonpath: str | None = None + #: Optional flag to disable HTTP redirects. Defaults to False. + allow_redirects: bool = True + # Private constants. May not be supported in future releases: _LOG_REQUEST_METRICS: bool = True # Disabled by default for safety: @@ -74,7 +71,17 @@ class RESTStream(Stream, t.Generic[_TToken], metaclass=abc.ABCMeta): # noqa: PL @property @abc.abstractmethod def url_base(self) -> str: - """Return the base url, e.g. ``https://api.mysite.com/v3/``.""" + """The base request URL, e.g. ``https://api.mysite.com/v3/``. + + Request URLs are generated by combining `url_base` and `path`, and expanding any + context variables in the path. + + For example, if ``url_base`` is ``https://api.mysite.com/v3/`` and ``path`` is + ``users/{user_id}/orders``, then if the stream has a context of + ``{"user_id": 123}`` generated by its parent stream with + :meth:`~singer_sdk.Stream.generate_child_contexts`, the full URL will be + ``https://api.mysite.com/v3/users/123/orders``. + """ def __init__( self, @@ -94,7 +101,7 @@ def __init__( super().__init__(name=name, schema=schema, tap=tap) if path: self.path = path - self._http_headers: dict = {} + self._http_headers: dict = {"User-Agent": self.user_agent} self._requests_session = requests.Session() self._compiled_jsonpath = None self._next_page_token_compiled_jsonpath = None @@ -144,6 +151,18 @@ def requests_session(self) -> requests.Session: self._requests_session = requests.Session() return self._requests_session + @cached_property + def user_agent(self) -> str: + """Get the user agent string for the stream. + + Returns: + The user agent string. + """ + return self.config.get( + "user_agent", + f"{self.tap_name}/{self._tap.plugin_version}", + ) + def validate_response(self, response: requests.Response) -> None: """Validate HTTP response. @@ -257,7 +276,11 @@ def _request( Returns: TODO """ - response = self.requests_session.send(prepared_request, timeout=self.timeout) + response = self.requests_session.send( + prepared_request, + timeout=self.timeout, + allow_redirects=self.allow_redirects, + ) self._write_request_duration_log( endpoint=self.path, response=response, @@ -543,10 +566,7 @@ def http_headers(self) -> dict: Returns: Dictionary of HTTP headers to use as a base for every request. """ - result = self._http_headers - if "user_agent" in self.config: - result["User-Agent"] = self.config.get("user_agent") - return result + return self._http_headers @property def timeout(self) -> int: @@ -593,7 +613,7 @@ def parse_response(self, response: requests.Response) -> t.Iterable[dict]: # Abstract methods: @property - def authenticator(self) -> _Auth: + def authenticator(self) -> Auth: """Return or set the authenticator for managing HTTP auth headers. If an authenticator is not specified, REST-based taps will simply pass diff --git a/singer_sdk/streams/sql.py b/singer_sdk/streams/sql.py index 2b610a2a5..2877a505b 100644 --- a/singer_sdk/streams/sql.py +++ b/singer_sdk/streams/sql.py @@ -11,10 +11,11 @@ import singer_sdk.helpers._catalog as catalog from singer_sdk._singerlib import CatalogEntry, MetadataMapping from singer_sdk.connectors import SQLConnector -from singer_sdk.streams.core import Stream +from singer_sdk.streams.core import REPLICATION_INCREMENTAL, Stream if t.TYPE_CHECKING: - from singer_sdk.streams.core import Context + from singer_sdk.connectors.sql import FullyQualifiedName + from singer_sdk.helpers.types import Context from singer_sdk.tap_base import Tap @@ -124,7 +125,7 @@ def primary_keys(self, new_value: t.Sequence[str]) -> None: self._singer_catalog_entry.metadata.root.table_key_properties = new_value @property - def fully_qualified_name(self) -> str: + def fully_qualified_name(self) -> FullyQualifiedName: """Generate the fully qualified version of the table name. Raises: @@ -227,7 +228,7 @@ def is_sorted(self) -> bool: Returns: `True` if stream is sorted. Defaults to `False`. """ - return self.replication_key is not None + return self.replication_method == REPLICATION_INCREMENTAL __all__ = ["SQLConnector", "SQLStream"] diff --git a/singer_sdk/tap_base.py b/singer_sdk/tap_base.py index d8fb75a8f..d69fa5f38 100644 --- a/singer_sdk/tap_base.py +++ b/singer_sdk/tap_base.py @@ -4,7 +4,6 @@ import abc import contextlib -import json import typing as t from enum import Enum @@ -20,7 +19,7 @@ from singer_sdk.helpers import _state from singer_sdk.helpers._classproperty import classproperty from singer_sdk.helpers._state import write_stream_state -from singer_sdk.helpers._util import read_json_file +from singer_sdk.helpers._util import dump_json, read_json_file from singer_sdk.helpers.capabilities import ( BATCH_CONFIG, CapabilitiesEnum, @@ -312,7 +311,7 @@ def catalog_json_text(self) -> str: Returns: The tap's catalog as formatted JSON text. """ - return json.dumps(self.catalog_dict, indent=2) + return dump_json(self.catalog_dict, indent=2) @property def _singer_catalog(self) -> Catalog: diff --git a/singer_sdk/target_base.py b/singer_sdk/target_base.py index d560a555e..22ad28176 100644 --- a/singer_sdk/target_base.py +++ b/singer_sdk/target_base.py @@ -242,9 +242,6 @@ def add_sink( Returns: A new sink for the stream. - - Raises: - Exception: If sink setup fails. """ self.logger.info("Initializing '%s' target sink...", self.name) sink_class = self.get_sink_class(stream_name=stream_name) @@ -334,7 +331,8 @@ def _process_record_message(self, message_dict: dict) -> None: self._assert_line_requires(message_dict, requires={"stream", "record"}) stream_name = message_dict["stream"] - self._assert_sink_exists(stream_name) + if stream_name not in self.mapper.stream_maps: + self._assert_sink_exists(stream_name) for stream_map in self.mapper.stream_maps[stream_name]: raw_record = copy.copy(message_dict["record"]) @@ -343,6 +341,7 @@ def _process_record_message(self, message_dict: dict) -> None: # Record was filtered out by the map transform continue + self._assert_sink_exists(stream_map.stream_alias) sink = self.get_sink(stream_map.stream_alias, record=transformed_record) context = sink._get_context(transformed_record) # noqa: SLF001 if sink.include_sdc_metadata_properties: @@ -360,6 +359,7 @@ def _process_record_message(self, message_dict: dict) -> None: sink.tally_record_read() sink.process_record(transformed_record, context) + sink.record_counter_metric.increment() sink._after_process_record(context) # noqa: SLF001 if sink.is_full: @@ -451,8 +451,10 @@ def _process_activate_version_message(self, message_dict: dict) -> None: message_dict: TODO """ stream_name = message_dict["stream"] - sink = self.get_sink(stream_name) - sink.activate_version(message_dict["version"]) + + for stream_map in self.mapper.stream_maps[stream_name]: + sink = self.get_sink(stream_map.stream_alias) + sink.activate_version(message_dict["version"]) def _process_batch_message(self, message_dict: dict) -> None: """Handle the optional BATCH message extension. @@ -509,7 +511,8 @@ def drain_one(self, sink: Sink) -> None: # noqa: PLR6301 return draining_status = sink.start_drain() - sink.process_batch(draining_status) + with sink.batch_processing_timer: + sink.process_batch(draining_status) sink.mark_drained() def _drain_all(self, sink_list: list[Sink], parallelism: int) -> None: diff --git a/singer_sdk/testing/suites.py b/singer_sdk/testing/suites.py index df93c86d2..fd53e6e19 100644 --- a/singer_sdk/testing/suites.py +++ b/singer_sdk/testing/suites.py @@ -17,6 +17,7 @@ StreamRecordMatchesStreamSchema, StreamRecordSchemaMatchesCatalogTest, StreamReturnsRecordTest, + StreamSchemaIsValidTest, TapCLIPrintsTest, TapDiscoveryTest, TapStreamConnectionTest, @@ -72,6 +73,7 @@ class TestSuite(t.Generic[T]): StreamRecordMatchesStreamSchema, StreamRecordSchemaMatchesCatalogTest, StreamReturnsRecordTest, + StreamSchemaIsValidTest, StreamPrimaryKeysTest, ], ) diff --git a/singer_sdk/testing/tap_tests.py b/singer_sdk/testing/tap_tests.py index e5b0efc42..5839e0cea 100644 --- a/singer_sdk/testing/tap_tests.py +++ b/singer_sdk/testing/tap_tests.py @@ -5,11 +5,13 @@ import typing as t import warnings -from jsonschema import Draft7Validator +from jsonschema import validators +from jsonschema.exceptions import SchemaError import singer_sdk.helpers._typing as th from singer_sdk import Tap from singer_sdk.helpers._compat import datetime_fromisoformat +from singer_sdk.typing import DEFAULT_JSONSCHEMA_VALIDATOR from .templates import AttributeTestTemplate, StreamTestTemplate, TapTestTemplate @@ -71,6 +73,28 @@ def test(self) -> None: assert "progress_markers" not in final_state, self.message +class StreamSchemaIsValidTest(StreamTestTemplate): + """Test that a stream's schema is valid.""" + + name = "schema_is_valid" + + def test(self) -> None: + """Run test. + + Raises: + AssertionError: if schema is not valid. + """ + schema = self.stream.schema + default = DEFAULT_JSONSCHEMA_VALIDATOR + validator = validators.validator_for(schema, default=default) + + try: + validator.check_schema(schema) + except SchemaError as e: # pragma: no cover + msg = f"Schema is not valid: {e}" + raise AssertionError(msg) from e + + class StreamReturnsRecordTest(StreamTestTemplate): """Test that a stream sync returns at least 1 record.""" @@ -134,10 +158,10 @@ class StreamRecordMatchesStreamSchema(StreamTestTemplate): def test(self) -> None: """Run test.""" schema = self.stream.schema - validator = Draft7Validator( - schema, - format_checker=Draft7Validator.FORMAT_CHECKER, - ) + default = DEFAULT_JSONSCHEMA_VALIDATOR + validator = validators.validator_for(schema, default=default)(schema) + validator.format_checker = default.FORMAT_CHECKER + for record in self.stream_records: errors = list(validator.iter_errors(record)) error_messages = "\n".join( diff --git a/singer_sdk/testing/target_test_streams/camelcase_complex_schema.singer b/singer_sdk/testing/target_test_streams/camelcase_complex_schema.singer index 03330c724..8c5d66fb6 100644 --- a/singer_sdk/testing/target_test_streams/camelcase_complex_schema.singer +++ b/singer_sdk/testing/target_test_streams/camelcase_complex_schema.singer @@ -1,2 +1,2 @@ {"type": "SCHEMA", "stream": "ForecastingTypeToCategory", "schema": {"properties": {"Id": {"type": "string"}, "IsDeleted": {"type": ["null", "boolean"]}, "CreatedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "CreatedById": {"type": ["null", "string"]}, "LastModifiedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "LastModifiedById": {"type": ["null", "string"]}, "SystemModstamp": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "ForecastingTypeId": {"type": ["null", "string"]}, "ForecastingItemCategory": {"type": ["null", "string"]}, "DisplayPosition": {"type": ["null", "integer"]}, "IsAdjustable": {"type": ["null", "boolean"]}, "IsOwnerAdjustable": {"type": ["null", "boolean"]}}, "type": "object", "additionalProperties": false}, "key_properties": ["Id"]} -{"type": "SCHEMA", "stream": "ForecastingTypeToCategory", "schema": {"properties": {"Id": {"type": "string"}, "IsDeleted": {"type": ["null", "boolean"]}, "CreatedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "CreatedById": {"type": ["null", "string"]}, "LastModifiedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "LastModifiedById": {"type": ["null", "string"]}, "SystemModstamp": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "ForecastingTypeId": {"type": ["null", "string"]}, "ForecastingItemCategory": {"type": ["null", "string"]}, "DisplayPosition": {"type": ["null", "integer"]}, "IsAdjustable": {"type": ["null", "boolean"]}, "IsOwnerAdjustable": {"type": ["null", "boolean"]}, "age": {"type": "integer"}, "NewCamelCasedAttribute": {"type": "string"}}, "type": "object", "additionalProperties": false}, "key_properties": ["Id"]} +{"type": "SCHEMA", "stream": "ForecastingTypeToCategory", "schema": {"properties": {"Id": {"type": "string"}, "IsDeleted": {"type": ["null", "boolean"]}, "CreatedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "CreatedById": {"type": ["null", "string"]}, "LastModifiedDate": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "LastModifiedById": {"type": ["null", "string"]}, "SystemModstamp": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": ["string", "null"]}]}, "ForecastingTypeId": {"type": ["null", "string"]}, "ForecastingItemCategory": {"type": ["null", "string"]}, "DisplayPosition": {"type": ["null", "integer"]}, "IsAdjustable": {"type": ["null", "boolean"]}, "IsOwnerAdjustable": {"type": ["null", "boolean"]}, "age": {"type": "integer"}, "NewCamelCasedAttribute": {"type": "string"}, "_attribute_startswith_underscore": {"type": "string"}}, "type": "object", "additionalProperties": false}, "key_properties": ["Id"]} diff --git a/singer_sdk/typing.py b/singer_sdk/typing.py index ca4d917df..6bf8d9527 100644 --- a/singer_sdk/typing.py +++ b/singer_sdk/typing.py @@ -48,7 +48,7 @@ other valid implementations which are not syntactically identical to those generated here. -""" +""" # noqa: A005 from __future__ import annotations @@ -58,9 +58,6 @@ import sqlalchemy as sa from jsonschema import ValidationError, validators -if t.TYPE_CHECKING: - from jsonschema.protocols import Validator - from singer_sdk.helpers._typing import ( JSONSCHEMA_ANNOTATION_SECRET, JSONSCHEMA_ANNOTATION_WRITEONLY, @@ -71,6 +68,8 @@ if t.TYPE_CHECKING: import sys + from jsonschema.protocols import Validator + if sys.version_info >= (3, 10): from typing import TypeAlias # noqa: ICN003 else: @@ -78,6 +77,7 @@ __all__ = [ + "DEFAULT_JSONSCHEMA_VALIDATOR", "ArrayType", "BooleanType", "CustomType", @@ -118,11 +118,13 @@ None, ] +DEFAULT_JSONSCHEMA_VALIDATOR: type[Validator] = validators.Draft7Validator # type: ignore[assignment] + T = t.TypeVar("T", bound=_JsonValue) P = t.TypeVar("P") -def extend_validator_with_defaults(validator_class): # noqa: ANN001, ANN201 +def extend_validator_with_defaults(validator_class: type[Validator]): # noqa: ANN201 """Fill in defaults, before validating with the provided JSON Schema Validator. See @@ -612,7 +614,7 @@ class Property(JSONTypeHelper[T], t.Generic[T]): """Generic Property. Should be nested within a `PropertiesList`.""" # TODO: Make some of these arguments keyword-only. This is a breaking change. - def __init__( # noqa: PLR0913 + def __init__( self, name: str, wrapped: JSONTypeHelper[T] | type[JSONTypeHelper[T]], diff --git a/tests/_singerlib/test_catalog.py b/tests/_singerlib/test_catalog.py index 69f377137..41efd8063 100644 --- a/tests/_singerlib/test_catalog.py +++ b/tests/_singerlib/test_catalog.py @@ -86,19 +86,19 @@ def test_selection_mask(): assert mask[()] is True # Explicitly deselected - assert mask[("properties", "id")] is False + assert mask["properties", "id"] is False # Missing defaults to parent selection - assert mask[("properties", "name")] is True + assert mask["properties", "name"] is True # Explicitly selected - assert mask[("properties", "an_object")] is False + assert mask["properties", "an_object"] is False # Missing defaults to parent selection - assert mask[("properties", "an_object", "properties", "id")] is False + assert mask["properties", "an_object", "properties", "id"] is False # Explicitly selected nested property - assert mask[("properties", "an_object", "properties", "a_string")] is True + assert mask["properties", "an_object", "properties", "a_string"] is True def test_metadata_mapping(): @@ -112,27 +112,27 @@ def test_metadata_mapping(): forced_replication_method="FULL_TABLE", ) ) - assert mapping[("properties", "id")] == Metadata( + assert mapping["properties", "id"] == Metadata( inclusion=Metadata.InclusionType.AUTOMATIC, selected=True, ) - assert mapping[("properties", "name")] == Metadata( + assert mapping["properties", "name"] == Metadata( inclusion=Metadata.InclusionType.AVAILABLE, selected=True, ) - assert mapping[("properties", "missing")] == Metadata() + assert mapping["properties", "missing"] == Metadata() selection_mask = mapping.resolve_selection() assert selection_mask[()] is True - assert selection_mask[("properties", "id")] is True - assert selection_mask[("properties", "updated_at")] is True - assert selection_mask[("properties", "name")] is True - assert selection_mask[("properties", "missing")] is True - assert selection_mask[("properties", "an_object")] is False - assert selection_mask[("properties", "an_object", "properties", "nested")] is False - assert selection_mask[("properties", "not_supported_selected")] is False - assert selection_mask[("properties", "not_supported_not_selected")] is False - assert selection_mask[("properties", "selected_by_default")] is True + assert selection_mask["properties", "id"] is True + assert selection_mask["properties", "updated_at"] is True + assert selection_mask["properties", "name"] is True + assert selection_mask["properties", "missing"] is True + assert selection_mask["properties", "an_object"] is False + assert selection_mask["properties", "an_object", "properties", "nested"] is False + assert selection_mask["properties", "not_supported_selected"] is False + assert selection_mask["properties", "not_supported_not_selected"] is False + assert selection_mask["properties", "selected_by_default"] is True def test_empty_metadata_mapping(): @@ -264,11 +264,11 @@ def test_standard_metadata( assert stream_metadata.schema_name == schema_name for pk in key_properties: - pk_metadata = metadata[("properties", pk)] + pk_metadata = metadata["properties", pk] assert pk_metadata.inclusion == Metadata.InclusionType.AUTOMATIC assert pk_metadata.selected is None for rk in valid_replication_keys or []: - rk_metadata = metadata[("properties", rk)] + rk_metadata = metadata["properties", rk] assert rk_metadata.inclusion == Metadata.InclusionType.AUTOMATIC assert rk_metadata.selected is None diff --git a/tests/_singerlib/test_messages.py b/tests/_singerlib/test_messages.py index 491573545..f9cd73bbe 100644 --- a/tests/_singerlib/test_messages.py +++ b/tests/_singerlib/test_messages.py @@ -8,7 +8,6 @@ from pytz import timezone import singer_sdk._singerlib as singer -from singer_sdk.io_base import SingerWriter UTC = datetime.timezone.utc @@ -19,24 +18,22 @@ def test_exclude_null_dict(): def test_format_message(): - singerwriter = SingerWriter() message = singer.RecordMessage( stream="test", record={"id": 1, "name": "test"}, ) - assert singerwriter.format_message(message) == ( + assert singer.format_message(message) == ( '{"type":"RECORD","stream":"test","record":{"id":1,"name":"test"}}' ) def test_write_message(): - singerwriter = SingerWriter() message = singer.RecordMessage( stream="test", record={"id": 1, "name": "test"}, ) with redirect_stdout(io.StringIO()) as out: - singerwriter.write_message(message) + singer.write_message(message) assert out.getvalue() == ( '{"type":"RECORD","stream":"test","record":{"id":1,"name":"test"}}\n' @@ -96,6 +93,21 @@ def test_record_message_time_extracted_to_utc(): assert record.time_extracted == datetime.datetime(2021, 1, 1, 9, tzinfo=UTC) +def test_record_message_with_version(): + record = singer.RecordMessage( + stream="test", + record={"id": 1, "name": "test"}, + version=1614556800, + ) + assert record.version == 1614556800 + assert record.to_dict() == { + "type": "RECORD", + "stream": "test", + "record": {"id": 1, "name": "test"}, + "version": 1614556800, + } + + def test_schema_message(): schema = singer.SchemaMessage( stream="test", diff --git a/tests/conftest.py b/tests/conftest.py index d2961722f..0b5bc4b74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -138,7 +138,7 @@ class SQLConnectorMock(SQLConnector): """A Mock SQLConnector class.""" -class SQLSinkMock(SQLSink): +class SQLSinkMock(SQLSink[SQLConnectorMock]): """A mock Sink class.""" name = "sql-sink-mock" diff --git a/tests/core/configuration/test_dict_config.py b/tests/core/configuration/test_dict_config.py index 62b525303..136b72e72 100644 --- a/tests/core/configuration/test_dict_config.py +++ b/tests/core/configuration/test_dict_config.py @@ -21,7 +21,7 @@ @pytest.fixture def config_file1(tmpdir) -> str: filepath: str = tmpdir.join("file1.json") - with Path(filepath).open("w") as f: + with Path(filepath).open("w", encoding="utf-8") as f: json.dump({"prop2": "from-file-1"}, f) return filepath @@ -30,7 +30,7 @@ def config_file1(tmpdir) -> str: @pytest.fixture def config_file2(tmpdir) -> str: filepath: str = tmpdir.join("file2.json") - with Path(filepath).open("w") as f: + with Path(filepath).open("w", encoding="utf-8") as f: json.dump({"prop3": ["from-file-2"]}, f) return filepath diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 97eb76e7f..30798b01c 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -5,11 +5,11 @@ import typing as t from contextlib import contextmanager -import pendulum import pytest from typing_extensions import override from singer_sdk import Stream, Tap +from singer_sdk.helpers._compat import datetime_fromisoformat from singer_sdk.typing import ( DateTimeType, IntegerType, @@ -70,7 +70,7 @@ class UnixTimestampIncrementalStream2(UnixTimestampIncrementalStream): def compare_start_date(self, value: str, start_date_value: str) -> str: """Compare a value to a start date value.""" - start_timestamp = pendulum.parse(start_date_value).format("X") + start_timestamp = datetime_fromisoformat(start_date_value).timestamp() return max(value, start_timestamp, key=float) diff --git a/tests/core/sinks/test_sql_sink.py b/tests/core/sinks/test_sql_sink.py new file mode 100644 index 000000000..c20be40f6 --- /dev/null +++ b/tests/core/sinks/test_sql_sink.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import typing as t +from textwrap import dedent + +import pytest + +from samples.sample_duckdb import DuckDBConnector +from singer_sdk.sinks.sql import SQLSink +from singer_sdk.target_base import SQLTarget + + +class DuckDBSink(SQLSink): + connector_class = DuckDBConnector + + +class DuckDBTarget(SQLTarget): + """DuckDB target class.""" + + name = "sql-target-mock" + config_jsonschema: t.ClassVar[dict] = {"type": "object", "properties": {}} + default_sink_class = DuckDBSink + + +class TestDuckDBSink: + @pytest.fixture + def target(self) -> DuckDBTarget: + return DuckDBTarget(config={"sqlalchemy_url": "duckdb:///"}) + + @pytest.fixture + def schema(self) -> dict: + return { + "properties": { + "id": { + "type": ["string", "null"], + }, + "col_ts": { + "format": "date-time", + "type": ["string", "null"], + }, + "table": { + "type": ["string", "null"], + }, + }, + } + + @pytest.fixture + def sink(self, target: DuckDBTarget, schema: dict) -> DuckDBSink: + return DuckDBSink( + target, + stream_name="foo", + schema=schema, + key_properties=["id"], + ) + + def test_generate_insert_statement(self, sink: DuckDBSink, schema: dict): + """Test that the insert statement is generated correctly.""" + expected = dedent( + """\ + INSERT INTO foo + (id, col_ts, "table") + VALUES (:id, :col_ts, :table)""" + ) + assert sink.generate_insert_statement("foo", schema=schema) == expected diff --git a/tests/core/sinks/test_validation.py b/tests/core/sinks/test_validation.py index c6a05ced1..f8df7f775 100644 --- a/tests/core/sinks/test_validation.py +++ b/tests/core/sinks/test_validation.py @@ -121,8 +121,8 @@ def test_validate_fastjsonschema(): @pytest.fixture -def draft7_sink_stop(): - """Return a sink object with Draft7 checks enabled.""" +def default_draft_sink_stop(): + """Return a sink object with the default draft checks enabled.""" class CustomSink(BatchSinkMock): """Custom sink class.""" @@ -147,8 +147,8 @@ class CustomSink(BatchSinkMock): @pytest.fixture -def draft7_sink_continue(): - """Return a sink object with Draft7 checks enabled.""" +def default_draft_sink_continue(): + """Return a sink object with the default draft checks enabled.""" class CustomSink(BatchSinkMock): """Custom sink class.""" @@ -174,9 +174,9 @@ class CustomSink(BatchSinkMock): def test_validate_record_jsonschema_format_checking_enabled_stop_on_error( - draft7_sink_stop, + default_draft_sink_stop, ): - sink: BatchSinkMock = draft7_sink_stop + sink: BatchSinkMock = default_draft_sink_stop record = { "id": 1, @@ -195,9 +195,9 @@ def test_validate_record_jsonschema_format_checking_enabled_stop_on_error( def test_validate_record_jsonschema_format_checking_enabled_continue_on_error( capsys: pytest.CaptureFixture, - draft7_sink_continue, + default_draft_sink_continue, ): - sink: BatchSinkMock = draft7_sink_continue + sink: BatchSinkMock = default_draft_sink_continue record = { "id": 1, diff --git a/tests/core/test_about.py b/tests/core/test_about.py index 6836615f2..70a445bb1 100644 --- a/tests/core/test_about.py +++ b/tests/core/test_about.py @@ -36,7 +36,7 @@ def about_info() -> AboutInfo: description="Example tap for Singer SDK", version="0.1.1", sdk_version="1.0.0", - supported_python_versions=["3.6", "3.7", "3.8"], + supported_python_versions=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], capabilities=[ TapCapabilities.CATALOG, TapCapabilities.DISCOVER, @@ -66,10 +66,11 @@ def about_info() -> AboutInfo: }, "required": ["api_key"], }, + env_var_prefix="TAP_EXAMPLE_", ) -@pytest.mark.snapshot() +@pytest.mark.snapshot @pytest.mark.parametrize( "about_format", [ diff --git a/tests/core/test_connector_sql.py b/tests/core/test_connector_sql.py index 43fc9c55f..c8390f33d 100644 --- a/tests/core/test_connector_sql.py +++ b/tests/core/test_connector_sql.py @@ -7,8 +7,11 @@ import pytest import sqlalchemy as sa from sqlalchemy.dialects import registry, sqlite +from sqlalchemy.engine.default import DefaultDialect +from samples.sample_duckdb import DuckDBConnector from singer_sdk.connectors import SQLConnector +from singer_sdk.connectors.sql import FullyQualifiedName from singer_sdk.exceptions import ConfigValidationError if t.TYPE_CHECKING: @@ -289,25 +292,6 @@ def test_engine_json_serialization(self, connector: SQLConnector): ] -class DuckDBConnector(SQLConnector): - allow_column_alter = True - - @staticmethod - def get_column_alter_ddl( - table_name: str, - column_name: str, - column_type: sa.types.TypeEngine, - ) -> sa.DDL: - return sa.DDL( - "ALTER TABLE %(table_name)s ALTER COLUMN %(column_name)s TYPE %(column_type)s", # noqa: E501 - { - "table_name": table_name, - "column_name": column_name, - "column_type": column_type, - }, - ) - - class TestDuckDBConnector: @pytest.fixture def connector(self): @@ -317,7 +301,7 @@ def test_create_schema(self, connector: DuckDBConnector): engine = connector._engine connector.create_schema("test_schema") inspector = sa.inspect(engine) - assert "test_schema" in inspector.get_schema_names() + assert "memory.test_schema" in inspector.get_schema_names() def test_column_rename(self, connector: DuckDBConnector): engine = connector._engine @@ -373,3 +357,38 @@ def create_engine(self) -> Engine: connector = CustomConnector(config={"sqlalchemy_url": "myrdbms:///"}) connector.create_engine() + + +def test_fully_qualified_name(): + fqn = FullyQualifiedName(table="my_table") + assert fqn == "my_table" + + fqn = FullyQualifiedName(schema="my_schema", table="my_table") + assert fqn == "my_schema.my_table" + + fqn = FullyQualifiedName( + database="my_catalog", + schema="my_schema", + table="my_table", + ) + assert fqn == "my_catalog.my_schema.my_table" + + +def test_fully_qualified_name_with_quoting(): + class QuotedFullyQualifiedName(FullyQualifiedName): + def __init__(self, *, dialect: sa.engine.Dialect, **kwargs: t.Any): + self.dialect = dialect + super().__init__(**kwargs) + + def prepare_part(self, part: str) -> str: + return self.dialect.identifier_preparer.quote(part) + + dialect = DefaultDialect() + + fqn = QuotedFullyQualifiedName(table="order", schema="public", dialect=dialect) + assert fqn == 'public."order"' + + +def test_fully_qualified_name_empty_error(): + with pytest.raises(ValueError, match="Could not generate fully qualified name"): + FullyQualifiedName() diff --git a/tests/core/test_flattening.py b/tests/core/test_flattening.py index 73169eab3..1e0466986 100644 --- a/tests/core/test_flattening.py +++ b/tests/core/test_flattening.py @@ -20,7 +20,7 @@ { "key_1": 1, "key_2__key_3": "value", - "key_2__key_4": '{"key_5": 1, "key_6": ["a", "b"]}', + "key_2__key_4": '{"key_5":1,"key_6":["a","b"]}', }, id="flattened schema limiting the max level", ), @@ -38,7 +38,7 @@ "key_1": 1, "key_2__key_3": "value", "key_2__key_4__key_5": 1, - "key_2__key_4__key_6": '["a", "b"]', + "key_2__key_4__key_6": '["a","b"]', }, id="flattened schema not limiting the max level", ), @@ -55,7 +55,7 @@ { "key_1": 1, "key_2__key_3": "value", - "key_2__key_4": '{"key_5": 1, "key_6": ["a", "b"]}', + "key_2__key_4": '{"key_5":1,"key_6":["a","b"]}', }, id="max level limiting flattened schema", ), diff --git a/tests/core/test_io.py b/tests/core/test_io.py index 0fcce614b..a48a785df 100644 --- a/tests/core/test_io.py +++ b/tests/core/test_io.py @@ -3,13 +3,16 @@ from __future__ import annotations import decimal +import io import itertools import json -from contextlib import nullcontext +from contextlib import nullcontext, redirect_stdout +from textwrap import dedent import pytest from singer_sdk._singerlib import RecordMessage +from singer_sdk._singerlib.exceptions import InvalidInputLine from singer_sdk.io_base import SingerReader, SingerWriter @@ -36,7 +39,7 @@ def _process_state_message(self, message_dict: dict) -> None: pytest.param( "not-valid-json", None, - pytest.raises(json.decoder.JSONDecodeError), + pytest.raises(InvalidInputLine), id="unparsable", ), pytest.param( @@ -57,6 +60,43 @@ def test_deserialize(line, expected, exception): assert reader.deserialize_json(line) == expected +def test_listen(): + reader = DummyReader() + input_lines = io.StringIO( + dedent("""\ + {"type": "SCHEMA", "stream": "users", "schema": {"type": "object", "properties": {"id": {"type": "integer"}, "value": {"type": "number"}}}} + {"type": "RECORD", "stream": "users", "record": {"id": 1, "value": 1.23}} + {"type": "RECORD", "stream": "users", "record": {"id": 2, "value": 2.34}} + {"type": "STATE", "value": {"bookmarks": {"users": {"id": 2}}}} + {"type": "SCHEMA", "stream": "batches", "schema": {"type": "object", "properties": {"id": {"type": "integer"}, "value": {"type": "number"}}}} + {"type": "BATCH", "stream": "batches", "encoding": {"format": "jsonl", "compression": "gzip"}, "manifest": ["file1.jsonl.gz", "file2.jsonl.gz"]} + {"type": "STATE", "value": {"bookmarks": {"users": {"id": 2}, "batches": {"id": 1000000}}}} + """) # noqa: E501 + ) + reader.listen(input_lines) + + +def test_listen_unknown_message(): + reader = DummyReader() + input_lines = io.StringIO('{"type": "UNKNOWN"}\n') + with pytest.raises(ValueError, match="Unknown message type"): + reader.listen(input_lines) + + +def test_write_message(): + writer = SingerWriter() + message = RecordMessage( + stream="test", + record={"id": 1, "name": "test"}, + ) + with redirect_stdout(io.StringIO()) as out: + writer.write_message(message) + + assert out.getvalue() == ( + '{"type":"RECORD","stream":"test","record":{"id":1,"name":"test"}}\n' + ) + + # Benchmark Tests diff --git a/tests/core/test_jsonschema_helpers.py b/tests/core/test_jsonschema_helpers.py index 2c13f93ee..aeb2bae0b 100644 --- a/tests/core/test_jsonschema_helpers.py +++ b/tests/core/test_jsonschema_helpers.py @@ -8,7 +8,6 @@ from textwrap import dedent import pytest -from jsonschema import Draft6Validator from singer_sdk.helpers._typing import ( JSONSCHEMA_ANNOTATION_SECRET, @@ -27,6 +26,7 @@ ) from singer_sdk.tap_base import Tap from singer_sdk.typing import ( + DEFAULT_JSONSCHEMA_VALIDATOR, AllOf, AnyType, ArrayType, @@ -639,7 +639,7 @@ def test_array_type(): assert ArrayType(wrapped_type).type_dict == expected_json_schema -@pytest.mark.snapshot() +@pytest.mark.snapshot @pytest.mark.parametrize( "schema_obj,snapshot_name", [ @@ -932,7 +932,7 @@ def test_discriminated_union(): ), ) - validator = Draft6Validator(th.to_dict()) + validator = DEFAULT_JSONSCHEMA_VALIDATOR(th.to_dict()) assert validator.is_valid( { diff --git a/tests/core/test_mapper.py b/tests/core/test_mapper.py index 058099f3a..2d691725b 100644 --- a/tests/core/test_mapper.py +++ b/tests/core/test_mapper.py @@ -672,7 +672,7 @@ def discover_streams(self): datetime.datetime(2022, 1, 1, tzinfo=datetime.timezone.utc), tick=False, ) -@pytest.mark.snapshot() +@pytest.mark.snapshot @pytest.mark.parametrize( "stream_maps,config,snapshot_name", [ diff --git a/tests/core/test_metrics.py b/tests/core/test_metrics.py index c78969f86..b0de4c9bb 100644 --- a/tests/core/test_metrics.py +++ b/tests/core/test_metrics.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import os import pytest import time_machine @@ -18,6 +19,8 @@ def __str__(self) -> str: def test_meter(): + pid = os.getpid() + class _MyMeter(metrics.Meter): def __enter__(self): return self @@ -27,11 +30,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): meter = _MyMeter(metrics.Metric.RECORD_COUNT) - assert meter.tags == {} + assert meter.tags == {metrics.Tag.PID: pid} stream_context = {"parent_id": 1} meter.context = stream_context - assert meter.tags == {metrics.Tag.CONTEXT: stream_context} + assert meter.tags == { + metrics.Tag.CONTEXT: stream_context, + metrics.Tag.PID: pid, + } meter.context = None assert metrics.Tag.CONTEXT not in meter.tags @@ -39,6 +45,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def test_record_counter(caplog: pytest.LogCaptureFixture): caplog.set_level(logging.INFO, logger=metrics.METRICS_LOGGER_NAME) + pid = os.getpid() custom_object = CustomObject("test", 1) with metrics.record_counter( @@ -68,6 +75,7 @@ def test_record_counter(caplog: pytest.LogCaptureFixture): assert point.tags == { metrics.Tag.STREAM: "test_stream", metrics.Tag.ENDPOINT: "test_endpoint", + metrics.Tag.PID: pid, "custom_tag": "pytest", "custom_obj": custom_object, } @@ -79,6 +87,7 @@ def test_record_counter(caplog: pytest.LogCaptureFixture): def test_sync_timer(caplog: pytest.LogCaptureFixture): caplog.set_level(logging.INFO, logger=metrics.METRICS_LOGGER_NAME) + pid = os.getpid() traveler = time_machine.travel(0, tick=False) traveler.start() @@ -100,6 +109,7 @@ def test_sync_timer(caplog: pytest.LogCaptureFixture): assert point.tags == { metrics.Tag.STREAM: "test_stream", metrics.Tag.STATUS: "succeeded", + metrics.Tag.PID: pid, "custom_tag": "pytest", } diff --git a/tests/core/test_record_typing.py b/tests/core/test_record_typing.py index 2657ff285..ac222f91c 100644 --- a/tests/core/test_record_typing.py +++ b/tests/core/test_record_typing.py @@ -6,9 +6,9 @@ import typing as t from datetime import datetime -import pendulum import pytest +from singer_sdk.helpers._compat import datetime_fromisoformat as parse from singer_sdk.helpers._typing import ( TypeConformanceLevel, conform_record_data_types, @@ -21,19 +21,19 @@ "record,schema,expected_row,ignore_props_message", [ ( - {"updatedAt": pendulum.parse("2021-08-25T20:05:28+00:00")}, + {"updatedAt": parse("2021-08-25T20:05:28+00:00")}, {"properties": {"updatedAt": True}}, {"updatedAt": "2021-08-25T20:05:28+00:00"}, None, ), ( - {"updatedAt": pendulum.parse("2021-08-25T20:05:28Z")}, + {"updatedAt": parse("2021-08-25T20:05:28Z")}, {"properties": {"updatedAt": True}}, {"updatedAt": "2021-08-25T20:05:28+00:00"}, None, ), ( - {"updatedAt": pendulum.parse("2021-08-25T20:05:28")}, + {"updatedAt": parse("2021-08-25T20:05:28")}, {"properties": {"updatedAt": True}}, {"updatedAt": "2021-08-25T20:05:28+00:00"}, None, @@ -84,8 +84,8 @@ def test_conform_record_data_types( @pytest.mark.parametrize( "datetime_val,expected", [ - (pendulum.parse("2021-08-25T20:05:28+00:00"), "2021-08-25T20:05:28+00:00"), - (pendulum.parse("2021-08-25T20:05:28+07:00"), "2021-08-25T20:05:28+07:00"), + (parse("2021-08-25T20:05:28+00:00"), "2021-08-25T20:05:28+00:00"), + (parse("2021-08-25T20:05:28+07:00"), "2021-08-25T20:05:28+07:00"), ( datetime.strptime( # noqa: DTZ007 "2021-08-25T20:05:28", diff --git a/tests/core/test_state_handling.py b/tests/core/test_state_handling.py index 3f4fff148..f58a0128b 100644 --- a/tests/core/test_state_handling.py +++ b/tests/core/test_state_handling.py @@ -2,6 +2,8 @@ from __future__ import annotations +import logging + import pytest from singer_sdk.helpers import _state @@ -127,3 +129,29 @@ def test_irresumable_state(): "replication_key_value": "2021-05-17T20:41:16Z", }, } + + +def test_null_replication_value(caplog): + stream_state = { + "replication_key": "updated_at", + "replication_key_value": "2021-05-17T20:41:16Z", + } + latest_record = {"updated_at": None} + replication_key = "updated_at" + is_sorted = True + check_sorted = False + + with caplog.at_level(logging.WARNING): + _state.increment_state( + stream_state, + latest_record=latest_record, + replication_key=replication_key, + is_sorted=is_sorted, + check_sorted=check_sorted, + ) + + assert ( + stream_state["replication_key_value"] == "2021-05-17T20:41:16Z" + ), "State should not be updated." + assert caplog.records[0].levelname == "WARNING" + assert "is null" in caplog.records[0].message diff --git a/tests/core/test_streams.py b/tests/core/test_streams.py index f3d9aba84..592f921e6 100644 --- a/tests/core/test_streams.py +++ b/tests/core/test_streams.py @@ -2,10 +2,10 @@ from __future__ import annotations +import datetime import logging import typing as t -import pendulum import pytest import requests @@ -14,6 +14,7 @@ InvalidReplicationKeyException, ) from singer_sdk.helpers._classproperty import classproperty +from singer_sdk.helpers._compat import datetime_fromisoformat as parse from singer_sdk.helpers.jsonpath import _compile_jsonpath, extract_jsonpath from singer_sdk.pagination import first from singer_sdk.streams.core import REPLICATION_FULL_TABLE, REPLICATION_INCREMENTAL @@ -22,12 +23,12 @@ from singer_sdk.typing import IntegerType, PropertiesList, Property, StringType from tests.core.conftest import SimpleTestStream -CONFIG_START_DATE = "2021-01-01" - if t.TYPE_CHECKING: from singer_sdk import Stream, Tap from tests.core.conftest import SimpleTestTap +CONFIG_START_DATE = "2021-01-01" + class RestTestStream(RESTStream): """Test RESTful stream class.""" @@ -116,14 +117,14 @@ def test_stream_apply_catalog(stream: Stream): "test", None, None, - pendulum.parse(CONFIG_START_DATE), + parse(CONFIG_START_DATE).replace(tzinfo=datetime.timezone.utc), id="datetime-repl-key-no-state", ), pytest.param( "test", None, "2021-02-01", - pendulum.datetime(2021, 2, 1), + datetime.datetime(2021, 2, 1, tzinfo=datetime.timezone.utc), id="datetime-repl-key-recent-bookmark", ), pytest.param( @@ -137,7 +138,7 @@ def test_stream_apply_catalog(stream: Stream): "test", None, "2020-01-01", - pendulum.parse(CONFIG_START_DATE), + parse(CONFIG_START_DATE).replace(tzinfo=datetime.timezone.utc), id="datetime-repl-key-old-bookmark", ), pytest.param( @@ -179,7 +180,7 @@ def test_stream_apply_catalog(stream: Stream): "unix_ts_override", None, "1577858400", - pendulum.parse(CONFIG_START_DATE).format("X"), + parse(CONFIG_START_DATE).timestamp(), id="unix-ts-repl-key-old-bookmark", ), ], diff --git a/tests/core/test_typing.py b/tests/core/test_typing.py index 66182c52d..5043c75e1 100644 --- a/tests/core/test_typing.py +++ b/tests/core/test_typing.py @@ -294,7 +294,7 @@ def test_conform_primitives(): ) assert ( _conform_primitive_property(datetime.date(2020, 5, 17), {"type": "string"}) - == "2020-05-17T00:00:00+00:00" + == "2020-05-17" ) assert ( _conform_primitive_property(datetime.timedelta(365), {"type": "string"}) diff --git a/tests/external/test_tap_dummyjson.py b/tests/external/test_tap_dummyjson.py new file mode 100644 index 000000000..a9f180afc --- /dev/null +++ b/tests/external/test_tap_dummyjson.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from samples.sample_tap_dummy_json.tap_dummyjson.tap import TapDummyJSON +from singer_sdk.testing import get_tap_test_class + +CONFIG = { + "username": "emilys", + "password": "emilyspass", +} + +TestTapDummyJSON = get_tap_test_class(tap_class=TapDummyJSON, config=CONFIG) diff --git a/tests/external/test_tap_google_analytics.py b/tests/external/test_tap_google_analytics.py deleted file mode 100644 index 1bed825fd..000000000 --- a/tests/external/test_tap_google_analytics.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Tests standard tap features using the built-in SDK tests library.""" - -from __future__ import annotations - -import warnings - -from samples.sample_tap_google_analytics.ga_tap import SampleTapGoogleAnalytics -from singer_sdk.exceptions import ConfigValidationError -from singer_sdk.testing import get_tap_test_class - -from .conftest import ga_config - -try: - TestSampleTapGoogleAnalytics = get_tap_test_class( - tap_class=SampleTapGoogleAnalytics, - config=ga_config(), - parse_env_config=True, - ) -except ConfigValidationError as e: - warnings.warn( - UserWarning( - "Could not configure external gitlab tests. " - f"Config in CI is expected via env vars.\n{e}", - ), - stacklevel=2, - ) diff --git a/tests/samples/test_tap_countries.py b/tests/samples/test_tap_countries.py index 122085c6c..5730eed96 100644 --- a/tests/samples/test_tap_countries.py +++ b/tests/samples/test_tap_countries.py @@ -140,10 +140,10 @@ def tally_messages(messages: list) -> t.Counter: assert counter["SCHEMA", "countries"] == 1 assert counter["BATCH", "countries"] == 1 - assert counter[("STATE",)] == 3 + assert counter["STATE",] == 3 -@pytest.mark.snapshot() +@pytest.mark.snapshot def test_write_schema( snapshot: Snapshot, snapshot_dir: Path, diff --git a/tests/samples/test_target_sqlite.py b/tests/samples/test_target_sqlite.py index edf88ee92..4f6d54e60 100644 --- a/tests/samples/test_target_sqlite.py +++ b/tests/samples/test_target_sqlite.py @@ -182,7 +182,9 @@ def test_sqlite_column_addition(sqlite_sample_target: SQLTarget): props_a: dict[str, dict] = {"col_a": th.StringType().to_dict()} props_b = deepcopy(props_a) props_b["col_b"] = th.IntegerType().to_dict() - schema_msg_a, schema_msg_b = ( + props_c = deepcopy(props_b) + props_c["_col_c"] = th.IntegerType().to_dict() + schema_msg_a, schema_msg_b, schema_msg_c = ( { "type": "SCHEMA", "stream": test_tbl, @@ -191,7 +193,7 @@ def test_sqlite_column_addition(sqlite_sample_target: SQLTarget): "properties": props, }, } - for props in [props_a, props_b] + for props in [props_a, props_b, props_c] ) tap_output_a = "\n".join( json.dumps(msg) @@ -211,8 +213,20 @@ def test_sqlite_column_addition(sqlite_sample_target: SQLTarget): }, ] ) + tap_output_c = "\n".join( + json.dumps(msg) + for msg in [ + schema_msg_c, + { + "type": "RECORD", + "stream": test_tbl, + "record": {"col_a": "samplerow2", "col_b": 2, "_col_c": 3}, + }, + ] + ) target_sync_test(sqlite_sample_target, input=StringIO(tap_output_a), finalize=True) target_sync_test(sqlite_sample_target, input=StringIO(tap_output_b), finalize=True) + target_sync_test(sqlite_sample_target, input=StringIO(tap_output_c), finalize=True) def test_sqlite_activate_version( diff --git a/tests/snapshots/about_format/json.snap.json b/tests/snapshots/about_format/json.snap.json index f1996dffa..c78b6b5b8 100644 --- a/tests/snapshots/about_format/json.snap.json +++ b/tests/snapshots/about_format/json.snap.json @@ -4,9 +4,12 @@ "version": "0.1.1", "sdk_version": "1.0.0", "supported_python_versions": [ - "3.6", - "3.7", - "3.8" + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", + "3.13" ], "capabilities": [ "catalog", diff --git a/tests/snapshots/about_format/markdown.snap.md b/tests/snapshots/about_format/markdown.snap.md index c9c4e7b82..860dafb70 100644 --- a/tests/snapshots/about_format/markdown.snap.md +++ b/tests/snapshots/about_format/markdown.snap.md @@ -10,6 +10,15 @@ Built with the [Meltano Singer SDK](https://sdk.meltano.com). * `discover` * `state` +## Supported Python Versions + +* 3.8 +* 3.9 +* 3.10 +* 3.11 +* 3.12 +* 3.13 + ## Settings | Setting | Required | Default | Description | @@ -20,9 +29,3 @@ Built with the [Meltano Singer SDK](https://sdk.meltano.com). | complex_setting.sub_setting | False | None | A sub-setting. | A full list of supported settings and capabilities is available by running: `tap-example --about` - -## Supported Python Versions - -* 3.6 -* 3.7 -* 3.8 diff --git a/tests/snapshots/about_format/text.snap.txt b/tests/snapshots/about_format/text.snap.txt index e6796f3b6..fb0872e85 100644 --- a/tests/snapshots/about_format/text.snap.txt +++ b/tests/snapshots/about_format/text.snap.txt @@ -2,6 +2,24 @@ Name: tap-example Description: Example tap for Singer SDK Version: 0.1.1 SDK Version: 1.0.0 -Supported Python Versions: ['3.6', '3.7', '3.8'] -Capabilities: [catalog, discover, state] -Settings: {'properties': {'start_date': {'type': 'string', 'format': 'date-time', 'description': 'Start date for the tap to extract data from.'}, 'api_key': {'type': 'string', 'description': 'API key for the tap to use.'}, 'complex_setting': {'type': 'object', 'description': 'A complex setting, with sub-settings.', 'properties': {'sub_setting': {'type': 'string', 'description': 'A sub-setting.'}}}}, 'required': ['api_key']} \ No newline at end of file +Supported Python Versions: + - 3.8 + - 3.9 + - 3.10 + - 3.11 + - 3.12 + - 3.13 +Capabilities: + - catalog + - discover + - state +Settings: + - Name: start_date + Type: string + Environment Variable: TAP_EXAMPLE_START_DATE + - Name: api_key + Type: string + Environment Variable: TAP_EXAMPLE_API_KEY + - Name: complex_setting + Type: object + Environment Variable: TAP_EXAMPLE_COMPLEX_SETTING diff --git a/tests/snapshots/mapped_stream/flatten_all.jsonl b/tests/snapshots/mapped_stream/flatten_all.jsonl index 21504a38f..79f981ac5 100644 --- a/tests/snapshots/mapped_stream/flatten_all.jsonl +++ b/tests/snapshots/mapped_stream/flatten_all.jsonl @@ -1,6 +1,6 @@ {"type":"STATE","value":{}} {"type":"SCHEMA","stream":"mystream","schema":{"properties":{"email":{"type":["string","null"]},"count":{"type":["integer","null"]},"user__id":{"type":["integer","null"]},"user__sub__num":{"type":["integer","null"]},"user__sub__custom_obj":{"type":["string","null"]},"user__some_numbers":{"type":["string","null"]}},"type":"object"},"key_properties":[]} -{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub__num":1,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[3.14, 2.718]"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub__num":2,"user__sub__custom_obj":"obj-world","user__some_numbers":"[10.32, 1.618]"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub__num":3,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[1.414, 1.732]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub__num":1,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[3.14,2.718]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub__num":2,"user__sub__custom_obj":"obj-world","user__some_numbers":"[10.32,1.618]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub__num":3,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[1.414,1.732]"},"time_extracted":"2022-01-01T00:00:00+00:00"} {"type":"STATE","value":{"bookmarks":{"mystream":{}}}} diff --git a/tests/snapshots/mapped_stream/flatten_depth_1.jsonl b/tests/snapshots/mapped_stream/flatten_depth_1.jsonl index 317008dd8..4dd18f86d 100644 --- a/tests/snapshots/mapped_stream/flatten_depth_1.jsonl +++ b/tests/snapshots/mapped_stream/flatten_depth_1.jsonl @@ -1,6 +1,6 @@ {"type":"STATE","value":{}} {"type":"SCHEMA","stream":"mystream","schema":{"properties":{"email":{"type":["string","null"]},"count":{"type":["integer","null"]},"user__id":{"type":["integer","null"]},"user__sub":{"type":["string","null"]},"user__some_numbers":{"type":["string","null"]}},"type":"object"},"key_properties":[]} -{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub":"{\"num\": 1, \"custom_obj\": \"obj-hello\"}","user__some_numbers":"[3.14, 2.718]"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub":"{\"num\": 2, \"custom_obj\": \"obj-world\"}","user__some_numbers":"[10.32, 1.618]"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub":"{\"num\": 3, \"custom_obj\": \"obj-hello\"}","user__some_numbers":"[1.414, 1.732]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub":"{\"num\":1,\"custom_obj\":\"obj-hello\"}","user__some_numbers":"[3.14,2.718]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub":"{\"num\":2,\"custom_obj\":\"obj-world\"}","user__some_numbers":"[10.32,1.618]"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub":"{\"num\":3,\"custom_obj\":\"obj-hello\"}","user__some_numbers":"[1.414,1.732]"},"time_extracted":"2022-01-01T00:00:00+00:00"} {"type":"STATE","value":{"bookmarks":{"mystream":{}}}} diff --git a/tests/snapshots/mapped_stream/map_and_flatten.jsonl b/tests/snapshots/mapped_stream/map_and_flatten.jsonl index 89397a046..5bc3b7f42 100644 --- a/tests/snapshots/mapped_stream/map_and_flatten.jsonl +++ b/tests/snapshots/mapped_stream/map_and_flatten.jsonl @@ -1,6 +1,6 @@ {"type":"STATE","value":{}} {"type":"SCHEMA","stream":"mystream","schema":{"properties":{"email":{"type":["string","null"]},"count":{"type":["integer","null"]},"user__id":{"type":["integer","null"]},"user__sub__num":{"type":["integer","null"]},"user__sub__custom_obj":{"type":["string","null"]},"user__some_numbers":{"type":["string","null"]},"email_hash":{"type":["string","null"]}},"type":"object"},"key_properties":["email_hash"]} -{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub__num":1,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[3.14, 2.718]","email_hash":"c160f8cc69a4f0bf2b0362752353d060"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub__num":2,"user__sub__custom_obj":"obj-world","user__some_numbers":"[10.32, 1.618]","email_hash":"4b9bb80620f03eb3719e0a061c14283d"},"time_extracted":"2022-01-01T00:00:00+00:00"} -{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub__num":3,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[1.414, 1.732]","email_hash":"426b189df1e2f359efe6ee90f2d2030f"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"alice@example.com","count":21,"user__id":1,"user__sub__num":1,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[3.14,2.718]","email_hash":"c160f8cc69a4f0bf2b0362752353d060"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"bob@example.com","count":13,"user__id":2,"user__sub__num":2,"user__sub__custom_obj":"obj-world","user__some_numbers":"[10.32,1.618]","email_hash":"4b9bb80620f03eb3719e0a061c14283d"},"time_extracted":"2022-01-01T00:00:00+00:00"} +{"type":"RECORD","stream":"mystream","record":{"email":"charlie@example.com","count":19,"user__id":3,"user__sub__num":3,"user__sub__custom_obj":"obj-hello","user__some_numbers":"[1.414,1.732]","email_hash":"426b189df1e2f359efe6ee90f2d2030f"},"time_extracted":"2022-01-01T00:00:00+00:00"} {"type":"STATE","value":{"bookmarks":{"mystream":{}}}}