diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 77112af5bc76..427c16bbbf2c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,6 @@ "jdkDistro": "open", "gradleVersion": "7.5.1" }, - // Python needed for `airbyte-ci` CLI "ghcr.io/devcontainers/features/python:1": { "installGradle": true, "version": "3.10", @@ -57,8 +56,7 @@ }, // Mark the root directory as 'safe' for git. "initializeCommand": "git config --add safe.directory /workspaces/airbyte", - // Install Gradle, `airbyte-ci` CLI, and Dagger (installed via airbyte-ci --help) - "postCreateCommand": "make tools.airbyte-ci-dev.install", + "postCreateCommand": "uv tool install airbyte-cdk", "containerEnv": { // Deterministic Poetry virtual env location: `./.venv` "POETRY_VIRTUALENVS_IN_PROJECT": "true" diff --git a/.devcontainer/java-connectors-generic/devcontainer.json b/.devcontainer/java-connectors-generic/devcontainer.json index 9ddf7c29b642..9a9177157daa 100644 --- a/.devcontainer/java-connectors-generic/devcontainer.json +++ b/.devcontainer/java-connectors-generic/devcontainer.json @@ -11,7 +11,6 @@ "jdkDistro": "open", "gradleVersion": "7.5.1" }, - // Python needed for `airbyte-ci` CLI "ghcr.io/devcontainers/features/python:1": { "installGradle": true, "version": "3.10", @@ -42,8 +41,8 @@ // Mark the root directory as 'safe' for git. "initializeCommand": "git config --add safe.directory /workspaces/airbyte", - // Install `airbyte-ci` and Gradle - "postCreateCommand": "make tools.airbyte-ci-dev.install && ./gradlew --version", + // Install dependencies and warm up Gradle + "postCreateCommand": "uv tool install airbyte-cdk && ./gradlew --version", "containerEnv": { // Deterministic Poetry virtual env location: `./.venv` diff --git a/.devcontainer/python-connectors-generic/devcontainer.json b/.devcontainer/python-connectors-generic/devcontainer.json index fb00fd287a5c..d0124e865166 100644 --- a/.devcontainer/python-connectors-generic/devcontainer.json +++ b/.devcontainer/python-connectors-generic/devcontainer.json @@ -49,8 +49,8 @@ // Mark the root directory as 'safe' for git. "initializeCommand": "git config --add safe.directory /workspaces/airbyte", - // Setup airbyte-ci on the container: - "postCreateCommand": "make tools.airbyte-ci-dev.install", + // Install the Airbyte CDK: + "postCreateCommand": "uv tool install airbyte-cdk", "containerEnv": { // Deterministic Poetry virtual env location: `./.venv` diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b1080705932f..c65013c27061 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,9 +38,24 @@ /airbyte-integrations/connectors/source-*/ @airbytehq/dbsources # ----------------------------------------------------------------------------- -# DESTINATION CONNECTORS -# ----------------------------------------------------------------------------- -/airbyte-integrations/connectors/destination-*/ @airbytehq/move-destinations +# DESTINATION CONNECTORS (certified + JDK-based only) +# ----------------------------------------------------------------------------- +# Only destination connectors that are both certified AND JDK-based are owned +# by @airbytehq/move-destinations. This avoids unnecessary review requests on +# community or non-JDK connectors (e.g. dependabot bumps). +/airbyte-integrations/connectors/destination-azure-blob-storage/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-bigquery/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-clickhouse/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-customer-io/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-databricks/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-gcs-data-lake/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-hubspot/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-mssql/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-postgres/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-redshift/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-s3/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-s3-data-lake/ @airbytehq/move-destinations +/airbyte-integrations/connectors/destination-snowflake/ @airbytehq/move-destinations # Destination documentation /docs/integrations/destinations/ @airbytehq/move-destinations diff --git a/.github/actions/install-airbyte-ci/action.yml b/.github/actions/install-airbyte-ci/action.yml deleted file mode 100644 index 0abae543bb56..000000000000 --- a/.github/actions/install-airbyte-ci/action.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: "Install Airbyte CI" -description: "Install Airbyte CI from source or from a binary according to changed files. Pulls the Dagger Engine image according to the dagger version used in airbyte-ci." - -inputs: - airbyte_ci_binary_url: - description: "URL to airbyte-ci binary" - required: false - default: https://connectors.airbyte.com/airbyte-ci/releases/ubuntu/latest/airbyte-ci - path_to_airbyte_ci_source: - description: "Path to airbyte-ci source" - required: false - default: airbyte-ci/connectors/pipelines - is_fork: - description: "Whether the PR is from a fork" - required: false - default: "false" -runs: - using: "composite" - steps: - - name: Get changed files - uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v44.0.0 - # When the PR is from a fork, we always install from binary, so we don't need to check for changes - if: inputs.is_fork == 'false' - id: changes - with: - files_yaml: | - pipelines: - - '${{ inputs.path_to_airbyte_ci_source }}/**' - - - name: "Determine how Airbyte CI should be installed" - shell: bash - id: determine-install-mode - run: | - echo "install-mode=binary" >> $GITHUB_OUTPUT - echo "SENTRY_ENVIRONMENT=dev" >> $GITHUB_ENV - - # When the PR is from a fork, we always install from binary - # if: inputs.is_fork == 'false' - # run: | - # if [[ "${{ github.ref }}" != "refs/heads/master" ]] && [[ "${{ steps.changes.outputs.pipelines_any_changed }}" == "true" ]]; then - # echo "Making changes to Airbyte CI on a non-master branch. Airbyte-CI will be installed from source." - # echo "install-mode=source" >> $GITHUB_OUTPUT - # echo "SENTRY_ENVIRONMENT=dev" >> $GITHUB_ENV - # else - # echo "install-mode=binary" >> $GITHUB_OUTPUT - # echo "SENTRY_ENVIRONMENT=production" >> $GITHUB_ENV - # fi - - - name: Install Airbyte CI from binary - id: install-airbyte-ci-binary - if: steps.determine-install-mode.outputs.install-mode == 'binary' || ${{ inputs.is_fork }} == 'true' - shell: bash - run: | - curl -sSL ${{ inputs.airbyte_ci_binary_url }} --output airbyte-ci-bin - sudo mv airbyte-ci-bin /usr/local/bin/airbyte-ci - sudo chmod +x /usr/local/bin/airbyte-ci - - - name: Install Python 3.11 - id: install-python-3-11 - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 - if: steps.determine-install-mode.outputs.install-mode == 'source' - with: - python-version: "3.11" - token: ${{ inputs.github_token }} - cache: "pip" - check-latest: true - update-environment: true - - - name: Install the latest version of uv - if: steps.determine-install-mode.outputs.install-mode == 'source' - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - - name: Install Airbyte CI from source - id: install-airbyte-ci-source - if: steps.determine-install-mode.outputs.install-mode == 'source' - shell: bash - run: | - uv tool install ${{ inputs.path_to_airbyte_ci_source }} - - - name: Upload uv logs as artifacts on failure - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: uv-logs - path: /opt/uv/logs/ - - - name: Upload pipx logs as artifacts on failure - # E.g. /opt/pipx/logs/cmd_2025-05-01_16.46.50_1_pip_errors.log - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: pipx-logs - path: /opt/pipx/logs/ - - - name: Print installed `airbyte-ci` version - shell: bash - run: | - airbyte-ci --version - - - name: Get dagger engine image name - id: get-dagger-engine-image-name - shell: bash - run: | - dagger_engine_image=$(airbyte-ci --ci-requirements | tail -n 1 | jq -r '.dagger_engine_image') - echo "dagger_engine_image=${dagger_engine_image}" >> "$GITHUB_OUTPUT" - - - name: Get dagger engine image - id: get-dagger-engine-image - uses: ./.github/actions/get-dagger-engine-image - with: - dagger_engine_image: ${{ steps.get-dagger-engine-image-name.outputs.dagger_engine_image }} - -outputs: - install_mode: - description: "Whether Airbyte CI was installed from source or from a binary" - value: ${{ steps.determine-install-mode.outputs.install-mode }} - dagger_engine_image_name: - description: "Dagger engine image name" - value: ${{ steps.get-dagger-engine-image-name.outputs.dagger_engine_image }} diff --git a/.github/actions/install-java-environment/action.yml b/.github/actions/install-java-environment/action.yml index 63fdcde6a071..78a3783fd974 100644 --- a/.github/actions/install-java-environment/action.yml +++ b/.github/actions/install-java-environment/action.yml @@ -19,11 +19,11 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: corretto java-version: ${{ inputs.java_version }} - - uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: cache-read-only: ${{ inputs.gradle_cache_read_only }} cache-write-only: ${{ inputs.gradle_cache_write_only }} diff --git a/.github/actions/run-airbyte-ci/action.yml b/.github/actions/run-airbyte-ci/action.yml deleted file mode 100644 index 60212620535e..000000000000 --- a/.github/actions/run-airbyte-ci/action.yml +++ /dev/null @@ -1,217 +0,0 @@ -name: "Run Dagger pipeline" -description: "Runs a given dagger pipeline" -inputs: - subcommand: - description: "Subcommand for airbyte-ci" - required: true - context: - description: "CI context (e.g., pull_request, manual)" - required: true - github_token: - description: "GitHub token" - required: false - dagger_cloud_token: - description: "Dagger Cloud token" - required: false - docker_hub_username: - description: "Dockerhub username" - required: false - docker_hub_password: - description: "Dockerhub password" - required: false - options: - description: "Options for the subcommand" - required: false - production: - description: "Whether to run in production mode" - required: false - default: "True" - report_bucket_name: - description: "Bucket name for CI reports" - required: false - default: "airbyte-ci-reports-multi" - gcp_gsm_credentials: - description: "GCP credentials for GCP Secret Manager" - required: false - default: "" - gcp_integration_tester_credentials: - description: "GCP credentials for integration tests" - required: false - default: "" - git_repo_url: - description: "Git repository URL" - default: https://github.com/airbytehq/airbyte.git - required: false - git_branch: - description: "Git branch to checkout" - required: false - git_revision: - description: "Git revision to checkout" - required: false - slack_webhook_url: - description: "Slack webhook URL" - required: false - metadata_service_gcs_credentials: - description: "GCP credentials for metadata service" - required: false - metadata_service_bucket_name: - description: "Bucket name for metadata service" - required: false - default: "prod-airbyte-cloud-connector-metadata-service" - sentry_dsn: - description: "Sentry DSN" - required: false - spec_cache_bucket_name: - description: "Bucket name for GCS spec cache" - required: false - default: "io-airbyte-cloud-spec-cache" - spec_cache_gcs_credentials: - description: "GCP credentials for GCS spec cache" - required: false - gcs_credentials: - description: "GCP credentials for GCS" - required: false - ci_job_key: - description: "CI job key" - required: false - s3_build_cache_access_key_id: - description: "Gradle S3 Build Cache AWS access key ID" - required: false - s3_build_cache_secret_key: - description: "Gradle S3 Build Cache AWS secret key" - required: false - airbyte_ci_binary_url: - description: "URL to airbyte-ci binary" - required: false - default: https://connectors.airbyte.com/airbyte-ci/releases/ubuntu/latest/airbyte-ci - python_registry_token: - description: "Python registry API token to publish python package" - required: false - is_fork: - description: "Whether the PR is from a fork" - required: false - default: "false" - max_attempts: - description: "Number of attempts at running the airbyte-ci command" - required: false - default: 1 - retry_wait_seconds: - description: "Number of seconds to wait between retry attempts" - required: false - default: 60 - -runs: - using: "composite" - steps: - - name: Get start timestamp - id: get-start-timestamp - shell: bash - run: echo "start-timestamp=$(date +%s)" >> $GITHUB_OUTPUT - - name: Debug-print local paths checked out - shell: bash - run: | - set -x - echo "Working directory: $(pwd)" - ls -la - ls -la airbyte-python-cdk || echo "No airbyte-python-cdk directory" - ls -laL ../airbyte-python-cdk || echo "No airbyte-python-cdk symlink" - - name: Install Java Environment - id: install-java-environment - uses: ./.github/actions/install-java-environment - - name: Docker login - id: docker-login - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - if: ${{ inputs.docker_hub_username != '' && inputs.docker_hub_password != '' }} - with: - username: ${{ inputs.docker_hub_username }} - password: ${{ inputs.docker_hub_password }} - - name: Install Airbyte CI - id: install-airbyte-ci - uses: ./.github/actions/install-airbyte-ci - with: - airbyte_ci_binary_url: ${{ inputs.airbyte_ci_binary_url }} - is_fork: ${{ inputs.is_fork }} - - name: Run airbyte-ci - id: run-airbyte-ci - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 - env: - CI: "True" - CI_GIT_USER: ${{ github.repository_owner }} - CI_PIPELINE_START_TIMESTAMP: ${{ steps.get-start-timestamp.outputs.start-timestamp }} - PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - # Next environment variables are workflow inputs based and can be set with empty values if the inputs are not required and passed - CI_CONTEXT: "${{ inputs.context }}" - CI_GIT_BRANCH: ${{ inputs.git_branch || github.head_ref }} - CI_GIT_REPO_URL: ${{ inputs.git_repo_url }} - CI_GIT_REVISION: ${{ inputs.git_revision || github.sha }} - CI_GITHUB_ACCESS_TOKEN: ${{ inputs.github_token }} - CI_JOB_KEY: ${{ inputs.ci_job_key }} - CI_REPORT_BUCKET_NAME: ${{ inputs.report_bucket_name }} - # Disabling Dagger Cloud, as it is not used in the current workflow - # and it is causing log spam in the GitHub Actions logs. - # If this is desired in the future, uncomment the following line and remove the line below setting DO_NOT_TRACK - # DAGGER_CLOUD_TOKEN: "${{ inputs.dagger_cloud_token }}" - DO_NOT_TRACK: "1" # Disable Dagger telemetry (noisy errors) - DOCKER_HUB_PASSWORD: ${{ inputs.docker_hub_password }} - DOCKER_HUB_USERNAME: ${{ inputs.docker_hub_username }} - GCP_GSM_CREDENTIALS: ${{ inputs.gcp_gsm_credentials }} - GCP_INTEGRATION_TESTER_CREDENTIALS: ${{ inputs.gcp_integration_tester_credentials }} - GCS_CREDENTIALS: ${{ inputs.gcs_credentials }} - METADATA_SERVICE_BUCKET_NAME: ${{ inputs.metadata_service_bucket_name }} - METADATA_SERVICE_GCS_CREDENTIALS: ${{ inputs.metadata_service_gcs_credentials }} - PRODUCTION: ${{ inputs.production }} - PYTHON_REGISTRY_TOKEN: ${{ inputs.python_registry_token }} - PYTHON_REGISTRY_URL: ${{ inputs.python_registry_url }} - S3_BUILD_CACHE_ACCESS_KEY_ID: ${{ inputs.s3_build_cache_access_key_id }} - S3_BUILD_CACHE_SECRET_KEY: ${{ inputs.s3_build_cache_secret_key }} - SENTRY_DSN: ${{ inputs.sentry_dsn }} - SLACK_WEBHOOK: ${{ inputs.slack_webhook_url }} - SPEC_CACHE_BUCKET_NAME: ${{ inputs.spec_cache_bucket_name }} - SPEC_CACHE_GCS_CREDENTIALS: ${{ inputs.spec_cache_gcs_credentials }} - with: - shell: bash - max_attempts: ${{ inputs.max_attempts }} - retry_wait_seconds: ${{ inputs.retry_wait_seconds }} - # 360mn > 6 hours: it's the GitHub runner max job duration - timeout_minutes: 360 - command: | - airbyte-ci --disable-update-check --disable-dagger-run --is-ci --gha-workflow-run-id=${{ github.run_id }} ${{ inputs.subcommand }} ${{ inputs.options }} - - name: Stop Engine - id: stop-engine - if: always() - shell: bash - run: | - mapfile -t containers < <(docker ps --filter name="dagger-engine-*" -q) - if [[ "${#containers[@]}" -gt 0 ]]; then - # give 5mn to the Dagger Engine to push cache data to Dagger Cloud - docker stop -t 300 "${containers[@]}"; - fi - - - name: Collect dagger engine logs - id: collect-dagger-engine-logs - if: always() - uses: jwalton/gh-docker-logs@2741064ab9d7af54b0b1ffb6076cf64c16f0220e # v2.2.2 - with: - dest: "./dagger_engine_logs" - images: "registry.dagger.io/engine" - - - name: Tar logs - id: tar-logs - if: always() - shell: bash - run: tar cvzf ./dagger_engine_logs.tgz ./dagger_engine_logs - - - name: Hash subcommand - id: hash-subcommand - shell: bash - if: always() - run: echo "subcommand_hash=$(echo ${{ inputs.subcommand }} | sha256sum | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT - - - name: Upload logs to GitHub - id: upload-dagger-engine-logs - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ${{ github.job }}_${{ steps.hash-subcommand.outputs.subcommand_hash }}_dagger_engine_logs.tgz - path: ./dagger_engine_logs.tgz - retention-days: 7 diff --git a/.github/actions/runner-prepare-for-build/action.yml b/.github/actions/runner-prepare-for-build/action.yml index fec6da875693..c4a0f22e94cd 100644 --- a/.github/actions/runner-prepare-for-build/action.yml +++ b/.github/actions/runner-prepare-for-build/action.yml @@ -17,7 +17,7 @@ runs: using: "composite" steps: - if: inputs.install_java == 'true' - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a3f037063b2a..043307c3122d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -27,3 +27,12 @@ updates: commit-message: prefix: "chore(deps): " open-pull-requests-limit: 10 + + # GitHub Actions - keep action versions up to date + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + commit-message: + prefix: "ci(deps): " + open-pull-requests-limit: 10 diff --git a/.github/pr-welcome-internal.md b/.github/pr-welcome-internal.md index 749254d2184d..1488e1ea8967 100644 --- a/.github/pr-welcome-internal.md +++ b/.github/pr-welcome-internal.md @@ -12,14 +12,17 @@ Airbyte Maintainers (that's you!) can execute the following slash commands on yo - 🛠️ Quick Fixes - `/format-fix` - Fixes most formatting issues. - `/bump-version` - Bumps connector versions, scraping `changelog` description from the PR title. + - Bump types: `patch` (default), `minor`, `major`, `major_rc`, `rc`, `promote`. + - The `rc` type is a smart default: applies `minor_rc` if stable, or bumps the RC number if already RC. + - The `promote` type strips the RC suffix to finalize a release. + - Example: `/bump-version type=rc` or `/bump-version type=minor` + - `/bump-progressive-rollout-version` - Alias for `/bump-version type=rc`. Bumps with an RC suffix and enables progressive rollout. - ❇️ AI Testing and Review (internal link: [AI-SDLC Docs](https://github.com/airbytehq/ai-skills/blob/main/docs/hydra/)): - `/ai-prove-fix` - Runs prerelease readiness checks, including testing against customer connections. - `/ai-canary-prerelease` - Rolls out prerelease to 5-10 connections for canary testing. - `/ai-review` - AI-powered PR review for connector safety and quality gates. - 🚀 Connector Releases: - `/publish-connectors-prerelease` - Publishes pre-release connector builds (tagged as `{version}-preview.{git-sha}`) for all modified connectors in the PR. - - `/bump-progressive-rollout-version` - Bumps connector version with an RC suffix (`2.16.10-rc.1`) for progressive rollouts (`enableProgressiveRollout: true`). - - Example: `/bump-progressive-rollout-version changelog="Add new feature for progressive rollout"` - ☕️ JVM connectors: - `/update-connector-cdk-version connector=` - Updates the specified connector to the latest CDK version. Example: `/update-connector-cdk-version connector=destination-bigquery` diff --git a/.github/workflows/ai-canary-prerelease-command.yml b/.github/workflows/ai-canary-prerelease-command.yml index e816669a83c8..892b83891ab6 100644 --- a/.github/workflows/ai-canary-prerelease-command.yml +++ b/.github/workflows/ai-canary-prerelease-command.yml @@ -35,10 +35,10 @@ jobs: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -69,6 +69,6 @@ jobs: github-token: ${{ steps.get-app-token.outputs.token }} api-version: v3 org-id: ${{ vars.DEVIN_AI_ORG_ID }} - start-message: "🐤 **AI Canary Prerelease session starting...** Rolling out to 5-10 connections, watching results, and reporting findings. [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/canary_prerelease.md)" + start-message: "🐤 **AI Canary Prerelease session starting...** Rolling out to 5-10 connections, watching results, and reporting findings. [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/canary_prerelease.md)" tags: | ai-oncall diff --git a/.github/workflows/ai-create-docs-pr-command.yml b/.github/workflows/ai-create-docs-pr-command.yml index d54e442fd4ae..53e21989eeeb 100644 --- a/.github/workflows/ai-create-docs-pr-command.yml +++ b/.github/workflows/ai-create-docs-pr-command.yml @@ -36,10 +36,10 @@ jobs: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -67,7 +67,7 @@ jobs: This will create a documentation PR stacked on top of PR #${PR_NUMBER}. - [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/connectordocs.md) + [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/connectordocs.md) STARTMSG echo "EOF" } >> $GITHUB_OUTPUT @@ -105,7 +105,7 @@ jobs: This will create a documentation PR targeting the master branch. - [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/connectordocs.md) + [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/connectordocs.md) STARTMSG echo "EOF" } >> $GITHUB_OUTPUT diff --git a/.github/workflows/ai-dev-command.yml b/.github/workflows/ai-dev-command.yml new file mode 100644 index 000000000000..83a91c3e38a9 --- /dev/null +++ b/.github/workflows/ai-dev-command.yml @@ -0,0 +1,166 @@ +name: AI Dev Playbook Command + +# Runs a dev version of an ai-skills playbook against an issue in this repo. +# Triggered via slash command: /ai-dev skills-pr= playbook= +# +# Examples: +# /ai-dev skills-pr=42 playbook=issue_triage +# /ai-dev skills-pr=42 playbook=issue_fix +# +# The dev playbook must already exist in the Devin API (created by the +# dev-playbooks.yml workflow in airbytehq/ai-skills when a PR is opened). + +on: + workflow_dispatch: + inputs: + issue: + description: "Issue number to test against" + type: string + required: false + skills-pr: + description: "ai-skills PR number with the dev playbook (named skills-pr to avoid collision with the pr static-arg)" + type: number + required: true + playbook: + description: "Playbook name (e.g. issue_triage, issue_fix)" + type: string + required: true + comment-id: + description: "The comment-id of the slash command. Used to update the comment with the status." + required: false + pr: + description: "PR number (passed by slash command dispatcher, unused)" + type: number + required: false + repo: + description: "Repo (passed by slash command dispatcher)" + required: false + default: "airbytehq/airbyte" + gitref: + description: "Git ref (passed by slash command dispatcher)" + required: false + +run-name: "AI Dev Playbook: !${{ github.event.inputs.playbook }}__dev-${{ github.event.inputs.skills-pr }}" + +permissions: + contents: read + issues: write + pull-requests: read + +jobs: + ai-dev: + runs-on: ubuntu-latest + steps: + - name: Get job variables + id: job-vars + run: | + echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT + + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Authenticate as GitHub App + uses: actions/create-github-app-token@v3.0.0 + id: get-app-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} + + - name: Resolve inputs + id: resolve + env: + INPUT_PR_NUMBER: ${{ inputs.skills-pr }} + INPUT_PLAYBOOK_NAME: ${{ inputs.playbook }} + INPUT_ISSUE_NUMBER: ${{ inputs.issue }} + INPUT_COMMENT_ID: ${{ inputs.comment-id }} + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + run: | + set -euo pipefail + + # Resolve from env vars (safely passed via env: to avoid script injection) + pr_number="$INPUT_PR_NUMBER" + playbook_name="$INPUT_PLAYBOOK_NAME" + issue_number="$INPUT_ISSUE_NUMBER" + comment_id="$INPUT_COMMENT_ID" + + # When invoked via slash command, inputs.issue is empty because + # the slash-commands.yml static-args don't include issue=. + # Resolve the issue number from the comment-id via the GitHub API. + if [[ -z "$issue_number" || "$issue_number" == "0" ]] && [[ -n "$comment_id" ]]; then + echo "Resolving issue number from comment-id..." + issue_number=$(curl -s \ + -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" \ + | jq -r '.issue_url | split("/") | last') + if [[ -z "$issue_number" || "$issue_number" == "null" ]]; then + echo "::warning::Could not resolve issue number from comment-id $comment_id" + issue_number="" + else + echo "Resolved issue number: $issue_number" + fi + fi + + if [[ -z "$pr_number" ]]; then + echo "::error::Missing required argument: pr (ai-skills PR number)" + exit 1 + fi + if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then + echo "::error::Invalid pr number: must be digits only" + exit 1 + fi + + if [[ -z "$playbook_name" ]]; then + echo "::error::Missing required argument: playbook (e.g. issue_triage)" + exit 1 + fi + if ! [[ "$playbook_name" =~ ^[a-z0-9_]+$ ]]; then + echo "::error::Invalid playbook name: must match [a-z0-9_]+" + exit 1 + fi + + dev_macro="!${playbook_name}__dev-${pr_number}" + + echo "pr_number=${pr_number}" >> $GITHUB_OUTPUT + echo "playbook_name=${playbook_name}" >> $GITHUB_OUTPUT + echo "issue_number=${issue_number}" >> $GITHUB_OUTPUT + echo "comment_id=${comment_id}" >> $GITHUB_OUTPUT + echo "dev_macro=${dev_macro}" >> $GITHUB_OUTPUT + + echo "Resolved inputs:" + echo " PR number: ${pr_number}" + echo " Playbook: ${playbook_name}" + echo " Issue: ${issue_number}" + echo " Dev macro: ${dev_macro}" + + - name: Post start comment + if: inputs.comment-id != '' + uses: peter-evans/create-or-update-comment@v4 + with: + token: ${{ steps.get-app-token.outputs.token }} + comment-id: ${{ inputs.comment-id }} + issue-number: ${{ steps.resolve.outputs.issue_number }} + body: | + > **AI Dev Playbook Starting** + > + > Using dev macro `${{ steps.resolve.outputs.dev_macro }}` from [ai-skills PR #${{ steps.resolve.outputs.pr_number }}](https://github.com/airbytehq/ai-skills/pull/${{ steps.resolve.outputs.pr_number }}) + > [View workflow run](${{ steps.job-vars.outputs.run-url }}) + + - name: Run dev playbook + uses: aaronsteers/devin-action@main + with: + comment-id: ${{ steps.resolve.outputs.comment_id }} + issue-number: ${{ steps.resolve.outputs.issue_number }} + playbook-macro: ${{ steps.resolve.outputs.dev_macro }} + devin-token: ${{ secrets.DEVIN_AI_API_KEY }} + github-token: ${{ steps.get-app-token.outputs.token }} + api-version: v3 + org-id: ${{ vars.DEVIN_AI_ORG_ID }} + start-message: "🔬 **Dev playbook session starting...** Using dev macro `${{ steps.resolve.outputs.dev_macro }}` from [ai-skills PR #${{ steps.resolve.outputs.pr_number }}](https://github.com/airbytehq/ai-skills/pull/${{ steps.resolve.outputs.pr_number }})" + bypass-approval: "true" + tags: | + ai-oncall + playbook-dev + dev-pr-${{ steps.resolve.outputs.pr_number }} diff --git a/.github/workflows/ai-docs-review-command.yml b/.github/workflows/ai-docs-review-command.yml index 26f31c150adc..8d2863a69267 100644 --- a/.github/workflows/ai-docs-review-command.yml +++ b/.github/workflows/ai-docs-review-command.yml @@ -84,7 +84,7 @@ jobs: - name: Authenticate as GitHub App if: steps.validate.outputs.valid == 'true' - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -110,7 +110,7 @@ jobs: - name: Checkout PR code if: steps.validate.outputs.valid == 'true' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Use refs/pull/N/head which works for both fork and non-fork PRs diff --git a/.github/workflows/ai-prove-fix-command.yml b/.github/workflows/ai-prove-fix-command.yml index 6e20a92f0fd9..282063fa1cd9 100644 --- a/.github/workflows/ai-prove-fix-command.yml +++ b/.github/workflows/ai-prove-fix-command.yml @@ -35,10 +35,10 @@ jobs: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -69,6 +69,6 @@ jobs: github-token: ${{ steps.get-app-token.outputs.token }} api-version: v3 org-id: ${{ vars.DEVIN_AI_ORG_ID }} - start-message: "🔍 **AI Prove Fix session starting...** Running readiness checks and testing against customer connections. [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/prove_fix.md)" + start-message: "🔍 **AI Prove Fix session starting...** Running readiness checks and testing against customer connections. [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/prove_fix.md)" tags: | ai-oncall diff --git a/.github/workflows/ai-release-watch-command.yml b/.github/workflows/ai-release-watch-command.yml index 186f9bc23f83..4edb102cca89 100644 --- a/.github/workflows/ai-release-watch-command.yml +++ b/.github/workflows/ai-release-watch-command.yml @@ -35,10 +35,10 @@ jobs: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -69,6 +69,6 @@ jobs: github-token: ${{ steps.get-app-token.outputs.token }} api-version: v3 org-id: ${{ vars.DEVIN_AI_ORG_ID }} - start-message: "👁️ **AI Release Watch session starting...** Monitoring rollout and tracking sync success rates. [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/release_watch.md)" + start-message: "👁️ **AI Release Watch session starting...** Monitoring rollout and tracking sync success rates. [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/release_watch.md)" tags: | ai-oncall diff --git a/.github/workflows/ai-review-command.yml b/.github/workflows/ai-review-command.yml index 55a87c98a7b4..b094d1e332bf 100644 --- a/.github/workflows/ai-review-command.yml +++ b/.github/workflows/ai-review-command.yml @@ -15,6 +15,16 @@ on: comment-id: description: "Comment ID" required: false + workflow_call: + inputs: + pr: + description: "PR number" + required: true + type: string + comment-id: + description: "Comment ID" + required: false + type: string run-name: "PR AI Review for PR #${{ inputs.pr }}" @@ -33,10 +43,10 @@ jobs: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -103,7 +113,7 @@ jobs: **AI PR Review** starting... Reviewing PR for connector safety and quality. - [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/pr_ai_review.md) + [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/pr_ai_review.md) tags: | ai-oncall pr-review diff --git a/.github/workflows/approve-regression-tests-command.yml b/.github/workflows/approve-regression-tests-command.yml deleted file mode 100644 index ae6640a6a541..000000000000 --- a/.github/workflows/approve-regression-tests-command.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: Approve Regression Tests -permissions: - pull-requests: write - statuses: write -on: - workflow_dispatch: - inputs: - pr: - description: "Pull request number. Used to pull the proper branch ref, including on forks." - type: number - required: false - comment-id: - description: "Optional. The comment-id of the slash command. Used to update the comment with the status." - required: false - connector-name: - description: "Optional. Name of the connector whose regression tests are approved." - required: false - - # These must be declared, but they are unused and ignored. - # TODO: Infer 'repo' and 'gitref' from PR number on other workflows, so we can remove these. - repo: - description: "Repo (Ignored)" - required: false - default: "airbytehq/airbyte" - gitref: - description: "Ref (Ignored)" - required: false - -run-name: "Approve Regression Tests #${{ github.event.inputs.pr }}" - -jobs: - approve-regression-tests: - name: "Approve Regression Tests" - runs-on: ubuntu-24.04 - steps: - - name: Get job variables - id: job-vars - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr }}) - echo "PR_JSON: $PR_JSON" - echo "repo=$(echo "$PR_JSON" | jq -r .head.repo.full_name)" >> $GITHUB_OUTPUT - BRANCH=$(echo "$PR_JSON" | jq -r .head.ref) - echo "branch=$BRANCH" >> $GITHUB_OUTPUT - echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - LATEST_COMMIT=$(gh api repos/${{ github.repository }}/commits/$BRANCH | jq -r .sha) - echo "latest_commit=$LATEST_COMMIT" >> $GITHUB_OUTPUT - - - name: Append comment with job run link - # If comment-id is not provided, this will create a new - # comment with the job run link. - id: first-comment-action - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - comment-id: ${{ github.event.inputs.comment-id }} - issue-number: ${{ github.event.inputs.pr }} - body: | - - > [Check job output.][1] - - [1]: ${{ steps.job-vars.outputs.run-url }} - - - name: Approve regression tests - id: approve-regression-tests - if: github.event.inputs.connector-name != null - run: | - echo "approving regression test status check for ${{ github.event.inputs.connector-name }} if it exists ...." - response=$(curl --write-out '%{http_code}' --silent --output /dev/null \ - --request POST \ - --url ${{ github.api_url }}/repos/${{ github.repository }}/statuses/${{ steps.job-vars.outputs.latest_commit }} \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --data '{ - "state": "success", - "context": "Regression Tests on ${{ github.event.inputs.connector-name }}", - "target_url": "https://github.com/airbytehq/airbyte/tree/master/airbyte-ci/connectors/live-tests" - }') - if [ $response -ne 201 ]; then - echo "Failed to approve regression tests for ${{ github.event.inputs.connector-name }}. HTTP status code: $response" - exit 1 - else - echo "Regression tests for ${{ github.event.inputs.connector-name }} approved." - fi - - - name: Update global regression test approval - id: update-global-regression-test-approval - if: github.event.inputs.connector-name == null - run: | - echo "updating regression test approval status check if it exists ...." - response=$(curl --write-out '%{http_code}' --silent --output /dev/null \ - --request POST \ - --url ${{ github.api_url }}/repos/${{ github.repository }}/statuses/${{ steps.job-vars.outputs.latest_commit }} \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --data '{ - "state": "success", - "context": "Regression Test Results Reviewed and Approved", - "target_url": "https://github.com/airbytehq/airbyte/tree/master/airbyte-ci/connectors/live-tests" - }') - if [ $response -ne 201 ]; then - echo "Failed to update regression test approval status check. HTTP status code: $response" - exit 1 - else - echo "Regression test status check updated." - fi - - - name: Append success comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: success() - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: "+1" - body: | - > ✅ Approving regression tests - - - name: Append failure comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: failure() - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: confused - body: | - > ❌ Regression test approval failed diff --git a/.github/workflows/auto-ai-review-label.yml b/.github/workflows/auto-ai-review-label.yml new file mode 100644 index 000000000000..acfd3db1bdc3 --- /dev/null +++ b/.github/workflows/auto-ai-review-label.yml @@ -0,0 +1,28 @@ +# Triggers the AI Review workflow when the 'auto-ai-review' label is present. +# Two triggers: +# 1. Label added to a non-draft PR -> immediately call AI review. +# 2. PR marked ready for review -> call AI review if label is already set. +# Draft PRs are skipped on `labeled` to avoid a double-dispatch: ai-review-command.yml +# marks drafts as ready, which would re-trigger this workflow via `ready_for_review`. +name: Auto AI Review on Label + +on: + pull_request: + types: [labeled, ready_for_review] + +run-name: "Auto AI Review for PR #${{ github.event.pull_request.number }}" + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + dispatch-ai-review: + if: | + (github.event.action == 'labeled' && github.event.label.name == 'auto-ai-review' && github.event.pull_request.draft != true) || + (github.event.action == 'ready_for_review' && contains(github.event.pull_request.labels.*.name, 'auto-ai-review')) + uses: ./.github/workflows/ai-review-command.yml + with: + pr: "${{ github.event.pull_request.number }}" + secrets: inherit diff --git a/.github/workflows/auto-merge-cron.yml b/.github/workflows/auto-merge-cron.yml new file mode 100644 index 000000000000..14dc2d8ed26a --- /dev/null +++ b/.github/workflows/auto-merge-cron.yml @@ -0,0 +1,386 @@ +name: Auto merge connector PRs Cron +# Dry-run mode is controlled by the org-level variable ENABLE_CONNECTOR_AUTO_MERGE. +# Set to "true" to enable real merges; any other value (or unset) runs in dry-run mode. +# In dry-run mode, PRs are still approved and promoted from draft, but merges are skipped. +# Manage at: https://github.com/organizations/airbytehq/settings/variables/actions + +on: + schedule: + # Every 2 hours on the hour. + - cron: "0 */2 * * *" + workflow_dispatch: + +# All repo operations use GitHub App tokens, not GITHUB_TOKEN. +permissions: {} + +jobs: + # ---------- Job 1: Discover candidate PRs ---------- + list-candidates: + name: List eligible PRs + runs-on: ubuntu-24.04 + steps: + # ---------- Authentication ---------- + - name: Authenticate as 'octavia-bot-hoard' GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-hoard-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} + + # ---------- Find candidate PRs ---------- + - name: Find force-merge PRs (bypass-ci-checks label) + id: force-prs + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + gh pr list \ + --repo airbytehq/airbyte \ + --label "auto-merge/bypass-ci-checks" \ + --state open \ + --base master \ + --json number,title \ + --limit 100 > /tmp/force.json + count=$(jq length /tmp/force.json) + echo "force-matrix=$(cat /tmp/force.json)" | tee -a $GITHUB_OUTPUT + echo "force-count=$count" | tee -a $GITHUB_OUTPUT + + - name: Find normal-merge PRs (auto-merge label, excluding force-merge) + id: normal-prs + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + gh pr list \ + --repo airbytehq/airbyte \ + --label "auto-merge" \ + --state open \ + --base master \ + --json number,title \ + --limit 100 > /tmp/auto-merge.json + # Exclude PRs already in the force-merge set + jq -c --argjson force '${{ steps.force-prs.outputs.force-matrix }}' \ + '[.[] | select(.number as $n | $force | map(.number) | index($n) | not)]' \ + /tmp/auto-merge.json > /tmp/normal.json + count=$(jq length /tmp/normal.json) + echo "normal-matrix=$(cat /tmp/normal.json)" | tee -a $GITHUB_OUTPUT + echo "normal-count=$count" | tee -a $GITHUB_OUTPUT + + # ---------- Fetch required checks ---------- + - name: Fetch required checks for master branch + id: required-checks + if: fromJSON(steps.normal-prs.outputs.normal-count) > 0 + uses: actions/github-script@v7 + with: + github-token: ${{ steps.get-hoard-token.outputs.token }} + script: | + const { data: rules } = await github.request( + 'GET /repos/{owner}/{repo}/rules/branches/{branch}', + { owner: 'airbytehq', repo: 'airbyte', branch: 'master' } + ); + const checks = []; + for (const rule of rules) { + if (rule.type === 'required_status_checks') { + for (const check of (rule.parameters?.required_status_checks || [])) { + checks.push(check.context); + } + } + } + core.info(`Required checks (${checks.length}): ${JSON.stringify(checks)}`); + core.setOutput('checks', JSON.stringify(checks)); + outputs: + normal-matrix: ${{ steps.normal-prs.outputs.normal-matrix }} + normal-count: ${{ steps.normal-prs.outputs.normal-count }} + force-matrix: ${{ steps.force-prs.outputs.force-matrix }} + force-count: ${{ steps.force-prs.outputs.force-count }} + required-checks: ${{ steps.required-checks.outputs.checks }} + + # ---------- Job 2: Normal merge (verify CI first) ---------- + normal-merge: + name: "Merge #${{ matrix.pr.number }}" + needs: list-candidates + if: fromJSON(needs.list-candidates.outputs.normal-count) > 0 + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + pr: ${{ fromJSON(needs.list-candidates.outputs.normal-matrix) }} + env: + PR_NUMBER: ${{ matrix.pr.number }} + PR_TITLE: ${{ matrix.pr.title }} + steps: + # ---------- Authentication ---------- + - name: Authenticate as 'octavia-bot-hoard' GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-hoard-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} + + # ---------- Validation ---------- + - name: Check PR is still open + id: check-open + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + state=$(gh pr view "$PR_NUMBER" --repo airbytehq/airbyte --json state --jq '.state') + echo "state=$state" | tee -a $GITHUB_OUTPUT + + - name: Validate connector-only file paths + if: steps.check-open.outputs.state == 'OPEN' + id: validate-paths + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + set -e + gh pr diff "$PR_NUMBER" --repo airbytehq/airbyte --name-only > /tmp/changed-files.txt + valid=true + while IFS= read -r file; do + case "$file" in + airbyte-integrations/connectors/*) ;; + docs/integrations/sources/*) ;; + docs/integrations/destinations/*) ;; + docs/ai-agents/connectors/*) ;; + docs/developers/pyairbyte/*) ;; + docusaurus/src/data/*) ;; + *) valid=false; echo "::warning::Non-connector file: $file"; break ;; + esac + done < /tmp/changed-files.txt + echo "valid=$valid" | tee -a $GITHUB_OUTPUT + + - name: Verify required status checks pass + if: steps.check-open.outputs.state == 'OPEN' && steps.validate-paths.outputs.valid == 'true' + id: verify-checks + uses: actions/github-script@v7 + env: + REQUIRED_CHECKS_JSON: ${{ needs.list-candidates.outputs.required-checks }} + with: + github-token: ${{ steps.get-hoard-token.outputs.token }} + script: | + const prNum = Number(process.env.PR_NUMBER); + const requiredChecks = new Set(JSON.parse(process.env.REQUIRED_CHECKS_JSON || '[]')); + + const { data: pr } = await github.rest.pulls.get({ + owner: 'airbytehq', repo: 'airbyte', pull_number: prNum, + }); + const sha = pr.head.sha; + + const statuses = await github.paginate( + github.rest.repos.listCommitStatusesForRef, + { owner: 'airbytehq', repo: 'airbyte', ref: sha, per_page: 100 } + ); + const successStatuses = new Set( + statuses.filter(s => s.state === 'success').map(s => s.context) + ); + + const checkRuns = await github.paginate( + github.rest.checks.listForRef, + { owner: 'airbytehq', repo: 'airbyte', ref: sha, per_page: 100 }, + (response) => response.data + ); + const successChecks = new Set( + checkRuns + .filter(cr => cr.conclusion === 'success' || cr.conclusion === 'skipped') + .map(cr => cr.name) + ); + + const allPassing = new Set([...successStatuses, ...successChecks]); + const missing = [...requiredChecks].filter(c => !allPassing.has(c)); + + if (missing.length > 0) { + core.info(`Missing checks: ${missing.join(', ')}`); + core.setOutput('ready', 'false'); + } else { + core.info('All required checks passing'); + core.setOutput('ready', 'true'); + } + + # ---------- Eligibility gate ---------- + - name: Determine merge eligibility + id: eligible + run: > + echo "result=${{ + steps.check-open.outputs.state == 'OPEN' + && steps.validate-paths.outputs.valid == 'true' + && steps.verify-checks.outputs.ready == 'true' + }}" | tee -a $GITHUB_OUTPUT + + # ---------- Prepare PR for merge ---------- + - name: Check if PR is a draft + if: steps.eligible.outputs.result == 'true' + id: check-draft + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + is_draft=$(gh pr view "$PR_NUMBER" --repo airbytehq/airbyte --json isDraft --jq '.isDraft') + echo "is_draft=$is_draft" | tee -a $GITHUB_OUTPUT + + - name: Mark draft PR as ready for review + if: steps.eligible.outputs.result == 'true' && steps.check-draft.outputs.is_draft == 'true' + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: gh pr ready "$PR_NUMBER" --repo airbytehq/airbyte + + # ---------- Approve and merge ---------- + - name: Authenticate as 'octavia-bot-admin' GitHub App + if: steps.eligible.outputs.result == 'true' + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-admin-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_ADMIN_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_ADMIN_PRIVATE_KEY }} + + - name: Approve PR with admin bot + if: steps.eligible.outputs.result == 'true' + env: + GH_TOKEN: ${{ steps.get-admin-token.outputs.token }} + run: > + gh pr review "$PR_NUMBER" + --repo airbytehq/airbyte + --approve + --body "Auto-approved by auto-merge workflow." + + - name: Squash merge PR + if: steps.eligible.outputs.result == 'true' && vars.ENABLE_CONNECTOR_AUTO_MERGE == 'true' + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + for attempt in 1 2 3; do + if gh pr merge "$PR_NUMBER" --repo airbytehq/airbyte --squash; then + echo "::notice::Merged PR ${PR_NUMBER}: ${PR_TITLE}" + exit 0 + fi + echo "::warning::Merge attempt $attempt/3 failed, retrying in 60s..." + sleep 60 + done + echo "::error::Failed to merge PR #$PR_NUMBER after 3 attempts" + exit 1 + + - name: Dry-run notice + if: steps.eligible.outputs.result == 'true' && vars.ENABLE_CONNECTOR_AUTO_MERGE != 'true' + run: echo "::notice::DRY RUN -- would merge PR ${PR_NUMBER}" + + # ---------- Job 3: Force merge (bypass CI checks) ---------- + force-merge: + name: "Force-merge #${{ matrix.pr.number }}" + needs: list-candidates + if: fromJSON(needs.list-candidates.outputs.force-count) > 0 + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + pr: ${{ fromJSON(needs.list-candidates.outputs.force-matrix) }} + env: + PR_NUMBER: ${{ matrix.pr.number }} + PR_TITLE: ${{ matrix.pr.title }} + steps: + # ---------- Authentication ---------- + - name: Authenticate as 'octavia-bot-hoard' GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-hoard-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} + + # ---------- Validation ---------- + - name: Check PR is still open + id: check-open + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + state=$(gh pr view "$PR_NUMBER" --repo airbytehq/airbyte --json state --jq '.state') + echo "state=$state" | tee -a $GITHUB_OUTPUT + + - name: Validate connector-only file paths + if: steps.check-open.outputs.state == 'OPEN' + id: validate-paths + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + set -e + gh pr diff "$PR_NUMBER" --repo airbytehq/airbyte --name-only > /tmp/changed-files.txt + valid=true + while IFS= read -r file; do + case "$file" in + airbyte-integrations/connectors/*) ;; + docs/integrations/sources/*) ;; + docs/integrations/destinations/*) ;; + docs/ai-agents/connectors/*) ;; + docs/developers/pyairbyte/*) ;; + docusaurus/src/data/*) ;; + *) valid=false; echo "::warning::Non-connector file: $file"; break ;; + esac + done < /tmp/changed-files.txt + echo "valid=$valid" | tee -a $GITHUB_OUTPUT + + # ---------- Eligibility gate ---------- + - name: Determine merge eligibility + id: eligible + run: > + echo "result=${{ + steps.check-open.outputs.state == 'OPEN' + && steps.validate-paths.outputs.valid == 'true' + }}" | tee -a $GITHUB_OUTPUT + + # ---------- Prepare PR for merge ---------- + - name: Check if PR is a draft + if: steps.eligible.outputs.result == 'true' + id: check-draft + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + is_draft=$(gh pr view "$PR_NUMBER" --repo airbytehq/airbyte --json isDraft --jq '.isDraft') + echo "is_draft=$is_draft" | tee -a $GITHUB_OUTPUT + + - name: Mark draft PR as ready for review + if: steps.eligible.outputs.result == 'true' && steps.check-draft.outputs.is_draft == 'true' + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: gh pr ready "$PR_NUMBER" --repo airbytehq/airbyte + + # ---------- Approve and merge ---------- + - name: Authenticate as 'octavia-bot-admin' GitHub App + if: steps.eligible.outputs.result == 'true' + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-admin-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_ADMIN_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_ADMIN_PRIVATE_KEY }} + + - name: Approve PR with admin bot + if: steps.eligible.outputs.result == 'true' + env: + GH_TOKEN: ${{ steps.get-admin-token.outputs.token }} + run: > + gh pr review "$PR_NUMBER" + --repo airbytehq/airbyte + --approve + --body "Auto-approved by auto-merge workflow (bypass-ci)." + + - name: Force squash merge PR + if: steps.eligible.outputs.result == 'true' && vars.ENABLE_CONNECTOR_AUTO_MERGE == 'true' + env: + GH_TOKEN: ${{ steps.get-hoard-token.outputs.token }} + run: | + for attempt in 1 2 3; do + if gh pr merge "$PR_NUMBER" --repo airbytehq/airbyte --squash; then + echo "::notice::Force-merged PR ${PR_NUMBER}: ${PR_TITLE}" + exit 0 + fi + echo "::warning::Merge attempt $attempt/3 failed, retrying in 60s..." + sleep 60 + done + echo "::error::Failed to force-merge PR #$PR_NUMBER after 3 attempts" + exit 1 + + - name: Dry-run notice + if: steps.eligible.outputs.result == 'true' && vars.ENABLE_CONNECTOR_AUTO_MERGE != 'true' + run: echo "::notice::DRY RUN -- would force-merge PR ${PR_NUMBER}" diff --git a/.github/workflows/auto-merge-labels.yml b/.github/workflows/auto-merge-labels.yml new file mode 100644 index 000000000000..ed3b3fb258ce --- /dev/null +++ b/.github/workflows/auto-merge-labels.yml @@ -0,0 +1,56 @@ +name: "Auto-Merge Label Removal" + +# When the last auto-merge label is removed from a PR, disable GitHub's +# native auto-merge so the PR no longer merges automatically. + +permissions: {} + +on: + pull_request_target: + types: + - unlabeled + +jobs: + disable-auto-merge: + name: Disable native auto-merge + # Runs when either auto-merge label is removed and the other is not + # still present on the PR. + if: > + github.event.action == 'unlabeled' + && ( + github.event.label.name == 'auto-merge' + || github.event.label.name == 'auto-merge/bypass-ci-checks' + ) + && !contains(github.event.pull_request.labels.*.name, 'auto-merge') + && !contains(github.event.pull_request.labels.*.name, 'auto-merge/bypass-ci-checks') + permissions: {} + runs-on: ubuntu-24.04 + steps: + - name: Authenticate as GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-app-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} + + - name: Check if native auto-merge is enabled + id: check-auto-merge + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + AUTO_MERGE=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json autoMergeRequest --jq '.autoMergeRequest') + echo "enabled=$( [ "$AUTO_MERGE" != "null" ] && [ -n "$AUTO_MERGE" ] && echo true || echo false )" \ + | tee -a "$GITHUB_OUTPUT" + + - name: Disable native auto-merge + if: steps.check-auto-merge.outputs.enabled == 'true' + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: > + gh pr merge "$PR_NUMBER" + --repo "${{ github.repository }}" + --disable-auto diff --git a/.github/workflows/auto-merge-toggle.yml b/.github/workflows/auto-merge-toggle.yml new file mode 100644 index 000000000000..daa4d2eb977b --- /dev/null +++ b/.github/workflows/auto-merge-toggle.yml @@ -0,0 +1,94 @@ +# Responds to GitHub's native auto-merge being enabled or disabled on a PR: +# +# 1. Enabled: adds a notice to the PR description. +# 2. Disabled: updates the PR description notice and removes auto-merge +# labels so the scheduled auto_merge.yml cron no longer re-processes +# the PR. +# +# Note: Adding an `auto-merge*` label no longer auto-promotes draft PRs. +# The connectors_up_to_date pipeline handles draft promotion and auto-merge +# enablement directly. Draft PRs must be explicitly marked "Ready for review" +# before native auto-merge and label-based automation will take effect. + +name: "Auto-Merge Toggle" + +permissions: {} + +on: + pull_request_target: + types: [auto_merge_enabled, auto_merge_disabled] + +jobs: + # ---------- Update PR description notice ---------- + update-description: + runs-on: ubuntu-24.04 + permissions: + pull-requests: write + steps: + - name: Add Auto-Merge Notice + if: github.event.action == 'auto_merge_enabled' + uses: bcgov/action-pr-description-add@14338bfe0278ead273b3c1189e5aa286ff6709c4 # v2.0.0 + with: + add_markdown: | + > [!IMPORTANT] + > ⚡ **Auto-merge enabled.** + > + > _This PR is set to merge automatically when all requirements are met._ + + - name: Remove Auto-Merge Notice + if: github.event.action == 'auto_merge_disabled' + uses: bcgov/action-pr-description-add@14338bfe0278ead273b3c1189e5aa286ff6709c4 # v2.0.0 + with: + add_markdown: | + > [!NOTE] + > **Auto-merge may have been disabled. Please check the PR status to confirm.** + + # ---------- Remove auto-merge labels when native auto-merge is disabled ---------- + remove-labels: + name: Remove auto-merge labels + if: > + github.event.action == 'auto_merge_disabled' + && ( + contains(github.event.pull_request.labels.*.name, 'auto-merge') + || contains(github.event.pull_request.labels.*.name, 'auto-merge/bypass-ci-checks') + ) + permissions: {} + runs-on: ubuntu-24.04 + steps: + - name: Authenticate as 'octavia-bot-hoard' GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-app-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} + + - name: Remove auto-merge label + if: contains(github.event.pull_request.labels.*.name, 'auto-merge') + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: > + gh pr edit "$PR_NUMBER" + --repo "${{ github.repository }}" + --remove-label "auto-merge" + + - name: Remove bypass-ci-checks label + if: contains(github.event.pull_request.labels.*.name, 'auto-merge/bypass-ci-checks') + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: > + gh pr edit "$PR_NUMBER" + --repo "${{ github.repository }}" + --remove-label "auto-merge/bypass-ci-checks" + + - name: Post notice comment + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: > + gh pr comment "$PR_NUMBER" + --repo "${{ github.repository }}" + --body "Auto-merge labels removed because native auto-merge was disabled on this PR. Re-add the label to re-enable." diff --git a/.github/workflows/auto-upgrade-certified-connectors-cdk.yml b/.github/workflows/auto-upgrade-certified-connectors-cdk.yml index 3b0928b27dbf..3c9cf93ef654 100644 --- a/.github/workflows/auto-upgrade-certified-connectors-cdk.yml +++ b/.github/workflows/auto-upgrade-certified-connectors-cdk.yml @@ -20,7 +20,7 @@ jobs: connectors: ${{ steps.list-connectors.outputs.connectors }} steps: - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Needed for when airbyte-enterprise calls this workflow submodules: true @@ -46,7 +46,7 @@ jobs: max-parallel: 5 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 id: app-token with: owner: "airbytehq" @@ -63,7 +63,7 @@ jobs: git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com' - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token }} # Needed for when airbyte-enterprise calls this workflow @@ -73,13 +73,13 @@ jobs: run: sudo snap install yq - name: Setup Java - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Setup Gradle - uses: gradle/actions/setup-gradle@80e941e61874822d2a89974089c4915748e8f4b7 # v4 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Run upgradeCdk for ${{ matrix.connector }} id: upgrade-cdk @@ -114,7 +114,7 @@ jobs: - name: Create Pull Request id: create-pr if: steps.check-changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ steps.app-token.outputs.token }} commit-message: "chore: upgrade bulk CDK for ${{ matrix.connector }}" diff --git a/.github/workflows/auto_merge.yml b/.github/workflows/auto_merge.yml deleted file mode 100644 index 8adff5140023..000000000000 --- a/.github/workflows/auto_merge.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Auto merge connector PRs Cron - -on: - schedule: - # Every 2 hours on the hour. - - cron: "0 */2 * * *" - workflow_dispatch: -jobs: - run_auto_merge: - name: Run auto-merge - runs-on: ubuntu-24.04 - steps: - - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 - id: get-app-token - with: - owner: "airbytehq" - repositories: "airbyte" - app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} - private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} - - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.11" - check-latest: true - update-environment: true - - name: Install and configure Poetry - uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 - with: - version: 1.8.5 - - name: Run auto merge - shell: bash - working-directory: airbyte-ci/connectors/auto_merge - env: - GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }} - AUTO_MERGE_PRODUCTION: ${{ vars.ENABLE_CONNECTOR_AUTO_MERGE }} - run: | - poetry install - poetry run auto-merge diff --git a/.github/workflows/auto_merge_notification.yml b/.github/workflows/auto_merge_notification.yml deleted file mode 100644 index 2a63dcf66405..000000000000 --- a/.github/workflows/auto_merge_notification.yml +++ /dev/null @@ -1,31 +0,0 @@ -# When a PR is has the auto-merge feature enabled or disabled, this workflow adds or updates -# warning text at the bottom of the PR description. - -name: "Add Auto-Merge Notification Text" -on: - pull_request: - types: [auto_merge_enabled, auto_merge_disabled] - -jobs: - update-description: - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Add Auto-Merge Notice - if: github.event.action == 'auto_merge_enabled' - uses: bcgov/action-pr-description-add@14338bfe0278ead273b3c1189e5aa286ff6709c4 # v2.0.0 - with: - add_markdown: | - > [!IMPORTANT] - > **Auto-merge enabled.** - > - > _This PR is set to merge automatically when all requirements are met._ - - - name: Remove Auto-Merge Notice - if: github.event.action == 'auto_merge_disabled' - uses: bcgov/action-pr-description-add@14338bfe0278ead273b3c1189e5aa286ff6709c4 # v2.0.0 - with: - add_markdown: | - > [!NOTE] - > **Auto-merge may have been disabled. Please check the PR status to confirm.** diff --git a/.github/workflows/autodoc.yml b/.github/workflows/autodoc.yml index dbb2bde5ff75..9cfa7c0bc0be 100644 --- a/.github/workflows/autodoc.yml +++ b/.github/workflows/autodoc.yml @@ -33,7 +33,7 @@ jobs: steps: # Step 1: Get the pushed code - name: Checkout pushed code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Full history needed for comprehensive analysis diff --git a/.github/workflows/build-connector-images-command.yml b/.github/workflows/build-connector-images-command.yml index 7519d9516bb3..e2b8338e3edc 100644 --- a/.github/workflows/build-connector-images-command.yml +++ b/.github/workflows/build-connector-images-command.yml @@ -58,7 +58,7 @@ jobs: > [Check job output.](${{ steps.job-vars.outputs.run-url }}) - name: Repo Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.repository }} ref: ${{ inputs.gitref || '' }} diff --git a/.github/workflows/bump-progressive-rollout-version-command.yml b/.github/workflows/bump-progressive-rollout-version-command.yml index 541973020ced..a3f1f5f4c62c 100644 --- a/.github/workflows/bump-progressive-rollout-version-command.yml +++ b/.github/workflows/bump-progressive-rollout-version-command.yml @@ -11,11 +11,6 @@ on: description: "Optional. The comment-id of the slash command. Used to update the comment with the status." required: false - type: - description: "The type of bump to perform. One of 'major', 'minor', or 'patch'." - required: false - default: "patch" - changelog: description: "Optional. The comment to add to the changelog. If not provided, the PR title will be used." required: false @@ -31,6 +26,11 @@ on: description: "Ref (Ignored)" required: false +permissions: + contents: read + issues: write + pull-requests: write + run-name: "Bump connector version for progressive rollout in PR: #${{ github.event.inputs.pr }}" concurrency: group: ${{ github.workflow }}-${{ github.event.inputs.pr }} @@ -39,140 +39,12 @@ concurrency: jobs: bump-progressive-rollout-version: - name: "Bump version of connectors for progressive rollout in this PR" - runs-on: ubuntu-24.04 - steps: - - name: Get job variables - id: job-vars - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr }}) - echo "repo=$(echo "$PR_JSON" | jq -r .head.repo.full_name)" >> $GITHUB_OUTPUT - echo "branch=$(echo "$PR_JSON" | jq -r .head.ref)" >> $GITHUB_OUTPUT - echo "pr_title=$(echo "$PR_JSON" | jq -r .title)" >> $GITHUB_OUTPUT - echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - - # NOTE: We still use a PAT here (rather than a GitHub App) because the workflow needs - # permissions to add commits to our main repo as well as forks. This will only work on - # forks if the user installs the app into their fork. Until we document this as a clear - # path, we will have to keep using the PAT. - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - repository: ${{ steps.job-vars.outputs.repo }} - ref: ${{ steps.job-vars.outputs.branch }} - fetch-depth: 1 - # Important that token is a PAT so that CI checks are triggered again. - # Without this we would be forever waiting on required checks to pass. - token: ${{ secrets.GH_PAT_APPROVINGTON_OCTAVIA }} - - - name: Append comment with job run link - # If comment-id is not provided, this will create a new - # comment with the job run link. - id: first-comment-action - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - comment-id: ${{ github.event.inputs.comment-id }} - issue-number: ${{ github.event.inputs.pr }} - body: | - - > **Progressive Rollout Version Bump Started** - > - > This will bump the connector version with an RC suffix and enable progressive rollout. - > [Check job output.][1] - - [1]: ${{ steps.job-vars.outputs.run-url }} - - - name: Log changelog source - run: | - if [ -n "${{ github.event.inputs.changelog }}" ]; then - echo "Using user-provided changelog: ${{ github.event.inputs.changelog }}" - else - echo "Using PR title as changelog: ${{ steps.job-vars.outputs.pr_title }}" - fi - - - name: Run airbyte-ci connectors --modified bump-version with --rc flag - uses: ./.github/actions/run-airbyte-ci - continue-on-error: true - with: - context: "manual" - gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - github_token: ${{ secrets.GH_PAT_APPROVINGTON_OCTAVIA }} - git_repo_url: https://github.com/${{ steps.job-vars.outputs.repo }}.git - subcommand: | - connectors --modified bump-version \ - ${{ github.event.inputs.type }} \ - "${{ github.event.inputs.changelog != '' && github.event.inputs.changelog || steps.job-vars.outputs.pr_title }}" \ - --pr-number ${{ github.event.inputs.pr }} \ - --rc - - # This is helpful in the case that we change a previously committed generated file to be ignored by git. - - name: Remove any files that have been gitignored - run: git ls-files -i -c --exclude-from=.gitignore | xargs -r git rm --cached - - # Check for changes in git - - name: Check for changes - id: git-diff - run: | - git diff --quiet && echo "No changes to commit" || echo "changes=true" >> $GITHUB_OUTPUT - shell: bash - - # Commit changes (if any) - - name: Commit changes - id: commit-step - if: steps.git-diff.outputs.changes == 'true' - run: | - git config --global user.name "Octavia Squidington III" - git config --global user.email "octavia-squidington-iii@users.noreply.github.com" - git add . - git commit -m "chore: bump-version for progressive rollout" - echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - - name: Push changes to '(${{ steps.job-vars.outputs.repo }})' - if: steps.git-diff.outputs.changes == 'true' - run: | - git remote add contributor https://github.com/${{ steps.job-vars.outputs.repo }}.git - git push contributor HEAD:'${{ steps.job-vars.outputs.branch }}' - - - name: Append success comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: steps.git-diff.outputs.changes == 'true' - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: hooray - body: | - > **Progressive Rollout Version Bump: SUCCESS** - > - > The connector version has been bumped with an RC suffix (e.g., `X.Y.Z-rc.1`). - > Changes applied successfully. (${{ steps.commit-step.outputs.sha }}) - > - > **Next steps:** - > 1. Merge this PR to publish the RC version - > 2. Monitor the progressive rollout in production - > 3. When ready to promote, use the `finalize_rollout` workflow with `action=promote` - > 4. If issues arise, use `action=rollback` instead - - - name: Append success comment (no-op) - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: steps.git-diff.outputs.changes != 'true' - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: "-1" - body: | - > Job completed successfully (no changes detected). - > - > This might happen if: - > - The connector already has an RC version - > - No modified connectors were detected in this PR - - - name: Append failure comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: failure() - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: confused - body: | - > Job failed. Check the [workflow logs](${{ steps.job-vars.outputs.run-url }}) for details. + # Delegate to the bump-version workflow with type=rc. + uses: ./.github/workflows/bump-version-command.yml + with: + pr: ${{ github.event.inputs.pr }} + comment-id: ${{ github.event.inputs.comment-id }} + type: "rc" + changelog: ${{ github.event.inputs.changelog }} + secrets: + GH_PAT_APPROVINGTON_OCTAVIA: ${{ secrets.GH_PAT_APPROVINGTON_OCTAVIA }} diff --git a/.github/workflows/bump-version-command.yml b/.github/workflows/bump-version-command.yml index 011cbddf6080..9d0277c86893 100644 --- a/.github/workflows/bump-version-command.yml +++ b/.github/workflows/bump-version-command.yml @@ -1,6 +1,30 @@ name: Bump versions for connectors in a PR on: + workflow_call: + inputs: + pr: + description: "Pull request number. This PR will be referenced in the changelog line." + type: string + required: false + comment-id: + description: "Optional. The comment-id of the slash command. Used to update the comment with the status." + type: string + required: false + type: + description: "The type of bump to perform. One of 'major', 'minor', or 'patch'." + type: string + required: false + default: "patch" + changelog: + description: "Optional. The comment to add to the changelog. If not provided, the PR title will be used." + type: string + required: false + default: "" + secrets: + GH_PAT_APPROVINGTON_OCTAVIA: + required: true + workflow_dispatch: inputs: pr: @@ -32,9 +56,14 @@ on: description: "Ref (Ignored)" required: false -run-name: "Bump connector versions in PR: #${{ github.event.inputs.pr }}" +permissions: + contents: read + issues: write + pull-requests: write + +run-name: "Bump connector versions in PR: #${{ inputs.pr }}" concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.pr }} + group: ${{ github.workflow }}-${{ inputs.pr }} # Cancel any previous runs on the same branch if they are still in progress cancel-in-progress: true @@ -47,9 +76,10 @@ jobs: id: job-vars env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ inputs.pr }} shell: bash run: | - PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr }}) + PR_JSON=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER") echo "repo=$(echo "$PR_JSON" | jq -r .head.repo.full_name)" >> $GITHUB_OUTPUT echo "branch=$(echo "$PR_JSON" | jq -r .head.ref)" >> $GITHUB_OUTPUT echo "pr_title=$(echo "$PR_JSON" | jq -r .title)" >> $GITHUB_OUTPUT @@ -60,7 +90,7 @@ jobs: # forks if the user installs the app into their fork. Until we document this as a clear # path, we will have to keep using the PAT. - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ steps.job-vars.outputs.repo }} ref: ${{ steps.job-vars.outputs.branch }} @@ -75,36 +105,76 @@ jobs: id: first-comment-action uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 with: - comment-id: ${{ github.event.inputs.comment-id }} - issue-number: ${{ github.event.inputs.pr }} + comment-id: ${{ inputs.comment-id }} + issue-number: ${{ inputs.pr }} body: | > Bump Version job started... [Check job output.][1] [1]: ${{ steps.job-vars.outputs.run-url }} - - name: Log changelog source + - name: Install uv + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + + - name: Install airbyte-ops CLI + run: uv tool install airbyte-internal-ops + + - name: Detect modified connectors + id: detect-connectors + env: + PR_NUMBER: ${{ inputs.pr }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - if [ -n "${{ github.event.inputs.changelog }}" ]; then - echo "Using user-provided changelog: ${{ github.event.inputs.changelog }}" + CONNECTORS=$( + airbyte-ops local connector list \ + --repo-path "$GITHUB_WORKSPACE" \ + --modified-only \ + --pr "$PR_NUMBER" \ + --gh-token "$GH_TOKEN" \ + --output-format lines + ) + + if [ -z "$CONNECTORS" ]; then + echo "No modified connectors found in PR #$PR_NUMBER." else - echo "Using PR title as changelog: ${{ steps.job-vars.outputs.pr_title }}" + echo "Modified connectors:" + echo "$CONNECTORS" fi - - name: Run airbyte-ci connectors --modified bump-version - uses: ./.github/actions/run-airbyte-ci - continue-on-error: true - with: - context: "manual" - gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - github_token: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }} - git_repo_url: https://github.com/${{ steps.job-vars.outputs.repo }}.git - subcommand: | - connectors --modified bump-version \ - ${{ github.event.inputs.type }} \ - "${{ github.event.inputs.changelog != '' && github.event.inputs.changelog || steps.job-vars.outputs.pr_title }}" \ - --pr-number ${{ github.event.inputs.pr }} + # Pass the connector list to the next step via GITHUB_OUTPUT. + # Only set the multiline output when CONNECTORS is non-empty so that + # the downstream `if:` reliably evaluates to false for empty lists. + if [ -n "$CONNECTORS" ]; then + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "connectors<<$EOF" >> $GITHUB_OUTPUT + echo "$CONNECTORS" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + fi + + - name: Bump versions for modified connectors + if: steps.detect-connectors.outputs.connectors != '' + env: + BUMP_TYPE: ${{ inputs.type }} + CHANGELOG_MSG: ${{ inputs.changelog }} + PR_TITLE: ${{ steps.job-vars.outputs.pr_title }} + PR_NUMBER: ${{ inputs.pr }} + CONNECTORS: ${{ steps.detect-connectors.outputs.connectors }} + run: | + MSG="${CHANGELOG_MSG:-$PR_TITLE}" + echo "Changelog message: $MSG" + echo "Bump type: $BUMP_TYPE" + echo "" + + echo "$CONNECTORS" | while IFS= read -r connector; do + [ -z "$connector" ] && continue + echo "--- Bumping version for $connector ---" + airbyte-ops local connector bump-version \ + --name "$connector" \ + --repo-path "$GITHUB_WORKSPACE" \ + --bump-type "$BUMP_TYPE" \ + --changelog-message "$MSG" \ + --pr-number "$PR_NUMBER" + done # This is helpful in the case that we change a previously committed generated file to be ignored by git. - name: Remove any files that have been gitignored @@ -121,11 +191,13 @@ jobs: - name: Commit changes id: commit-step if: steps.git-diff.outputs.changes == 'true' + env: + BUMP_TYPE: ${{ inputs.type }} run: | git config --global user.name "Octavia Squidington III" git config --global user.email "octavia-squidington-iii@users.noreply.github.com" git add . - git commit -m "chore: bump-version ${{ github.event.inputs.bump-type }}" + git commit -m "chore: bump-version $BUMP_TYPE" echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - name: Push changes to '(${{ steps.job-vars.outputs.repo }})' diff --git a/.github/workflows/cdk-destination-connector-compatibility-test.yml b/.github/workflows/cdk-destination-connector-compatibility-test.yml index 7ffe59065625..481c22b1f6fb 100644 --- a/.github/workflows/cdk-destination-connector-compatibility-test.yml +++ b/.github/workflows/cdk-destination-connector-compatibility-test.yml @@ -17,7 +17,7 @@ jobs: connectors-matrix: ${{ steps.generate-matrix.outputs.connectors_matrix }} steps: - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for when airbyte-enterprise calls this workflow @@ -47,25 +47,25 @@ jobs: steps: - name: Checkout Airbyte if: matrix.connector - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for airbyte-enterprise connectors (no-op otherwise) - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: matrix.connector with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: matrix.connector with: gradle-version: "8.14" - name: Install the latest version of uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe if: matrix.connector @@ -117,7 +117,7 @@ jobs: - name: Authenticate as GitHub App if: always() && steps.check-move-notify.outputs.should-notify == 'true' - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 id: app-token with: owner: "airbytehq" diff --git a/.github/workflows/cdk-source-connector-compatibility-test.yml b/.github/workflows/cdk-source-connector-compatibility-test.yml index 92e216316d57..215cb02a6283 100644 --- a/.github/workflows/cdk-source-connector-compatibility-test.yml +++ b/.github/workflows/cdk-source-connector-compatibility-test.yml @@ -17,7 +17,7 @@ jobs: connectors-matrix: ${{ steps.generate-matrix.outputs.connectors_matrix }} steps: - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for when airbyte-enterprise calls this workflow @@ -47,25 +47,25 @@ jobs: steps: - name: Checkout Airbyte if: matrix.connector - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for airbyte-enterprise connectors (no-op otherwise) - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: matrix.connector with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: matrix.connector with: gradle-version: "8.14" - name: Install the latest version of uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe if: matrix.connector diff --git a/.github/workflows/changelog_update_check.yml b/.github/workflows/changelog_update_check.yml index f80dddd3f9ce..f16149d141ba 100644 --- a/.github/workflows/changelog_update_check.yml +++ b/.github/workflows/changelog_update_check.yml @@ -11,7 +11,7 @@ jobs: steps: - name: CDK Changes id: cdk-changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 #v3.0.2 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d #v4.0.1 with: filters: | base_version: diff --git a/.github/workflows/community-pr-permissions-check.yml b/.github/workflows/community-pr-permissions-check.yml index 7f424f839a36..eb462c4664ca 100644 --- a/.github/workflows/community-pr-permissions-check.yml +++ b/.github/workflows/community-pr-permissions-check.yml @@ -21,7 +21,7 @@ jobs: if: github.event.pull_request.head.repo.fork == true steps: - name: Checkout Repo - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: master repository: airbytehq/airbyte diff --git a/.github/workflows/connector-ci-checks.yml b/.github/workflows/connector-ci-checks.yml index fa528fa7810d..e14404f293f8 100644 --- a/.github/workflows/connector-ci-checks.yml +++ b/.github/workflows/connector-ci-checks.yml @@ -50,7 +50,7 @@ jobs: steps: - name: Checkout Current Branch id: checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -78,7 +78,7 @@ jobs: git fetch --quiet upstream master - id: cdk-changes - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 with: # Note: expressions within a filter are OR'ed filters: | @@ -139,7 +139,7 @@ jobs: - name: Checkout Airbyte if: matrix.connector id: checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -147,7 +147,7 @@ jobs: fetch-depth: 1 # Java deps - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: matrix.connector with: distribution: zulu @@ -156,7 +156,7 @@ jobs: # The default behaviour is read-only on PR branches and read/write on master. # See https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#using-the-cache-read-only. - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: matrix.connector with: gradle-version: "8.14" @@ -164,7 +164,7 @@ jobs: # TODO: We can delete this step once Airbyte-CI is removed from Java integration tests. - name: Set up Python (For Airbyte-CI) if: matrix.connector - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" check-latest: true @@ -172,7 +172,7 @@ jobs: - name: Install the latest version of uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe if: matrix.connector @@ -230,7 +230,7 @@ jobs: - name: Checkout Airbyte if: matrix.connector id: checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -240,7 +240,7 @@ jobs: # Python deps - name: Set up Python if: matrix.connector - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" check-latest: true @@ -254,7 +254,7 @@ jobs: - name: Install the latest version of uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install system dependencies if: matrix.connector @@ -331,7 +331,7 @@ jobs: steps: - name: Checkout Airbyte if: matrix.connector - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -339,14 +339,14 @@ jobs: fetch-depth: 1 # Java deps - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: matrix.connector with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: matrix.connector with: cache-read-only: false @@ -356,7 +356,7 @@ jobs: # Python deps - name: Set up Python if: matrix.connector - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" check-latest: true @@ -369,7 +369,7 @@ jobs: - name: Install the latest version of uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install system dependencies if: matrix.connector @@ -422,7 +422,7 @@ jobs: steps: - name: Checkout Airbyte if: matrix.connector - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -430,7 +430,7 @@ jobs: fetch-depth: 0 - name: Install uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe if: matrix.connector @@ -479,7 +479,7 @@ jobs: steps: - name: Checkout Airbyte if: matrix.connector - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref || github.ref_name }} @@ -488,18 +488,18 @@ jobs: - name: Install uv if: matrix.connector - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 # Java deps (needed for JVM connector Docker image builds via Gradle) # TODO: Consider making this conditional on connector language to skip for non-JVM connectors. - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: matrix.connector with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: matrix.connector with: gradle-version: "8.14" @@ -586,7 +586,7 @@ jobs: # be available, so the following steps will be a no-op. - name: Authenticate as GitHub App - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token if: always() && !cancelled() continue-on-error: true @@ -598,7 +598,7 @@ jobs: if: > always() && !cancelled() && steps.get-app-token.outcome == 'success' - uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 # v2.0.0 + uses: LouisBrunner/checks-action@dfcbcf801bff1ea7f1414824fc28f2cd697b35da # v3.0.0 with: name: "Connector CI Checks Summary" # << Name of the 'Required' check sha: ${{ needs.generate-matrix.outputs.commit-sha }} diff --git a/.github/workflows/connector-image-build.yml b/.github/workflows/connector-image-build.yml index 5c6b613d18d7..a9f8d9cb102a 100644 --- a/.github/workflows/connector-image-build.yml +++ b/.github/workflows/connector-image-build.yml @@ -42,14 +42,14 @@ jobs: url: https://ghcr.io/airbytehq/${{ inputs.connector }} steps: - name: Checkout Current Branch - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.event.pull_request.head.repo.full_name }} ref: ${{ inputs.gitref || github.head_ref }} fetch-depth: 1 - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe run: | @@ -74,14 +74,14 @@ jobs: echo "image-build-num-tag=${IMAGE_PR_NUM_TAG}-build${{ github.run_number }}" | tee -a $GITHUB_OUTPUT # Java deps - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 if: ${{ steps.vars.outputs.connector-language == 'java' }} with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 if: ${{ steps.vars.outputs.connector-language == 'java' }} with: cache-read-only: false @@ -95,7 +95,7 @@ jobs: ./gradlew :airbyte-integrations:connectors:${{ inputs.connector }}:distTar - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/connectors-cdk-version-check.yml b/.github/workflows/connectors-cdk-version-check.yml index 2ce090236c8f..cc9a80ef1ab4 100644 --- a/.github/workflows/connectors-cdk-version-check.yml +++ b/.github/workflows/connectors-cdk-version-check.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Detect Connector CDK Version Changes id: connector-cdk-version-changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 #v3.0.2 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d #v4.0.1 with: filters: | cdk-version: @@ -27,18 +27,18 @@ jobs: if: needs.detect-changes.outputs.changed == 'true' steps: - name: Checkout Airbyte - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for airbyte-enterprise connectors (no-op otherwise) - name: Setup Java - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Setup Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 #v4.4.4 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: gradle-version: "8.14" diff --git a/.github/workflows/connectors-up-to-date.yml b/.github/workflows/connectors-up-to-date.yml new file mode 100644 index 000000000000..8300dbf0849d --- /dev/null +++ b/.github/workflows/connectors-up-to-date.yml @@ -0,0 +1,370 @@ +name: Connectors up-to-date +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +on: + schedule: + # Runs every Tuesday at 03:00 UTC (8 PM Pacific Monday evening) + - cron: "0 3 * * 2" + workflow_dispatch: + inputs: + connectors-options: + description: >- + Override: pass these flags directly to `airbyte-ops local connector list` + instead of the default source/destination selection. + Examples: `--connectors-filter=source-github` (one connector), + `--connectors-filter=source-github,source-stripe` (multiple by name), + `--certified-only --connector-type=destination` (custom query). + default: "" +jobs: + generate_matrix: + name: Generate matrix + runs-on: ubuntu-24.04 + outputs: + generated_matrix: ${{ steps.generate_matrix.outputs.generated_matrix }} + steps: + - name: Checkout Airbyte + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install uv + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + + - name: Install airbyte-ops CLI + run: uv tool install airbyte-internal-ops + + # --- Connector selection --- + # When connectors-options is provided, use it directly as an override. + # Otherwise, use the three-call pattern: sources (all support levels) + + # destinations (certified only), then combine. + + # Override path: use connectors-options as-is + - name: "[override] List connectors from manual input" + id: list-override + if: github.event.inputs.connectors-options != '' + env: + CONNECTOR_OPTIONS: ${{ github.event.inputs.connectors-options }} + run: | + MATRIX=$( + airbyte-ops local connector list \ + --repo-path "$GITHUB_WORKSPACE" \ + $CONNECTOR_OPTIONS \ + --output-format=json-gh-matrix + ) + echo "Matrix: $MATRIX" + # Use heredoc delimiter for multiline JSON + echo "generated_matrix<> $GITHUB_OUTPUT + echo "$MATRIX" >> $GITHUB_OUTPUT + echo "MATRIX_EOF" >> $GITHUB_OUTPUT + + # Default path — Call 1: Sources (all support levels) + - name: List source connectors + id: list-sources + if: github.event.inputs.connectors-options == '' || github.event_name == 'schedule' + run: | + SOURCES=$( + airbyte-ops local connector list \ + --repo-path "$GITHUB_WORKSPACE" \ + --connector-type=source \ + --language python --language low-code --language manifest-only \ + --exclude-connectors=source-declarative-manifest \ + --output-format=csv + ) + echo "Sources: $SOURCES" + echo "sources=$SOURCES" | tee -a $GITHUB_OUTPUT + + # Default path — Call 2: Destinations (certified only) + - name: List destination connectors + id: list-destinations + if: github.event.inputs.connectors-options == '' || github.event_name == 'schedule' + run: | + DESTINATIONS=$( + airbyte-ops local connector list \ + --repo-path "$GITHUB_WORKSPACE" \ + --connector-type=destination \ + --certified-only \ + --language python --language low-code --language manifest-only \ + --output-format=csv + ) + echo "Destinations: $DESTINATIONS" + echo "destinations=$DESTINATIONS" | tee -a $GITHUB_OUTPUT + + # Default path — Call 3: Combine both sets into GH Actions matrix + - name: Generate matrix from source + destination lists + id: list-default + if: github.event.inputs.connectors-options == '' || github.event_name == 'schedule' + run: | + MATRIX=$( + airbyte-ops local connector list \ + --repo-path "$GITHUB_WORKSPACE" \ + --connectors-filter="${{ steps.list-sources.outputs.sources }},${{ steps.list-destinations.outputs.destinations }}" \ + --output-format=json-gh-matrix + ) + echo "Matrix: $MATRIX" + # Use heredoc delimiter for multiline JSON + echo "generated_matrix<> $GITHUB_OUTPUT + echo "$MATRIX" >> $GITHUB_OUTPUT + echo "MATRIX_EOF" >> $GITHUB_OUTPUT + + # Merge: pick whichever path ran, and validate non-empty + - name: Set final matrix output + id: generate_matrix + env: + OVERRIDE_MATRIX: ${{ steps.list-override.outputs.generated_matrix }} + DEFAULT_MATRIX: ${{ steps.list-default.outputs.generated_matrix }} + run: | + FINAL_MATRIX="${OVERRIDE_MATRIX:-$DEFAULT_MATRIX}" + if [ -z "$FINAL_MATRIX" ]; then + echo "::error::Matrix generation produced empty output. Check CLI commands above for errors." + exit 1 + fi + echo "Final matrix: $FINAL_MATRIX" + # Use heredoc delimiter for multiline JSON + echo "generated_matrix<> $GITHUB_OUTPUT + echo "$FINAL_MATRIX" >> $GITHUB_OUTPUT + echo "MATRIX_EOF" >> $GITHUB_OUTPUT + + run_connectors_up_to_date: + needs: generate_matrix + name: Connectors up-to-date (${{ matrix.connector }}) + runs-on: connector-up-to-date-medium + continue-on-error: true + strategy: + matrix: ${{ fromJson(needs.generate_matrix.outputs.generated_matrix) }} + permissions: + pull-requests: write + contents: write + env: + CONNECTOR_NAME: ${{ matrix.connector }} + CONNECTOR_DIR: ${{ matrix.connector_dir }} + CONNECTOR_LANGUAGE: ${{ matrix.connector_language }} + steps: + - name: Set today's date + run: echo "TODAY=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV" + + - name: Authenticate as 'octavia-bot-hoard' GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-app-token + with: + owner: "airbytehq" + repositories: ${{ inputs.repo || github.event.repository.name }} + app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} + + # Checkout with the App token so git push authenticates as the + # GitHub App. Pushes made with GITHUB_TOKEN do not trigger + # pull_request workflows; pushes made with a GitHub App token do. + - name: Checkout Airbyte + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + token: ${{ steps.get-app-token.outputs.token }} + + - name: Get GitHub App User ID + id: get-user-id + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + run: echo "user-id=$(gh api "/users/${{ steps.get-app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + + - name: Configure git author + run: | + git config --global user.name '${{ steps.get-app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.get-app-token.outputs.app-slug }}[bot]@users.noreply.github.com' + + - name: Install uv + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + + - name: Install airbyte-ops CLI + run: uv tool install airbyte-internal-ops + + - name: Docker login + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + # --- Step 1: Set up Python + Poetry --- + # bump-deps runs `poetry update --lock` internally, so Poetry must be available. + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.11" + + - name: Install and configure Poetry + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 + with: + version: 2.3.3 + + # --- Step 2: Bump dependencies (CDK + external) --- + - name: Bump dependencies + id: bump-deps + run: | + BUMP_DEPS_OUTPUT=$( + airbyte-ops local connector bump-deps \ + --name "$CONNECTOR_NAME" \ + --repo-path "$GITHUB_WORKSPACE" + ) + echo "$BUMP_DEPS_OUTPUT" + DEPS_UPDATED=$(echo "$BUMP_DEPS_OUTPUT" | jq -r '.updated') + echo "updated=$DEPS_UPDATED" | tee -a $GITHUB_OUTPUT + OUTDATED_PKGS=$(echo "$BUMP_DEPS_OUTPUT" | jq -r '(.outdated_packages // []) | join(", ")') + echo "outdated_packages=$OUTDATED_PKGS" | tee -a $GITHUB_OUTPUT + + # --- Step 3: Update base image --- + - name: Update base image in metadata.yaml + id: bump-base-image + run: | + BUMP_IMAGE_OUTPUT=$( + airbyte-ops local connector bump-base-image \ + --name "$CONNECTOR_NAME" \ + --repo-path "$GITHUB_WORKSPACE" + ) + echo "$BUMP_IMAGE_OUTPUT" + IMAGE_UPDATED=$(echo "$BUMP_IMAGE_OUTPUT" | jq -r '.updated') + echo "updated=$IMAGE_UPDATED" | tee -a $GITHUB_OUTPUT + + # --- Step 4: Check for changes --- + # Use structured output from CLI commands instead of git diff to avoid + # false positives from Poetry lock file reformatting across versions. + - name: Check for changes + id: check-changes + env: + DEPS_UPDATED: ${{ steps.bump-deps.outputs.updated }} + IMAGE_UPDATED: ${{ steps.bump-base-image.outputs.updated }} + run: | + echo "bump-deps updated: $DEPS_UPDATED" + echo "bump-base-image updated: $IMAGE_UPDATED" + if [ "$DEPS_UPDATED" = "true" ] || [ "$IMAGE_UPDATED" = "true" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Changes detected for $CONNECTOR_NAME" + git diff --stat + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "No changes detected for $CONNECTOR_NAME (CLI reports no updates)" + fi + + # --- Step 5: Create or update PR --- + # peter-evans/create-pull-request detects working-tree changes, commits them, + # and creates (or updates) a draft PR. We submit it as a draft so we can push + # a follow-up changelog commit before marking it ready for review. + - name: Create or update PR + id: create-pr + if: steps.check-changes.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + with: + token: ${{ steps.get-app-token.outputs.token }} + commit-message: "deps(${{ env.CONNECTOR_NAME }}): update dependencies [skip ci]" + branch: "up-to-date/${{ env.CONNECTOR_NAME }}" + delete-branch: true + title: "deps(${{ env.CONNECTOR_NAME }}): 🐙 update dependencies [${{ env.TODAY }}]" + body: | + Updates dependencies for `${{ env.CONNECTOR_NAME }}`. + + **Updated packages:** ${{ steps.bump-deps.outputs.outdated_packages || 'N/A' }} + + 🤖 Generated by [automated workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + labels: | + area/connectors + auto-merge + draft: true + + # --- Step 6: Check out PR branch --- + # peter-evans/create-pull-request commits dependency changes to the PR branch + # but leaves the working tree dirty on master. We must reset and switch to the + # PR branch before running the version bump (which needs the PR number). + - name: Check out PR branch + if: steps.check-changes.outputs.has_changes == 'true' + env: + PR_BRANCH: ${{ steps.create-pr.outputs.pull-request-branch }} + run: | + git checkout -- . + git fetch --depth=1 origin "$PR_BRANCH" + git checkout "$PR_BRANCH" + + # --- Step 7: Bump version + changelog (needs PR number) --- + # This runs after PR creation so we can reference the PR number in the changelog. + - name: Bump connector version (patch) with changelog + if: steps.check-changes.outputs.has_changes == 'true' + env: + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + run: | + airbyte-ops local connector bump-version \ + --name "$CONNECTOR_NAME" \ + --repo-path "$GITHUB_WORKSPACE" \ + --bump-type patch \ + --changelog-message "Update dependencies" \ + --pr-number "$PR_NUMBER" + + # --- Step 8: Mark PR ready for review --- + # Mark ready BEFORE pushing the changelog commit so that the push + # triggers pull_request/synchronize CI workflows (drafts skip most CI). + - name: Mark PR as ready for review + if: steps.check-changes.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + run: gh pr ready "$PR_NUMBER" + + # --- Step 9: Push changelog commit to the PR branch --- + # bump-version modifies files in both the connector dir and the + # docs changelog (docs/integrations/…), so we stage everything. + - name: Push changelog commit to PR branch + if: steps.check-changes.outputs.has_changes == 'true' + env: + PR_BRANCH: ${{ steps.create-pr.outputs.pull-request-branch }} + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + run: | + git add "$CONNECTOR_DIR" docs/ + git diff --cached --stat + git commit -m "deps($CONNECTOR_NAME): bump version and update changelog" + git push origin "$PR_BRANCH" + + # --- Step 10: Enable auto-merge --- + - name: Enable auto-merge on PR + if: steps.check-changes.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + run: gh pr merge "$PR_NUMBER" --squash --auto + + # --- Step 11: Apply bot approval --- + # Use a different bot identity (admin) to approve, because the hoard bot + # that created the PR cannot approve its own PRs. + - name: Authenticate as 'octavia-bot-admin' GitHub App + if: steps.check-changes.outputs.has_changes == 'true' + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-admin-token + with: + owner: "airbytehq" + repositories: ${{ inputs.repo || github.event.repository.name }} + app-id: ${{ secrets.OCTAVIA_BOT_ADMIN_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_ADMIN_PRIVATE_KEY }} + + - name: Apply bot approval + if: steps.check-changes.outputs.has_changes == 'true' + continue-on-error: true + env: + GH_TOKEN: ${{ steps.get-admin-token.outputs.token }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + run: gh pr review "$PR_NUMBER" --approve --body "Auto-approved by connectors up-to-date workflow." + + # --- Step 12: Write job summary --- + - name: Write step summary + if: always() + env: + PR_URL: ${{ steps.create-pr.outputs.pull-request-url }} + PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }} + HAS_CHANGES: ${{ steps.check-changes.outputs.has_changes }} + DEPS_UPDATED: ${{ steps.bump-deps.outputs.updated }} + IMAGE_UPDATED: ${{ steps.bump-base-image.outputs.updated }} + OUTDATED_PKGS: ${{ steps.bump-deps.outputs.outdated_packages }} + run: | + if [ "$HAS_CHANGES" = "true" ] && [ -n "$PR_URL" ]; then + echo "### ✅ \`$CONNECTOR_NAME\` — PR created" >> $GITHUB_STEP_SUMMARY + echo "- **PR:** [#${PR_NUMBER}](${PR_URL})" >> $GITHUB_STEP_SUMMARY + [ "$DEPS_UPDATED" = "true" ] && echo "- Dependencies updated: $OUTDATED_PKGS" >> $GITHUB_STEP_SUMMARY || true + [ "$IMAGE_UPDATED" = "true" ] && echo "- Base image updated" >> $GITHUB_STEP_SUMMARY || true + elif [ "$HAS_CHANGES" = "true" ]; then + echo "### ❌ \`$CONNECTOR_NAME\` — changes detected but PR creation failed" >> $GITHUB_STEP_SUMMARY + else + echo "### ⏭️ \`$CONNECTOR_NAME\` — no changes (no-op)" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/connectors_insights.yml b/.github/workflows/connectors_insights.yml deleted file mode 100644 index 2be10c889b10..000000000000 --- a/.github/workflows/connectors_insights.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Connectors Insights - -on: - schedule: - - cron: "0 0,12 * * *" # Run every 12 hours UTC - workflow_dispatch: - inputs: - rewrite: - default: false -jobs: - connectors_insights: - name: Connectors Insights generation - runs-on: connector-nightly-xlarge - timeout-minutes: 1440 # 24 hours - steps: - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Docker login - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - - name: Get Dagger Engine Image - uses: ./.github/actions/get-dagger-engine-image - with: - dagger_engine_image: "registry.dagger.io/engine:v0.9.6" - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.11" - - name: Install Poetry - uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 - with: - version: 1.8.5 - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - name: Load cached venv - id: cached-poetry-dependencies - uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3.5.0 - with: - path: .venv - key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry -C airbyte-ci/connectors/connectors_insights install --no-interaction --no-root - - name: Install project - run: poetry -C airbyte-ci/connectors/connectors_insights install --no-interaction - - name: Write Google service account key to file - run: echo "$GCP_SA_KEY" > $HOME/gcp-sa-key.json - env: - GCP_SA_KEY: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - - name: Set GOOGLE_APPLICATION_CREDENTIALS - run: echo "GOOGLE_APPLICATION_CREDENTIALS=$HOME/gcp-sa-key.json" >> $GITHUB_ENV - - name: Run connectors insights - run: | - poetry -C airbyte-ci/connectors/connectors_insights run connectors-insights generate --gcs-uri=gs://prod-airbyte-cloud-connector-metadata-service/connector_insights --connector-directory airbyte-integrations/connectors/ --concurrency 10 ${{ inputs.rewrite == 'true' && '--rewrite' || ''}} diff --git a/.github/workflows/connectors_up_to_date.yml b/.github/workflows/connectors_up_to_date.yml deleted file mode 100644 index 29a051c21548..000000000000 --- a/.github/workflows/connectors_up_to_date.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Connectors up-to-date -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - -on: - schedule: - # Runs every Tuesday at 03:00 UTC (8 PM Pacific Monday evening) - - cron: "0 3 * * 2" - workflow_dispatch: - inputs: - connectors-options: - description: "Options to pass to the 'airbyte-ci connectors' command group." - default: "--language=python --language=low-code --language=manifest-only" -jobs: - generate_matrix: - name: Generate matrix - runs-on: ubuntu-24.04 - outputs: - generated_matrix: ${{ steps.generate_matrix.outputs.generated_matrix }} - steps: - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Run airbyte-ci connectors list [SCHEDULED TRIGGER] - if: github.event_name == 'schedule' - id: airbyte-ci-connectors-list-scheduled - uses: ./.github/actions/run-airbyte-ci - with: - context: "master" - subcommand: 'connectors --language=python --language=low-code --language=manifest-only --metadata-query="\"-rc.\" not in data.dockerImageTag and \"source-declarative-manifest\" not in data.dockerRepository" list --output=selected_connectors.json' - - name: Run airbyte-ci connectors list [MANUAL TRIGGER] - if: github.event_name == 'workflow_dispatch' - id: airbyte-ci-connectors-list-workflow-dispatch - uses: ./.github/actions/run-airbyte-ci - with: - context: "master" - subcommand: 'connectors ${{ github.event.inputs.connectors-options }} --metadata-query="\"-rc.\" not in data.dockerImageTag and \"source-declarative-manifest\" not in data.dockerRepository" list --output=selected_connectors.json' - # We generate a dynamic matrix from the list of selected connectors. - # A matrix is required in this situation because the number of connectors is large and running them all in a single job would exceed the maximum job time limit of 6 hours. - # We use 25 connectors per job, with hopes they can finish within the 1 hours duration of the GitHub App's - # token. - - name: Generate matrix - 25 connectors per job - id: generate_matrix - run: | - matrix=$(jq -c -r '{include: [.[] | "--name=" + .] | to_entries | group_by(.key / 25 | floor) | map(map(.value) | {"connector_names": join(" ")})}' selected_connectors.json) - echo "generated_matrix=$matrix" >> $GITHUB_OUTPUT - - run_connectors_up_to_date: - needs: generate_matrix - name: Connectors up-to-date - runs-on: connector-up-to-date-medium - continue-on-error: true - strategy: - matrix: ${{fromJson(needs.generate_matrix.outputs.generated_matrix)}} - permissions: - pull-requests: write - steps: - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Authenticate as 'octavia-bot-hoard' GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 - id: get-app-token - with: - owner: "airbytehq" - repositories: ${{ inputs.repo || github.event.repository.name }} - app-id: ${{ secrets.OCTAVIA_BOT_HOARD_APP_ID }} - private-key: ${{ secrets.OCTAVIA_BOT_HOARD_PRIVATE_KEY }} - # Token is good for 1 hour - - name: Run airbyte-ci connectors up-to-date [WORKFLOW] - id: airbyte-ci-connectors-up-to-date-workflow-dispatch - uses: ./.github/actions/run-airbyte-ci - with: - context: "master" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_3 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - github_token: ${{ steps.get-app-token.outputs.token }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - s3_build_cache_access_key_id: ${{ secrets.SELF_RUNNER_AWS_ACCESS_KEY_ID }} - s3_build_cache_secret_key: ${{ secrets.SELF_RUNNER_AWS_SECRET_ACCESS_KEY }} - subcommand: "connectors --concurrency=10 ${{ matrix.connector_names}} up-to-date --create-prs --auto-merge" diff --git a/.github/workflows/docker-connector-base-image-tests.yml b/.github/workflows/docker-connector-base-image-tests.yml index 7ea77d64a124..e71426faf0b9 100644 --- a/.github/workflows/docker-connector-base-image-tests.yml +++ b/.github/workflows/docker-connector-base-image-tests.yml @@ -27,10 +27,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 with: filters: | java-images: @@ -57,12 +57,12 @@ jobs: if: needs.detect-changes.outputs.java-images == 'true' steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -104,12 +104,12 @@ jobs: if: needs.detect-changes.outputs.python-images == 'true' steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -159,19 +159,19 @@ jobs: url: https://ghcr.io/airbytehq/${{ matrix.connector }} steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 # Java deps - name: Set up Java - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: zulu java-version: 21 cache: gradle - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: cache-read-only: false cache-write-only: false @@ -183,7 +183,7 @@ jobs: ./gradlew :airbyte-integrations:connectors:${{ matrix.connector }}:distTar - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -220,7 +220,7 @@ jobs: spec - name: Setup uv - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install CDK CLI run: | @@ -264,10 +264,10 @@ jobs: url: https://ghcr.io/airbytehq/${{ matrix.connector }} steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -304,7 +304,7 @@ jobs: spec - name: Setup uv - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install CDK CLI run: | @@ -344,12 +344,12 @@ jobs: url: https://ghcr.io/airbytehq/${{ matrix.connector }} steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -385,7 +385,7 @@ jobs: spec - name: Setup uv - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install CDK CLI run: | diff --git a/.github/workflows/docker-connector-image-publishing.yml b/.github/workflows/docker-connector-image-publishing.yml index 590458a07449..7cea86158bf0 100644 --- a/.github/workflows/docker-connector-image-publishing.yml +++ b/.github/workflows/docker-connector-image-publishing.yml @@ -50,19 +50,19 @@ jobs: url: https://${{ github.event.inputs.repository-root == 'docker.io/airbyte' && 'hub.docker.com/r/airbyte' || github.event.inputs.repository-root }}/${{ github.event.inputs.connector-type }}-connector-base steps: - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Log in to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 if: ${{ github.event.inputs.repository-root == 'docker.io/airbyte' }} with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Log in to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 if: ${{ github.event.inputs.repository-root == 'ghcr.io/airbytehq' }} with: registry: ghcr.io/airbytehq diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index e37d218fce2d..e3d3f09fb682 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -15,7 +15,7 @@ jobs: # The detection method uses the GitHub REST API. - name: Detect Changes id: detect-changes - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 with: filters: | docs: @@ -34,7 +34,7 @@ jobs: if: needs.detect-changes.outputs.changed == 'true' steps: - name: Checkout Current Branch - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name }} @@ -67,7 +67,7 @@ jobs: - name: Install uv if: steps.check-skip.outputs.skip-build != 'true' - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Poe if: steps.check-skip.outputs.skip-build != 'true' @@ -95,7 +95,7 @@ jobs: steps: - name: Checkout Current Branch - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 @@ -110,7 +110,7 @@ jobs: vercel-args: --archive=tgz - name: Authenticate as GitHub App - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -120,7 +120,7 @@ jobs: # If successful, post a check status with the Preview URL as its "details" link - name: Post Custom Check with Preview URL (${{ steps.deploy-vercel.outputs.preview-url }}) - uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 # v2.0.0 + uses: LouisBrunner/checks-action@dfcbcf801bff1ea7f1414824fc28f2cd697b35da # v3.0.0 with: name: "Vercel Preview Deployed" # << Name of the check status: completed diff --git a/.github/workflows/enforce-draft-pr-status.yml b/.github/workflows/enforce-draft-pr-status.yml index ed74e80439cf..fe27e1618bfe 100644 --- a/.github/workflows/enforce-draft-pr-status.yml +++ b/.github/workflows/enforce-draft-pr-status.yml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/finalize_rollout.yml b/.github/workflows/finalize_rollout.yml index af72829293fd..e99590b296f5 100644 --- a/.github/workflows/finalize_rollout.yml +++ b/.github/workflows/finalize_rollout.yml @@ -1,12 +1,11 @@ -name: Finalize connector rollout -# This workflow is called by the backend Temporal workflow "finalizeRollout" -# as a part of its response to calls to `/connector_rollout/manual_finalize`, -# or in response to automatic fallback/finalization. +name: Finalize Progressive Rollout +# Dispatched by the platform's Temporal connector-rollout worker +# (PromoteOrRollbackActivityImpl) after a promote/rollback decision. +# After this workflow completes, verifyDefaultVersionActivity polls +# for up to 3 hours for the GA version to become default, then +# finalizeRolloutActivity unpins actors. # -# The workflow performs these tasks: -# 1. Generate an `auto-merge/bypass`-labeled PR to version-bump forward -# the named connector, including changelog entry. -# 2. Delete the `.../release_candidate` subdirectory in the GCS Registry Store. +# See: airbyte-platform-internal/.../connector-rollout-worker/ on: repository_dispatch: @@ -19,45 +18,260 @@ on: action: description: "Action to perform" required: true + type: choice options: ["promote", "rollback"] + +permissions: + contents: write + pull-requests: write + +# --------------------------------------------------------------------------- +# Job 1: Registry cleanup (runs for both promote and rollback) +# --------------------------------------------------------------------------- jobs: - finalize_rollout: - name: Finalize connector rollout for ${{ github.event.inputs.connector_name }} + rc-registry-cleanup: + name: "Registry cleanup (${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action || github.event.client_payload.action }})" runs-on: ubuntu-24.04 env: ACTION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action || github.event.client_payload.action }} CONNECTOR_NAME: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.connector_name || github.event.client_payload.connector_name }} steps: - - name: Check action value + - name: Validate action run: | if [[ "${ACTION}" != "promote" && "${ACTION}" != "rollback" ]]; then - echo "Invalid action: ${ACTION}" + echo "::error::Invalid action: '${ACTION}'. Must be 'promote' or 'rollback'." exit 1 fi shell: bash + # The cleanup CLI reads metadata.yaml from the local repo to + # resolve the connector's dockerRepository. - name: Checkout Airbyte repo - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Ops CLI run: uv tool install airbyte-internal-ops - # Delete the release_candidate/ metadata marker from GCS. - # This is the same operation for both promote and rollback: - # it removes the RC pointer so the platform knows the rollout - # is finalized. The versioned blob (e.g. 1.2.3-rc.1/) is - # intentionally preserved as an audit trail. - - name: "GCS cleanup: delete RC metadata marker" + # Deletes the release_candidate/ metadata marker from GCS. + # The versioned blob (e.g. 1.2.3-rc.1/) is preserved as audit trail. + - name: "Delete RC metadata marker" shell: bash env: GCS_CREDENTIALS: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} REGISTRY_STORE: "coral:prod" run: > - airbyte-ops registry rc cleanup + airbyte-ops registry progressive-rollout cleanup --name "${CONNECTOR_NAME}" --store "${REGISTRY_STORE}" + + # Recompile the registry JSON indexes so the RC entry is removed. + - name: "Recompile registry for connector" + shell: bash + env: + GCS_CREDENTIALS: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} + REGISTRY_STORE: "coral:prod" + run: > + airbyte-ops registry store compile + --store "${REGISTRY_STORE}" + --connector-name "${CONNECTOR_NAME}" + + # --- Notifications --- + - name: Notify Slack on rollback success + if: success() && env.ACTION == 'rollback' + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.PUBLISH_ON_MERGE_SLACK_WEBHOOK }} + with: + payload: | + { + "channel": "#connector-publish-failures", + "username": "Connectors CI/CD Bot", + "text": "↩️ Successfully rolled back RC for `${{ env.CONNECTOR_NAME }}` — GCS marker deleted:\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + - name: Notify Slack on failure + if: failure() + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.PUBLISH_ON_MERGE_SLACK_WEBHOOK }} + with: + payload: | + { + "channel": "#connector-publish-failures", + "username": "Connectors CI/CD Bot", + "text": "🚨 Registry cleanup failed for `${{ env.CONNECTOR_NAME }}` (${{ env.ACTION }}):\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + # --------------------------------------------------------------------------- + # Job 2: Create and merge cleanup PR (promote only) + # --------------------------------------------------------------------------- + create-promotion-pr: + name: "Create and Merge Promotion PR" + needs: rc-registry-cleanup + if: >- + github.event.inputs.action == 'promote' + || github.event.client_payload.action == 'promote' + runs-on: ubuntu-24.04 + env: + CONNECTOR_NAME: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.connector_name || github.event.client_payload.connector_name }} + steps: + # Authenticate as OCTAVIA_BOT so PRs trigger downstream CI. + - name: Authenticate as GitHub App + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-app-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} + + - name: Configure git identity + run: | + git config --global user.name "Octavia Squidington III" + git config --global user.email "octavia-squidington-iii@users.noreply.github.com" + + - name: Checkout Airbyte repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + token: ${{ steps.get-app-token.outputs.token }} + fetch-depth: 1 + + - name: Install uv + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + + - name: Install Ops CLI + run: uv tool install airbyte-internal-ops + + # bump-version --bump-type promote strips -rc.N suffix, sets + # enableProgressiveRollout to false, and bumps the version. + # --no-changelog because we don't have a PR number yet. + - name: "Version bump and cleanup" + shell: bash + run: > + airbyte-ops local connector bump-version + --name "${CONNECTOR_NAME}" + --repo-path "${{ github.workspace }}" + --bump-type promote + --no-changelog + + # Commits cleanup changes (version bump + progressive rollout + # flag removal) and opens a draft PR. This gives us a PR + # number for the changelog entry. + - name: Create cleanup PR + id: create-pr + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + with: + token: ${{ steps.get-app-token.outputs.token }} + commit-message: "chore: finalize promote for ${{ env.CONNECTOR_NAME }}" + branch: "finalize-rollout/promote/${{ env.CONNECTOR_NAME }}" + base: ${{ github.event.repository.default_branch }} + delete-branch: true + title: "chore: finalize promote for ${{ env.CONNECTOR_NAME }}" + body: | + Automated cleanup after connector rollout **promote** for `${{ env.CONNECTOR_NAME }}`. + + Generated by [`finalize_rollout` workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + labels: | + area/connectors + auto-merge/bypass-ci-checks + draft: true + + # Switch to the PR branch so changelog add reads the bumped + # GA version from metadata.yaml (not the RC version on master). + - name: "Checkout PR branch" + if: steps.create-pr.outputs.pull-request-number + run: git fetch origin "finalize-rollout/promote/${CONNECTOR_NAME}" && git checkout "finalize-rollout/promote/${CONNECTOR_NAME}" + + # changelog add reads the current version from metadata.yaml + # (already bumped to GA by the promote step above) and writes + # a changelog entry — no version files are modified. + - name: "Add changelog entry" + if: steps.create-pr.outputs.pull-request-number + shell: bash + run: > + airbyte-ops local connector changelog add + --connector-name "${CONNECTOR_NAME}" + --repo-path "${{ github.workspace }}" + --message "Promoted release candidate to GA" + --pr-number "${{ steps.create-pr.outputs.pull-request-number }}" + + # Commit and push the changelog entry to the existing PR branch. + - name: "Push changelog to PR" + if: steps.create-pr.outputs.pull-request-number + run: | + git add docs/ + git commit -m "chore: add changelog for ${CONNECTOR_NAME}" + git push origin "finalize-rollout/promote/${CONNECTOR_NAME}" + + # The merge triggers the publish pipeline that + # verifyDefaultVersionActivity is waiting for. + - name: Mark PR ready for review + if: steps.create-pr.outputs.pull-request-number + env: + GH_TOKEN: ${{ steps.get-app-token.outputs.token }} + run: gh pr ready "${{ steps.create-pr.outputs.pull-request-number }}" + + # Use a different bot identity (admin) to approve and merge, + # because the bot that created the PR cannot approve its own PRs + # and needs admin privileges to bypass branch protection. + - name: Authenticate as GitHub App (Admin) + if: steps.create-pr.outputs.pull-request-number + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + id: get-admin-token + with: + owner: "airbytehq" + repositories: "airbyte" + app-id: ${{ secrets.OCTAVIA_BOT_ADMIN_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_ADMIN_PRIVATE_KEY }} + + - name: Approve cleanup PR + if: steps.create-pr.outputs.pull-request-number + env: + GH_TOKEN: ${{ steps.get-admin-token.outputs.token }} + run: > + gh pr review "${{ steps.create-pr.outputs.pull-request-number }}" + --repo "${{ github.repository }}" + --approve + --body "Auto-approved by finalize_rollout workflow." + + - name: Force-merge cleanup PR + if: steps.create-pr.outputs.pull-request-number + env: + GH_TOKEN: ${{ steps.get-admin-token.outputs.token }} + run: | + gh pr merge "${{ steps.create-pr.outputs.pull-request-number }}" \ + --repo "${{ github.repository }}" \ + --squash \ + --admin + + # --- Notifications --- + - name: Notify Slack on promote success + if: success() && steps.create-pr.outputs.pull-request-number + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.PUBLISH_ON_MERGE_SLACK_WEBHOOK }} + with: + payload: | + { + "channel": "#connector-publish-failures", + "username": "Connectors CI/CD Bot", + "text": "↗️ Successfully promoted `${{ env.CONNECTOR_NAME }}` — cleanup PR merged:\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + + - name: Notify Slack on failure + if: failure() + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.PUBLISH_ON_MERGE_SLACK_WEBHOOK }} + with: + payload: | + { + "channel": "#connector-publish-failures", + "username": "Connectors CI/CD Bot", + "text": "🚨 Promote PR failed for `${{ env.CONNECTOR_NAME }}`:\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/.github/workflows/force-merge-command.yml b/.github/workflows/force-merge-command.yml index 9b55db360c45..8a0231ec28ae 100644 --- a/.github/workflows/force-merge-command.yml +++ b/.github/workflows/force-merge-command.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App (Admin) - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/format-fix-command.yml b/.github/workflows/format-fix-command.yml index e14907a17319..50063e9da658 100644 --- a/.github/workflows/format-fix-command.yml +++ b/.github/workflows/format-fix-command.yml @@ -48,7 +48,7 @@ jobs: # forks if the user installs the app into their fork. Until we document this as a clear # path, we will have to keep using the PAT. - name: Checkout Airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ steps.job-vars.outputs.repo }} ref: ${{ steps.job-vars.outputs.branch }} @@ -73,12 +73,12 @@ jobs: # Compare the below to the `format_check.yml` workflow - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Setup Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" cache: "pip" diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml index 81385483f298..5a8e61536bbf 100644 --- a/.github/workflows/format_check.yml +++ b/.github/workflows/format_check.yml @@ -14,14 +14,14 @@ jobs: name: "Format Check" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Setup Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" cache: "pip" diff --git a/.github/workflows/generate-connector-registries.yml b/.github/workflows/generate-connector-registries.yml index 382e2e85c125..61f1655499e6 100644 --- a/.github/workflows/generate-connector-registries.yml +++ b/.github/workflows/generate-connector-registries.yml @@ -47,13 +47,13 @@ jobs: steps: - name: Checkout Airbyte - uses: actions/checkout@8edcb1bdb4e267140fa742c62e395cd74f332709 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.inputs.gitref || github.ref }} submodules: true - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Ops CLI run: uv tool install airbyte-internal-ops diff --git a/.github/workflows/gradle-dependency-diff.yml b/.github/workflows/gradle-dependency-diff.yml index 5f58863fb959..94fb3bf0a472 100644 --- a/.github/workflows/gradle-dependency-diff.yml +++ b/.github/workflows/gradle-dependency-diff.yml @@ -10,9 +10,9 @@ jobs: if: github.event.pull_request.head.repo.fork == false steps: - name: Checkout Code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Java - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: temurin java-version: 21 diff --git a/.github/workflows/internal-airbyte-ci-release.yml b/.github/workflows/internal-airbyte-ci-release.yml deleted file mode 100644 index fcc2b257ec60..000000000000 --- a/.github/workflows/internal-airbyte-ci-release.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Connector Ops CI - Airbyte CI Release - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - push: - paths: - - "airbyte-ci/connectors/pipelines/**" - workflow_dispatch: - -env: - DEV_GCS_BUCKET_NAME: dev-airbyte-cloud-connector-metadata-service-2 - PROD_GCS_BUCKET_NAME: prod-airbyte-cloud-connector-metadata-service - BINARY_FILE_NAME: airbyte-ci - -jobs: - build: - runs-on: ${{ matrix.os }}-latest - strategy: - fail-fast: false - matrix: - os: ["ubuntu", "macos"] - - steps: - - name: Checkout Airbyte - id: checkout_airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - with: - ref: ${{ github.sha }} # This is required to make sure that the same commit is checked out on all runners - - - name: Get short SHA - id: get_short_sha - uses: benjlevesque/short-sha@36eb8c530990ceac5ddf3c0bc32d02c677ae9706 # v2.2 - - - name: Install Python - id: install_python - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 - with: - python-version: "3.11" - check-latest: true - update-environment: true - - - name: Install Poetry - id: install_poetry - uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 - with: - version: 1.8.5 - - - name: Install Dependencies - id: install_dependencies - working-directory: airbyte-ci/connectors/pipelines/ - run: poetry install --with dev - - - name: Build release - id: build_release - working-directory: airbyte-ci/connectors/pipelines/ - run: poetry run poe build-release-binary ${{ env.BINARY_FILE_NAME }} - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: airbyte-ci-${{ matrix.os }}-${{ steps.get_short_sha.outputs.sha }} - path: airbyte-ci/connectors/pipelines/dist/${{ env.BINARY_FILE_NAME }} - - - name: Authenticate to Google Cloud Dev - id: auth_dev - uses: google-github-actions/auth@3a3c4c57d294ef65efaaee4ff17b22fa88dd3c69 # v1.3.0 - with: - credentials_json: "${{ secrets.METADATA_SERVICE_DEV_GCS_CREDENTIALS }}" - - - name: Upload pre-release to GCS dev bucket - id: upload_pre_release_to_gcs - if: github.ref != 'refs/heads/master' - uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1.0.3 - with: - path: airbyte-ci/connectors/pipelines/dist/${{ env.BINARY_FILE_NAME }} - destination: ${{ env.DEV_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/${{ steps.get_short_sha.outputs.sha }} - headers: |- - cache-control:public, max-age=10 - - - name: Print pre-release public url - id: print_pre_release_public_url - run: | - echo "https://storage.googleapis.com/${{ env.DEV_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/${{ steps.get_short_sha.outputs.sha }}/${{ env.BINARY_FILE_NAME }}" - - # if master, upload per version and latest to prod bucket - - - name: Set version from poetry version --short - id: set_version - if: github.ref == 'refs/heads/master' - working-directory: airbyte-ci/connectors/pipelines/ - run: | - echo "version=$(poetry version --short)" >> $GITHUB_OUTPUT - - - name: Authenticate to Google Cloud Prod - id: auth_prod - uses: google-github-actions/auth@3a3c4c57d294ef65efaaee4ff17b22fa88dd3c69 # v1.3.0 - with: - credentials_json: "${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }}" - - - name: Upload version release to GCS prod bucket - id: upload_version_release_to_gcs - if: github.ref == 'refs/heads/master' - uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1.0.3 - with: - path: airbyte-ci/connectors/pipelines/dist/${{ env.BINARY_FILE_NAME }} - destination: ${{ env.PROD_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/${{ steps.set_version.outputs.version }} - headers: |- - cache-control:public, max-age=10 - - - name: Print release version public url - id: print_version_release_public_url - if: github.ref == 'refs/heads/master' - run: | - echo "https://storage.googleapis.com/${{ env.PROD_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/${{ steps.set_version.outputs.version }}/${{ env.BINARY_FILE_NAME }}" - - - name: Upload latest release to GCS prod bucket - id: upload_latest_release_to_gcs - if: github.ref == 'refs/heads/master' - uses: google-github-actions/upload-cloud-storage@e95a15f226403ed658d3e65f40205649f342ba2c # v1.0.3 - with: - path: airbyte-ci/connectors/pipelines/dist/${{ env.BINARY_FILE_NAME }} - destination: ${{ env.PROD_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/latest - headers: |- - cache-control:public, max-age=10 - - - name: Print latest release public url - id: print_latest_release_public_url - if: github.ref == 'refs/heads/master' - run: | - echo "https://storage.googleapis.com/${{ env.PROD_GCS_BUCKET_NAME }}/airbyte-ci/releases/${{ matrix.os }}/latest/${{ env.BINARY_FILE_NAME }}" diff --git a/.github/workflows/internal-airbyte-ci-tests.yml b/.github/workflows/internal-airbyte-ci-tests.yml deleted file mode 100644 index d20c93f1cf52..000000000000 --- a/.github/workflows/internal-airbyte-ci-tests.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Internal CI Tooling Checks - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - workflow_dispatch: - inputs: - airbyte_ci_subcommand: - description: "Subcommand to pass to the 'airbyte-ci test' command" - default: "--poetry-package-path=airbyte-ci/connectors/pipelines" - pull_request: - types: - - opened - - reopened - - synchronize -jobs: - changes: - name: Detect Changes - runs-on: ubuntu-24.04 - outputs: - internal_poetry_packages: ${{ steps.changes.outputs.internal_poetry_packages }} - - steps: - - name: Checkout Airbyte - if: github.event_name != 'pull_request' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - id: changes - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 - with: - # Note: expressions within a filter are OR'ed - filters: | - # This list is duplicated in `pipelines/airbyte_ci/test/__init__.py` - internal_poetry_packages: - - airbyte-ci/connectors/pipelines/** - - airbyte-ci/connectors/connectors_insights/** - - airbyte-ci/connectors/connector_ops/** - - airbyte-ci/connectors/ci_credentials/** - - airbyte-ci/connectors/erd/** - - airbyte-ci/connectors/metadata_service/lib/** - - airbyte-integrations/bases/connector-acceptance-test/** - - run-tests: - needs: changes - # We only run the Internal Poetry packages CI job if there are changes to the packages on a non-forked PR - if: needs.changes.outputs.internal_poetry_packages == 'true' && github.event.pull_request.head.repo.fork != true - name: Internal Poetry packages CI - runs-on: tooling-test-large - permissions: - pull-requests: read - statuses: write - steps: - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} - - name: Checkout Airbyte Python CDK - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - repository: airbytehq/airbyte-python-cdk - ref: main - # We can't clone into a parent directory of the repo, so we clone into - # a subdirectory and then move it over as a sibling directory. - # This will be used for the `--use-local-cdk` flag in `airbyte-ci` command - path: airbyte-python-cdk - - name: Move airbyte-python-cdk to sibling directory path - shell: bash - run: mv ./airbyte-python-cdk ../airbyte-python-cdk - - name: Show local paths checked out - shell: bash - run: | - set -x - echo "Current directory: $(pwd)" - ls -la - ls -la ../airbyte-python-cdk || echo "No airbyte-python-cdk directory" - - - name: Extract branch name [WORKFLOW DISPATCH] - shell: bash - if: github.event_name == 'workflow_dispatch' - run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT - id: extract_branch - - name: Fetch last commit id from remote branch [PULL REQUESTS] - if: github.event_name == 'pull_request' - id: fetch_last_commit_id_pr - run: echo "commit_id=$(git ls-remote --heads origin refs/heads/${{ github.head_ref }} | cut -f 1)" >> $GITHUB_OUTPUT - - name: Fetch last commit id from remote branch [WORKFLOW DISPATCH] - if: github.event_name == 'workflow_dispatch' - id: fetch_last_commit_id_wd - run: echo "commit_id=$(git rev-parse origin/${{ steps.extract_branch.outputs.branch }})" >> $GITHUB_OUTPUT - - - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 - id: get-app-token - with: - owner: "airbytehq" - repositories: "airbyte" - app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} - private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - - - name: Run poe tasks for modified internal packages [PULL REQUEST] - if: github.event_name == 'pull_request' - id: run-airbyte-ci-test-pr - uses: ./.github/actions/run-airbyte-ci - with: - context: "pull_request" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_5 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - git_branch: ${{ github.head_ref }} - git_revision: ${{ steps.fetch_last_commit_id_pr.outputs.commit_id }} - github_token: ${{ steps.get-app-token.outputs.token }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - subcommand: "test --modified" - - - name: Run poe tasks for requested internal packages [WORKFLOW DISPATCH] - id: run-airbyte-ci-test-workflow-dispatch - if: github.event_name == 'workflow_dispatch' - uses: ./.github/actions/run-airbyte-ci - with: - context: "manual" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_5 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - git_branch: ${{ steps.extract_branch.outputs.branch }} - git_revision: ${{ steps.fetch_last_commit_id_pr.outputs.commit_id }} - github_token: ${{ steps.get-app-token.outputs.token }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - subcommand: "test ${{ inputs.airbyte_ci_subcommand}}" diff --git a/.github/workflows/java-bulk-cdk-base-publish.yml b/.github/workflows/java-bulk-cdk-base-publish.yml index 85e7480625a7..d1eb085750c3 100644 --- a/.github/workflows/java-bulk-cdk-base-publish.yml +++ b/.github/workflows/java-bulk-cdk-base-publish.yml @@ -27,16 +27,16 @@ jobs: timeout-minutes: 30 steps: - name: Checkout Airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Docker login - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1.14.1 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/java-bulk-cdk-extract-publish.yml b/.github/workflows/java-bulk-cdk-extract-publish.yml index e611375a52f2..b336902f1e8d 100644 --- a/.github/workflows/java-bulk-cdk-extract-publish.yml +++ b/.github/workflows/java-bulk-cdk-extract-publish.yml @@ -30,16 +30,16 @@ jobs: timeout-minutes: 30 steps: - name: Checkout Airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Docker login - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1.14.1 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/java-bulk-cdk-load-publish.yml b/.github/workflows/java-bulk-cdk-load-publish.yml index 5e102f88d232..97a688797830 100644 --- a/.github/workflows/java-bulk-cdk-load-publish.yml +++ b/.github/workflows/java-bulk-cdk-load-publish.yml @@ -29,16 +29,16 @@ jobs: timeout-minutes: 30 steps: - name: Checkout Airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Docker login - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1.14.1 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/java-cdk-tests.yml b/.github/workflows/java-cdk-tests.yml index 1df9a0393978..18d4b6af510f 100644 --- a/.github/workflows/java-cdk-tests.yml +++ b/.github/workflows/java-cdk-tests.yml @@ -29,9 +29,9 @@ jobs: steps: - name: Checkout Airbyte if: github.event_name != 'pull_request' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: changes - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 with: filters: | java: @@ -52,9 +52,9 @@ jobs: steps: - name: Checkout Airbyte if: github.event_name != 'pull_request' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: changes - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 with: filters: | base: @@ -80,9 +80,9 @@ jobs: timeout-minutes: 60 steps: - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Java Setup - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" @@ -130,9 +130,9 @@ jobs: timeout-minutes: 60 steps: - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Java Setup - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" diff --git a/.github/workflows/kotlin-bulk-cdk-dokka-publish.yml b/.github/workflows/kotlin-bulk-cdk-dokka-publish.yml index 298cfa2293c8..4f9549bcbc28 100644 --- a/.github/workflows/kotlin-bulk-cdk-dokka-publish.yml +++ b/.github/workflows/kotlin-bulk-cdk-dokka-publish.yml @@ -29,7 +29,7 @@ jobs: - name: Checkout Repository [Push Trigger] if: github.event_name == 'push' - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 @@ -38,7 +38,7 @@ jobs: # Push triggers will require the checked-out code. id: detect-changes if: github.event_name != 'workflow_dispatch' - uses: dorny/paths-filter@v3.0.2 + uses: dorny/paths-filter@v4.0.1 with: filters: | bulk-cdk: @@ -56,14 +56,14 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref || github.ref }} - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" @@ -111,7 +111,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 @@ -152,7 +152,7 @@ jobs: - name: Authenticate as GitHub App if: github.event_name == 'pull_request' - uses: actions/create-github-app-token@v2.0.6 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -162,7 +162,7 @@ jobs: - name: Post Custom Check with Preview URL if: github.event_name == 'pull_request' - uses: LouisBrunner/checks-action@v2.0.0 + uses: LouisBrunner/checks-action@dfcbcf801bff1ea7f1414824fc28f2cd697b35da # v3.0.0 with: name: "Kotlin Bulk CDK Docs Preview" status: completed diff --git a/.github/workflows/notify-on-replication-version-change.yml b/.github/workflows/notify-on-replication-version-change.yml index 152fda02f000..1d4cdd018421 100644 --- a/.github/workflows/notify-on-replication-version-change.yml +++ b/.github/workflows/notify-on-replication-version-change.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.workflow_run.head_sha }} fetch-depth: 2 @@ -115,7 +115,7 @@ jobs: - name: Authenticate as GitHub App if: fromJSON(steps.detect-changes.outputs.changes)[0] != null - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/oss-issue-triage.yml b/.github/workflows/oss-issue-triage.yml index 1c4b1db578ac..b857c80df0d6 100644 --- a/.github/workflows/oss-issue-triage.yml +++ b/.github/workflows/oss-issue-triage.yml @@ -31,7 +31,7 @@ jobs: fi - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -105,7 +105,7 @@ jobs: - Join the [Airbyte Community Slack](https://slack.airbyte.com) to connect with other users and community members - If you're an Airbyte Cloud customer, you can [open a support ticket](https://support.airbyte.com) or contact your account representative, referencing this issue URL - [View playbook](https://github.com/airbytehq/oncall/blob/main/prompts/playbooks/oss_issue_triage.md) + [View playbook](https://github.com/airbytehq/ai-skills/blob/main/devin/playbooks/oss_issue_triage.md) tags: | oss-issue-triage community-issue @@ -117,7 +117,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/poe-command.yml b/.github/workflows/poe-command.yml index 680c806d18da..5873d607201b 100644 --- a/.github/workflows/poe-command.yml +++ b/.github/workflows/poe-command.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/pr-ai-review-submit.yml b/.github/workflows/pr-ai-review-submit.yml index 2461e5d31853..faaf22eb228d 100644 --- a/.github/workflows/pr-ai-review-submit.yml +++ b/.github/workflows/pr-ai-review-submit.yml @@ -47,7 +47,7 @@ jobs: - name: Authenticate as GitHub App if: steps.parse-marker.outputs.result == 'FAIL' || steps.parse-marker.outputs.result == 'APPROVE' - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" diff --git a/.github/workflows/promote-draft-on-auto-merge-label.yml b/.github/workflows/promote-draft-on-auto-merge-label.yml deleted file mode 100644 index 657a2b0f6ff4..000000000000 --- a/.github/workflows/promote-draft-on-auto-merge-label.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Promote Draft on Auto-Merge Label - -# This workflow automatically marks draft PRs as ready for review -# when an auto-merge label is added. This complements the -# enforce-draft-pr-status workflow: if a PR is converted to draft -# before its auto-merge label is applied, this workflow will -# promote it back to ready once the label arrives. - -on: - pull_request_target: - types: - - labeled - -jobs: - promote-draft: - name: Mark PR as Ready for Review - if: | - github.event.pull_request.draft == true && - ( - github.event.label.name == 'auto-merge' || - github.event.label.name == 'auto-merge/bypass-ci-checks' - ) - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-24.04 - steps: - - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 - id: get-app-token - with: - owner: "airbytehq" - repositories: "airbyte" - app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} - private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - - - name: Mark PR as ready for review - env: - GH_TOKEN: ${{ steps.get-app-token.outputs.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - gh pr ready "$PR_NUMBER" --repo "${{ github.repository }}" diff --git a/.github/workflows/publish-connectors-prerelease-command.yml b/.github/workflows/publish-connectors-prerelease-command.yml index d78b1f7f416d..e19e6199d311 100644 --- a/.github/workflows/publish-connectors-prerelease-command.yml +++ b/.github/workflows/publish-connectors-prerelease-command.yml @@ -57,7 +57,7 @@ jobs: connector-version: ${{ steps.connector-version.outputs.connector-version }} steps: - name: Checkout to get commit SHA - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repo || github.repository }} ref: refs/pull/${{ inputs.pr }}/head diff --git a/.github/workflows/publish-java-cdk-command.yml b/.github/workflows/publish-java-cdk-command.yml index 7c8fab8a9aab..36ce7a0ba2ad 100644 --- a/.github/workflows/publish-java-cdk-command.yml +++ b/.github/workflows/publish-java-cdk-command.yml @@ -89,7 +89,7 @@ jobs: > :clock2: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} - name: Checkout Airbyte - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ env.GITREF }} @@ -107,14 +107,14 @@ jobs: echo "CDK_VERSION=${cdk_version}" >> $GITHUB_ENV - name: Setup Java - uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Docker login # Some tests use testcontainers which pull images from DockerHub. - uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1.14.1 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} diff --git a/.github/workflows/publish_connectors.yml b/.github/workflows/publish_connectors.yml index fdca377d9d64..0e21bf54632f 100644 --- a/.github/workflows/publish_connectors.yml +++ b/.github/workflows/publish_connectors.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Checkout Airbyte # v4 - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.gitref || '' }} fetch-depth: 2 # Required so we can conduct a diff from the previous commit to understand what connectors have changed. @@ -151,7 +151,7 @@ jobs: docker-image-tag: ${{ steps.resolve-docker-image-tag.outputs.docker-image-tag }} steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -162,7 +162,7 @@ jobs: - name: Checkout Airbyte # v4 - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.gitref || '' }} fetch-depth: 2 # Required so we can conduct a diff from the previous commit to understand what connectors have changed. @@ -173,21 +173,21 @@ jobs: shell: bash run: docker buildx create --use --driver=docker-container --name builder --platform linux/amd64,linux/arm64 - - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: zulu java-version: 21 cache: gradle - name: Log in to Docker Hub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Set up Python # v5 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: "3.11" check-latest: true @@ -202,7 +202,7 @@ jobs: version: 1.8.5 - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: # We pass a token with dedicated GH rate limit github-token: ${{ steps.get-app-token.outputs.token }} @@ -285,14 +285,6 @@ jobs: ;; esac - # We're intentionally not using the `google-github-actions/auth` action. - # The upload-additional-connector-metadata step runs a script which handles auth - # manually, because we're writing files to multiple buckets using different - # credentials for each bucket. - - name: Install gcloud - # v2.1.5 - uses: google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9 - - name: Publish to Python Registry id: publish-python-registry if: steps.connector-metadata.outputs.connector-language == 'python' && needs.publish_options.outputs.dry-run != 'true' && needs.publish_options.outputs.with-semver-suffix == 'none' @@ -308,24 +300,6 @@ jobs: run: | echo "DRY-RUN: Skipping Python Registry publish for ${{ matrix.connector }}" - - name: Upload Python Dependencies to GCS - id: upload-python-dependencies-master - if: steps.connector-metadata.outputs.connector-language == 'python' && needs.publish_options.outputs.dry-run != 'true' - shell: bash - env: - GCS_CREDENTIALS: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - CONNECTOR_VERSION_TAG: ${{ steps.resolve-version.outputs.connector-version-tag }} - run: | - ./poe-tasks/upload-python-dependencies.sh \ - --name ${{ matrix.connector }} \ - --bucket prod-airbyte-cloud-connector-metadata-service \ - --with-semver-suffix ${{ needs.publish_options.outputs.with-semver-suffix }} - - - name: "[DRY-RUN] Skip Upload Python Dependencies to GCS" - if: steps.connector-metadata.outputs.connector-language == 'python' && needs.publish_options.outputs.dry-run == 'true' - run: | - echo "DRY-RUN: Skipping Python Dependencies upload for ${{ matrix.connector }}" - - name: Build and publish JVM connectors images id: build-and-publish-JVM-connectors-images if: steps.connector-metadata.outputs.connector-language == 'java' && needs.publish_options.outputs.dry-run != 'true' @@ -373,16 +347,6 @@ jobs: docker-hub-username: ${{ secrets.DOCKER_HUB_USERNAME }} docker-hub-password: ${{ secrets.DOCKER_HUB_PASSWORD }} - - name: Upload additional connector metadata (spec cache + SBOM) - id: upload-additional-connector-metadata - if: needs.publish_options.outputs.dry-run != 'true' - shell: bash - run: ./poe-tasks/upload-additional-connector-metadata.sh --name ${{ matrix.connector }} --with-semver-suffix ${{ needs.publish_options.outputs.with-semver-suffix }} - env: - SPEC_CACHE_GCS_CREDENTIALS: ${{ secrets.SPEC_CACHE_SERVICE_ACCOUNT_KEY_PUBLISH }} - METADATA_SERVICE_GCS_CREDENTIALS: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }} - CONNECTOR_VERSION_TAG: ${{ steps.resolve-version.outputs.connector-version-tag }} - # --- Registry artifact generation and publishing --- - name: Enable progressive rollout for RC builds @@ -428,15 +392,17 @@ jobs: registry connector-version artifacts generate \ --metadata-file "airbyte-integrations/connectors/${{ matrix.connector }}/metadata.yaml" \ --docker-image "${DOCKER_IMAGE}" \ - --output-dir ./registry-artifacts \ - --with-validate + --output-dir ./registry-artifacts/${{ matrix.connector }} \ + --with-validate \ + --with-sbom \ + --with-dependency-dump - name: Upload Registry CI Artifacts if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: registry-artifacts-${{ matrix.connector }}-${{ steps.resolve-docker-image-tag.outputs.docker-image-tag }} - path: ./registry-artifacts/ + path: ./registry-artifacts/${{ matrix.connector }}/ retention-days: 7 - name: Publish Registry Artifacts @@ -452,7 +418,7 @@ jobs: registry connector-version artifacts publish \ --name "${{ matrix.connector }}" \ --version "${{ steps.resolve-docker-image-tag.outputs.docker-image-tag }}" \ - --artifacts-dir ./registry-artifacts \ + --artifacts-dir ./registry-artifacts/${{ matrix.connector }} \ --store "${REGISTRY_STORE}" \ --with-validate @@ -477,7 +443,7 @@ jobs: run: | echo "Creating RC metadata marker for ${{ matrix.connector }}" airbyte-ops \ - registry rc create \ + registry progressive-rollout create \ --name "${{ matrix.connector }}" \ --store "${REGISTRY_STORE}" @@ -554,7 +520,7 @@ jobs: if: ${{ always() && needs.publish_connectors.result == 'failure' }} steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -565,7 +531,7 @@ jobs: - name: Checkout Airbyte # v4 - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.gitref || '' }} submodules: true # Required for the enterprise repo since it uses a submodule that needs to exist for this workflow to run successfully. diff --git a/.github/workflows/pyairbyte-docs-generate.yml b/.github/workflows/pyairbyte-docs-generate.yml index 5832a9006a74..6ad173cadc56 100644 --- a/.github/workflows/pyairbyte-docs-generate.yml +++ b/.github/workflows/pyairbyte-docs-generate.yml @@ -34,13 +34,13 @@ jobs: steps: - name: Checkout Airbyte Repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -259,7 +259,7 @@ jobs: - name: Authenticate as GitHub App if: steps.changes.outputs.has_changes == 'true' - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -269,7 +269,7 @@ jobs: - name: Create Pull Request if: steps.changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ steps.get-app-token.outputs.token }} commit-message: "docs: Update PyAirbyte API reference documentation for v${{ steps.version.outputs.version }}" diff --git a/.github/workflows/regenerate-agent-engine-api-spec.yml b/.github/workflows/regenerate-agent-engine-api-spec.yml index bf740e7f31a5..15b2fc20d516 100644 --- a/.github/workflows/regenerate-agent-engine-api-spec.yml +++ b/.github/workflows/regenerate-agent-engine-api-spec.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 id: app-token with: owner: "airbytehq" @@ -35,7 +35,7 @@ jobs: git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com' - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.app-token.outputs.token }} @@ -77,7 +77,7 @@ jobs: - name: Create Pull Request id: create-pr if: steps.check-changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ steps.app-token.outputs.token }} commit-message: "chore(docs): regenerate cached Agent Engine API spec" diff --git a/.github/workflows/resolve-stale-metadata.yml b/.github/workflows/resolve-stale-metadata.yml index 37285688534f..80d822c7b3ad 100644 --- a/.github/workflows/resolve-stale-metadata.yml +++ b/.github/workflows/resolve-stale-metadata.yml @@ -26,12 +26,12 @@ jobs: steps: - name: Checkout Airbyte - uses: actions/checkout@8edcb1bdb4e267140fa742c62e395cd74f332709 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.inputs.gitref || github.ref }} - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Install Ops CLI continue-on-error: true diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index e7be60193dba..44ce22352f9a 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -15,7 +15,7 @@ jobs: name: Docs / MarkDownLint runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: markdownlint uses: reviewdog/action-markdownlint@3667398db9118d7e78f7a63d10e26ce454ba5f58 # v0.26.2 with: @@ -28,7 +28,7 @@ jobs: name: Docs / Vale runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # V5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # Pinned to V2.1.1 diff --git a/.github/workflows/run-cat-tests-command.yml b/.github/workflows/run-cat-tests-command.yml deleted file mode 100644 index 6aa1827fe28b..000000000000 --- a/.github/workflows/run-cat-tests-command.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: On-Demand CAT Tests (Legacy) -# This workflow is used to run the legacy CAT tests for modified connectors. -# It can be triggered manually via slash command or workflow dispatch. - -on: - workflow_dispatch: - inputs: - repo: - description: "The repository name" - required: false - type: string - gitref: - description: "The git reference (branch or tag)" - required: false - type: string - comment-id: - description: "The ID of the comment triggering the workflow" - required: false - type: number - pr: - description: "The pull request number, if applicable" - required: false - type: number - -run-name: "Run connector CAT tests in PR: #${{ github.event.inputs.pr }}" -concurrency: - group: ${{ github.workflow }}-${{ github.event.inputs.pr }} - # Cancel any previous runs on the same branch if they are still in progress - cancel-in-progress: true - -jobs: - run-cat-tests: - name: "On-Demand CAT Tests" - runs-on: ubuntu-24.04 - steps: - - name: Get job variables - id: job-vars - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.inputs.pr }}) - echo "repo=$(echo "$PR_JSON" | jq -r .head.repo.full_name)" >> $GITHUB_OUTPUT - echo "branch=$(echo "$PR_JSON" | jq -r .head.ref)" >> $GITHUB_OUTPUT - echo "pr_title=$(echo "$PR_JSON" | jq -r .title)" >> $GITHUB_OUTPUT - echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT - - - name: Append comment with job run link - # If comment-id is not provided, this will create a new - # comment with the job run link. - id: first-comment-action - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - with: - comment-id: ${{ github.event.inputs.comment-id }} - issue-number: ${{ github.event.inputs.pr }} - body: | - > Connector CAT tests started... [Check job output.](${{ steps.job-vars.outputs.run-url }}) - > - > If the connector supports live tests or regression tests, you may want to run those now also: - > - [Run Live Tests](https://github.com/airbytehq/airbyte/actions/workflows/live_tests.yml) - > - [Run Regression Tests](https://github.com/airbytehq/airbyte/actions/workflows/regression_tests.yml) - > - - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - repository: ${{ steps.job-vars.outputs.repo }} - ref: ${{ steps.job-vars.outputs.branch }} - fetch-depth: 0 - - # NOTE: We still use a PAT here (rather than a GitHub App) because the workflow needs - # permissions to add commits to our main repo as well as forks. This will only work on - # forks if the user installs the app into their fork. Until we document this as a clear - # path, we will have to keep using the PAT. - - name: Run CAT tests with `airbyte-ci` - if: github.event_name == 'workflow_dispatch' - uses: ./.github/actions/run-airbyte-ci - with: - context: "pull_request" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_3 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcp_integration_tester_credentials: ${{ secrets.GCLOUD_INTEGRATION_TESTER }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - github_token: ${{ secrets.GH_PAT_APPROVINGTON_OCTAVIA }} - s3_build_cache_access_key_id: ${{ secrets.SELF_RUNNER_AWS_ACCESS_KEY_ID }} - s3_build_cache_secret_key: ${{ secrets.SELF_RUNNER_AWS_SECRET_ACCESS_KEY }} - git_branch: ${{ steps.job-vars.outputs.branch }} - git_revision: ${{ steps.job-vars.outputs.commit_id }} - subcommand: "connectors --modified test --only-step=acceptance" - - - name: Append completion comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 - if: always() - with: - comment-id: ${{ steps.first-comment-action.outputs.comment-id }} - reactions: confused - body: | - > CAT Tests completed. - > _**Important: Please check the job output to verify the results.**_ diff --git a/.github/workflows/run-live-tests-command.yml b/.github/workflows/run-live-tests-command.yml deleted file mode 100644 index 1cd701c93bc0..000000000000 --- a/.github/workflows/run-live-tests-command.yml +++ /dev/null @@ -1,177 +0,0 @@ -name: On-Demand Live Connector Validation Tests - -concurrency: - # This is the name of the concurrency group. It is used to prevent concurrent runs of the same workflow. - # - # - github.head_ref is only defined on PR runs, it makes sure that the concurrency group is unique for pull requests - # ensuring that only one run per pull request is active at a time. - # - # - github.run_id is defined on all runs, it makes sure that the concurrency group is unique for workflow dispatches. - # This allows us to run multiple workflow dispatches in parallel. - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - inputs: - # Global static-arg inputs for slash commands - repo: - description: "The repository name. Optional. Defaults to 'airbytehq/airbyte'." - required: false - default: "airbytehq/airbyte" - type: string - gitref: - description: "The git reference (branch or tag). Optional. Defaults to the default branch." - required: false - type: string - comment-id: - description: "The ID of the comment triggering the workflow. Optional." - required: false - type: number - pr: - description: "The pull request number, if applicable. Optional." - required: false - type: number - - # Workflow-specific inputs - connector_filter: - description: > - Connector filter. Will be passed to the `airbyte-ci connectors` command. - To select all modified connectors, use '--modified'. To select specific connectors, - pass one or or more `--name` args, e.g. '--name=source-faker --name=source-hardcoded-records'. - default: "--modified" - connection_id: - description: > - Connection ID. ID of the connection to test; use "auto" to let the - connection retriever choose a connection. - default: "auto" - streams: - description: > - (Optional) Streams. Which streams to include in tests. - If not set, these will be chosen automatically. - required: false - default: "" - type: string - should_read_with_state: - description: Whether to run tests against the read command with state - default: "true" - type: boolean - use_local_cdk: - description: Use the local CDK when building the target connector - default: "false" - type: boolean - disable_proxy: - description: Disable proxy for requests - default: "false" - type: boolean - - # Workaround: GitHub currently supports a max of 10 inputs for workflow_dispatch events. - # We need to consolidate some inputs to stay within this limit. - # connection_subset: - # description: The subset of connections to select from. - # default: "sandboxes" - # type: choice - # options: - # - sandboxes - # - all - -jobs: - live_tests: - name: Live Tests - runs-on: linux-24.04-large # Custom runner, defined in GitHub org settings - timeout-minutes: 360 # 6 hours - steps: - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Extract branch name [WORKFLOW DISPATCH] - shell: bash - if: github.event_name == 'workflow_dispatch' - run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT - id: extract_branch - - - name: Install Poetry - id: install_poetry - uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 - with: - version: 1.8.5 - - - name: Make poetry venv in project - id: poetry_venv - run: poetry config virtualenvs.in-project true - - - name: Install Python packages - id: install_python_packages - working-directory: airbyte-ci/connectors/pipelines - run: poetry install - - - name: Fetch last commit id from remote branch [WORKFLOW DISPATCH] - if: github.event_name == 'workflow_dispatch' - id: fetch_last_commit_id_wd - run: echo "commit_id=$(git rev-parse origin/${{ steps.extract_branch.outputs.branch }})" >> $GITHUB_OUTPUT - - - name: Setup Stream Parameters - if: github.event_name == 'workflow_dispatch' - run: | - if [ -z "${{ github.event.inputs.streams }}" ]; then - echo "STREAM_PARAMS=" >> $GITHUB_ENV - else - STREAMS=$(echo "${{ github.event.inputs.streams }}" | sed 's/,/ --connector_live_tests.selected-streams=/g') - echo "STREAM_PARAMS=--connector_live_tests.selected-streams=$STREAMS" >> $GITHUB_ENV - fi - - - name: Setup Local CDK Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.use_local_cdk }}; then - echo "USE_LOCAL_CDK_FLAG=--use-local-cdk" >> $GITHUB_ENV - else - echo "USE_LOCAL_CDK_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup State Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.should_read_with_state }}; then - echo "READ_WITH_STATE_FLAG=--connector_live_tests.should-read-with-state" >> $GITHUB_ENV - else - echo "READ_WITH_STATE_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup Proxy Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.disable_proxy }}; then - echo "DISABLE_PROXY_FLAG=--connector_live_tests.disable-proxy" >> $GITHUB_ENV - else - echo "DISABLE_PROXY_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup Connection Subset Option - if: github.event_name == 'workflow_dispatch' - run: | - echo "CONNECTION_SUBSET=--connector_live_tests.connection-subset=sandboxes" >> $GITHUB_ENV - # TODO: re-enable when we have resolved the more-than-10-inputs issue in workflow_dispatch. - # run: | - # echo "CONNECTION_SUBSET=--connector_live_tests.connection-subset=${{ github.event.inputs.connection_subset }}" >> $GITHUB_ENV - - # NOTE: We still use a PAT here (rather than a GitHub App) because the workflow needs - # permissions to add commits to our main repo as well as forks. This will only work on - # forks if the user installs the app into their fork. Until we document this as a clear - # path, we will have to keep using the PAT. - - name: Run Live Tests [WORKFLOW DISPATCH] - if: github.event_name == 'workflow_dispatch' # TODO: consider using the matrix strategy (https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs). See https://github.com/airbytehq/airbyte/pull/37659#discussion_r1583380234 for details. - uses: ./.github/actions/run-airbyte-ci - with: - context: "manual" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_3 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcp_integration_tester_credentials: ${{ secrets.GCLOUD_INTEGRATION_TESTER }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - git_branch: ${{ steps.extract_branch.outputs.branch }} - git_revision: ${{ steps.fetch_last_commit_id_pr.outputs.commit_id }} - github_token: ${{ secrets.GH_PAT_MAINTENANCE_OSS }} - s3_build_cache_access_key_id: ${{ secrets.SELF_RUNNER_AWS_ACCESS_KEY_ID }} - s3_build_cache_secret_key: ${{ secrets.SELF_RUNNER_AWS_SECRET_ACCESS_KEY }} - subcommand: connectors ${{ env.USE_LOCAL_CDK_FLAG }} ${{ inputs.connector_filter }} test --only-step connector_live_tests --connector_live_tests.test-suite=live --connector_live_tests.connection-id=${{ github.event.inputs.connection_id }} --connector_live_tests.pr-url="https://github.com/airbytehq/airbyte/pull/${{ github.event.inputs.pr }}" ${{ env.READ_WITH_STATE_FLAG }} ${{ env.DISABLE_PROXY_FLAG }} ${{ env.STREAM_PARAMS }} ${{ env.CONNECTION_SUBSET }} diff --git a/.github/workflows/run-regression-tests-command.yml b/.github/workflows/run-regression-tests-command.yml deleted file mode 100644 index 78dd5e0a74f0..000000000000 --- a/.github/workflows/run-regression-tests-command.yml +++ /dev/null @@ -1,315 +0,0 @@ -name: On-Demand Connector Regression Tests - -concurrency: - # This is the name of the concurrency group. It is used to prevent concurrent runs of the same workflow. - # - # - github.head_ref is only defined on PR runs, it makes sure that the concurrency group is unique for pull requests - # ensuring that only one run per pull request is active at a time. - # - # - github.run_id is defined on all runs, it makes sure that the concurrency group is unique for workflow dispatches. - # This allows us to run multiple workflow dispatches in parallel. - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - inputs: - # Global static-arg inputs for slash commands - repo: - description: "The repository name" - required: false - default: "airbytehq/airbyte" - type: string - gitref: - description: "The git reference (branch or tag)" - required: false - type: string - comment-id: - description: "The ID of the comment triggering the workflow" - required: false - type: number - pr: - description: "The pull request number, if applicable" - required: false - type: number - - # Workflow-specific inputs - connector_filter: - description: > - Connector filter. Will be passed to the `airbyte-ci connectors` command. - To select all modified connectors, use '--modified'. To select specific connectors, - pass one or or more `--name` args, e.g. '--name=source-faker --name=source-hardcoded-records'. - default: "--modified" - connection_id: - description: > - Connection ID. ID of the connection to test; use "auto" to let the - connection retriever choose a connection. - default: "auto" - streams: - description: > - (Optional) Streams. Which streams to include in tests. - If not set, these will be chosen automatically. - required: false - default: "" - type: string - should_read_with_state: - description: Whether to run tests against the read command with state - default: "true" - type: boolean - use_local_cdk: - description: Use the local CDK when building the target connector - default: "false" - type: boolean - disable_proxy: - description: Disable proxy for requests - default: "false" - type: boolean - - # Workaround: GitHub currently supports a max of 10 inputs for workflow_dispatch events. - # We need to consolidate some inputs to stay within this limit. - # connection_subset: - # description: The subset of connections to select from. - # default: "sandboxes" - # type: choice - # options: - # - sandboxes - # - all - # control_version: - # description: The version to use as a control version. This is useful when the version defined in the cloud registry does not have a lot of usage (either because a progressive rollout is underway or because a new version has just been released). - # required: false - # type: string - -jobs: - regression_tests: - name: Regression Tests - runs-on: linux-24.04-large # Custom runner, defined in GitHub org settings - timeout-minutes: 360 # 6 hours - permissions: - contents: read - pull-requests: write - issues: write - steps: - - name: Append start with run link - id: pr-comment-id - if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr != '' - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ github.token }} - issue-number: ${{ github.event.inputs.pr }} - comment-id: ${{ github.event.inputs.comment-id }} - edit-mode: append - body: | - > Starting regression tests (filter: `${{ github.event.inputs.connector_filter || '--modified' }}`) - > Workflow run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - - name: Install Python - id: install_python - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 - with: - python-version: "3.11" - check-latest: true - update-environment: true - - - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Extract branch name [WORKFLOW DISPATCH] - shell: bash - if: github.event_name == 'workflow_dispatch' - run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT - id: extract_branch - - - name: Install Poetry - id: install_poetry - uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 - with: - version: 1.8.5 - - - name: Make poetry venv in project - id: poetry_venv - run: poetry config virtualenvs.in-project true - - - name: Install Python packages - id: install_python_packages - working-directory: airbyte-ci/connectors/pipelines - run: poetry install - - - name: Fetch last commit id from remote branch [WORKFLOW DISPATCH] - if: github.event_name == 'workflow_dispatch' - id: fetch_last_commit_id_wd - run: echo "commit_id=$(git rev-parse origin/${{ steps.extract_branch.outputs.branch }})" >> $GITHUB_OUTPUT - - - name: Setup Stream Parameters - if: github.event_name == 'workflow_dispatch' - run: | - if [ -z "${{ github.event.inputs.streams }}" ]; then - echo "STREAM_PARAMS=" >> $GITHUB_ENV - else - STREAMS=$(echo "${{ github.event.inputs.streams }}" | sed 's/,/ --connector_live_tests.selected-streams=/g') - echo "STREAM_PARAMS=--connector_live_tests.selected-streams=$STREAMS" >> $GITHUB_ENV - fi - - - name: Setup Local CDK Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.use_local_cdk }}; then - echo "USE_LOCAL_CDK_FLAG=--use-local-cdk" >> $GITHUB_ENV - else - echo "USE_LOCAL_CDK_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup State Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.should_read_with_state }}; then - echo "READ_WITH_STATE_FLAG=--connector_live_tests.should-read-with-state" >> $GITHUB_ENV - else - echo "READ_WITH_STATE_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup Proxy Flag - if: github.event_name == 'workflow_dispatch' - run: | - if ${{ github.event.inputs.disable_proxy }}; then - echo "DISABLE_PROXY_FLAG=--connector_live_tests.disable-proxy" >> $GITHUB_ENV - else - echo "DISABLE_PROXY_FLAG=" >> $GITHUB_ENV - fi - - - name: Setup Connection Subset Option - if: github.event_name == 'workflow_dispatch' - run: | - echo "CONNECTION_SUBSET=--connector_live_tests.connection-subset=sandboxes" >> $GITHUB_ENV - # TODO: re-enable when we have resolved the more-than-10-inputs issue in workflow_dispatch. - # run: | - # echo "CONNECTION_SUBSET=--connector_live_tests.connection-subset=${{ github.event.inputs.connection_subset }}" >> $GITHUB_ENV - - - name: Setup Control Version - if: github.event_name == 'workflow_dispatch' - run: | - echo "CONTROL_VERSION=" >> $GITHUB_ENV - # TODO: re-enable when we have resolved the more-than-10-inputs issue in workflow_dispatch. - # run: | - # if [ -n "${{ github.event.inputs.control_version }}" ]; then - # echo "CONTROL_VERSION=--connector_live_tests.control-version=${{ github.event.inputs.control_version }}" >> $GITHUB_ENV - # else - # echo "CONTROL_VERSION=" >> $GITHUB_ENV - # fi - - # NOTE: We still use a PAT here (rather than a GitHub App) because the workflow needs - # permissions to add commits to our main repo as well as forks. This will only work on - # forks if the user installs the app into their fork. Until we document this as a clear - # path, we will have to keep using the PAT. - - name: Run Regression Tests [WORKFLOW DISPATCH] - id: run-regression-tests - if: github.event_name == 'workflow_dispatch' # TODO: consider using the matrix strategy (https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs). See https://github.com/airbytehq/airbyte/pull/37659#discussion_r1583380234 for details. - uses: ./.github/actions/run-airbyte-ci - with: - context: "manual" - dagger_cloud_token: ${{ secrets.DAGGER_CLOUD_TOKEN_CACHE_3 }} - docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }} - docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }} - gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }} - gcp_integration_tester_credentials: ${{ secrets.GCLOUD_INTEGRATION_TESTER }} - sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }} - git_branch: ${{ steps.extract_branch.outputs.branch }} - git_revision: ${{ steps.fetch_last_commit_id_pr.outputs.commit_id }} - github_token: ${{ secrets.GH_PAT_MAINTENANCE_OSS }} - s3_build_cache_access_key_id: ${{ secrets.SELF_RUNNER_AWS_ACCESS_KEY_ID }} - s3_build_cache_secret_key: ${{ secrets.SELF_RUNNER_AWS_SECRET_ACCESS_KEY }} - subcommand: connectors ${{ env.USE_LOCAL_CDK_FLAG }} ${{ inputs.connector_filter }} test --only-step connector_live_tests --connector_live_tests.test-suite=regression --connector_live_tests.connection-id=${{ github.event.inputs.connection_id }} --connector_live_tests.pr-url="https://github.com/airbytehq/airbyte/pull/${{ github.event.inputs.pr }}" ${{ env.READ_WITH_STATE_FLAG }} ${{ env.DISABLE_PROXY_FLAG }} ${{ env.STREAM_PARAMS }} ${{ env.CONNECTION_SUBSET }} ${{ env.CONTROL_VERSION }} --global-status-check-context="Regression Tests" --global-status-check-description='Running regression tests' - - - name: Locate regression test report - if: always() && github.event_name == 'workflow_dispatch' - id: locate-report - run: | - # Find the most recent report.html file in /tmp/live_tests_artifacts/ - REPORT_PATH=$(find /tmp/live_tests_artifacts -name "report.html" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -f2- -d" ") - if [ -n "$REPORT_PATH" ]; then - echo "report_path=$REPORT_PATH" >> "$GITHUB_OUTPUT" - echo "Found report at: $REPORT_PATH" - else - echo "report_path=" >> "$GITHUB_OUTPUT" - echo "No report.html found in /tmp/live_tests_artifacts/" - fi - - - name: Upload regression test report - if: always() && github.event_name == 'workflow_dispatch' && steps.locate-report.outputs.report_path != '' - uses: actions/upload-artifact@v4 - with: - name: regression-test-report - path: ${{ steps.locate-report.outputs.report_path }} - if-no-files-found: ignore - - - name: Append regression outcome - if: always() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr != '' - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ github.token }} - comment-id: ${{ steps.pr-comment-id.outputs.comment-id }} - edit-mode: append - body: | - > Regression tests: ${{ steps.run-regression-tests.outcome == 'success' && '✅ PASSED' || steps.run-regression-tests.outcome == 'failure' && '❌ FAILED' || steps.run-regression-tests.outcome == 'cancelled' && '⚠️ CANCELLED' || steps.run-regression-tests.outcome == 'skipped' && '⏭️ SKIPPED' || '❓ UNKNOWN' }} - > Report: ${{ steps.locate-report.outputs.report_path != '' && 'artifact `regression-test-report` available in the run' || 'not generated' }} - - - name: Install live-tests dependencies for LLM evaluation - if: always() && github.event_name == 'workflow_dispatch' - working-directory: airbyte-ci/connectors/live-tests - run: poetry install - - - name: Install and Start Ollama - if: always() && github.event_name == 'workflow_dispatch' - run: | - curl -fsSL https://ollama.com/install.sh | sh - ollama serve & - sleep 5 - ollama pull llama3.2:3b - echo "Ollama server started and model pulled" - - - name: Evaluate Regression Test Report with LLM - if: always() && github.event_name == 'workflow_dispatch' && steps.locate-report.outputs.report_path != '' - id: llm-eval - continue-on-error: true - working-directory: airbyte-ci/connectors/live-tests - env: - OPENAI_API_KEY: ollama - OPENAI_BASE_URL: http://127.0.0.1:11434/v1 - EVAL_MODEL: llama3.2:3b - run: | - set -u - echo "ran=false" >> "$GITHUB_OUTPUT" - echo "result=error" >> "$GITHUB_OUTPUT" - - REPORT_PATH="${{ steps.locate-report.outputs.report_path }}" - - if [ -z "$REPORT_PATH" ]; then - echo "Error: No report path provided from locate-report step" >&2 - echo "## ⚠️ LLM Evaluation Skipped" >> "$GITHUB_STEP_SUMMARY" - echo "No regression test report found. The tests may have failed to generate a report." >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - echo "Evaluating report at: $REPORT_PATH" - - # Run the evaluation script - OUT_JSON="$RUNNER_TEMP/llm_eval.json" - poetry run python src/live_tests/regression_tests/llm_evaluation/evaluate_report.py \ - --report-path "$REPORT_PATH" \ - --output-json "$OUT_JSON" - - # If we got here, script exit 0 and produced a judgment - PASS=$(jq -r '.evaluation.pass' "$OUT_JSON") - if [ "$PASS" = "true" ]; then RES="pass"; else RES="fail"; fi - echo "ran=true" >> "$GITHUB_OUTPUT" - echo "result=$RES" >> "$GITHUB_OUTPUT" - - - name: Append LLM outcome - if: always() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr != '' - env: - EVAL_MODEL: llama3.2:3b - uses: peter-evans/create-or-update-comment@v4 - with: - token: ${{ github.token }} - comment-id: ${{ steps.pr-comment-id.outputs.comment-id }} - edit-mode: append - body: | - > LLM Evaluation: ${{ steps.llm-eval.outputs.ran == 'true' && (steps.llm-eval.outputs.result == 'pass' && '✅ PASS' || steps.llm-eval.outputs.result == 'fail' && '❌ FAIL' || '⚠️ ERROR') || '⚠️ Did not run' }}${{ steps.llm-eval.outputs.ran == 'true' && format(' (model: {0})', env.EVAL_MODEL) || '' }} diff --git a/.github/workflows/slash-commands.yml b/.github/workflows/slash-commands.yml index f38e748f32f0..b9ebf9ea52b2 100644 --- a/.github/workflows/slash-commands.yml +++ b/.github/workflows/slash-commands.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -58,15 +58,16 @@ jobs: commands: | ai-canary-prerelease - ai-docs-review ai-create-docs-pr + ai-dev + ai-docs-review ai-prove-fix ai-release-watch ai-review approve-regression-tests + build-connector-images bump-progressive-rollout-version bump-version - build-connector-images force-merge format-fix poe diff --git a/.github/workflows/stale-community-issues.yaml b/.github/workflows/stale-community-issues.yaml index 2f1f95d07eea..f19f2827e65c 100644 --- a/.github/workflows/stale-community-issues.yaml +++ b/.github/workflows/stale-community-issues.yaml @@ -1,7 +1,9 @@ name: Stale Issue action on: + workflow_dispatch: schedule: - - cron: "0 9 * * *" + - cron: "0 1 * * *" + timezone: "America/Los_Angeles" jobs: close-issues: @@ -10,14 +12,15 @@ jobs: issues: write steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" repositories: "airbyte" app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - - uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1 + - uses: actions/stale@b5d41d4d0e39afb4e3d58060a07f6fb1ce2caa77 # v10.2.0 + id: stale with: any-of-labels: "community" exempt-issue-labels: "frozen" @@ -33,3 +36,13 @@ jobs: To keep it open, please comment to let us know why it is important to you and if it is still reproducible on recent versions of Airbyte. close-issue-message: "This issue was closed because it has been inactive for 20 days since being marked as stale." repo-token: ${{ steps.get-app-token.outputs.token }} + - name: Write job summary + if: always() + run: | + echo "## Stale Community Issues Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Staled Issues" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.stale.outputs.staled-issues-prs }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Closed Issues" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.stale.outputs.closed-issues-prs }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/stale-routed-issues.yaml b/.github/workflows/stale-routed-issues.yaml index 0d3201cf4aba..da0e6f3b3dab 100644 --- a/.github/workflows/stale-routed-issues.yaml +++ b/.github/workflows/stale-routed-issues.yaml @@ -1,7 +1,9 @@ name: Stale issue action on: + workflow_dispatch: schedule: - - cron: "* 8 * * *" + - cron: "0 2 * * *" + timezone: "America/Los_Angeles" jobs: close-issues: @@ -11,14 +13,15 @@ jobs: pull-requests: write steps: - name: Authenticate as GitHub App - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: get-app-token with: owner: "airbytehq" repositories: "airbyte" app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - - uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1 + - uses: actions/stale@b5d41d4d0e39afb4e3d58060a07f6fb1ce2caa77 # v10.2.0 + id: stale with: any-of-labels: "frozen" days-before-issue-stale: 365 @@ -31,3 +34,13 @@ jobs: To keep it open, please comment to let us know why it is important to you and if it is still reproducible on recent versions of Airbyte. close-issue-message: "This issue was closed because it has been inactive for 20 days since being marked as stale." repo-token: ${{ steps.get-app-token.outputs.token }} + - name: Write job summary + if: always() + run: | + echo "## Stale Routed Issues Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Staled Issues" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.stale.outputs.staled-issues-prs }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Closed Issues" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.stale.outputs.closed-issues-prs }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/sync-ai-connector-docs.yml b/.github/workflows/sync-ai-connector-docs.yml index c98f6ddbad35..e52ca1a25283 100644 --- a/.github/workflows/sync-ai-connector-docs.yml +++ b/.github/workflows/sync-ai-connector-docs.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout airbyte repo - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Checkout airbyte-agent-connectors - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: airbytehq/airbyte-agent-connectors path: agent-connectors-source @@ -48,7 +48,7 @@ jobs: run: rm -rf agent-connectors-source - name: Authenticate as GitHub App - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3.0.0 id: get-app-token with: owner: "airbytehq" @@ -57,7 +57,7 @@ jobs: private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} - name: Create PR if changes - uses: peter-evans/create-pull-request@0979079bc20c05bbbb590a56c21c4e2b1d1f1bbe # v6 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ steps.get-app-token.outputs.token }} commit-message: "docs: sync agent connector docs from airbyte-agent-connectors repo" diff --git a/.github/workflows/tk-todo-check.yml b/.github/workflows/tk-todo-check.yml index 61905fab1577..317ff9845273 100644 --- a/.github/workflows/tk-todo-check.yml +++ b/.github/workflows/tk-todo-check.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check for TK-TODO markers # IGNORE:TK uses: aaronsteers/tk-todo-check-action@v1.0.0 # IGNORE:TK diff --git a/.github/workflows/update-connector-cdk-version-command.yml b/.github/workflows/update-connector-cdk-version-command.yml index f214b8e26a56..bf161e900d45 100644 --- a/.github/workflows/update-connector-cdk-version-command.yml +++ b/.github/workflows/update-connector-cdk-version-command.yml @@ -44,7 +44,7 @@ jobs: > Update CDK version job started for `${{ github.event.inputs.connector }}`. Check the [job logs](${{ steps.resolve-job-vars.outputs.run-url }}) for details. - name: Checkout Airbyte - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true # Needed for airbyte-enterprise connectors (no-op otherwise) @@ -71,13 +71,13 @@ jobs: fi - name: Setup Java - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "21" - name: Setup Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Run CDK upgrade run: ./gradlew ":airbyte-integrations:connectors:${{ github.event.inputs.connector }}:upgradeCdk" diff --git a/.github/workflows/welcome-message.yml b/.github/workflows/welcome-message.yml index 52dcb64be7d8..40ed67c97506 100644 --- a/.github/workflows/welcome-message.yml +++ b/.github/workflows/welcome-message.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Render template id: template diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1170125df75e..0c04d32b0d77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,11 +13,6 @@ exclude: | ^.*?/source-amplitude/unit_tests/api_data/zipped\.json$| # Generated/test files - ^airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/.*$| - ^.*?/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/.*/invalid/.*$| - ^airbyte-ci/connectors/metadata_service/lib/tests/fixtures/.*/invalid/.*$| - ^.*?/airbyte-ci/connectors/pipelines/tests/test_format/non_formatted_code/.*$| - ^airbyte-ci/connectors/pipelines/tests/test_format/non_formatted_code/.*$| ^.*?/airbyte-integrations/connectors/destination-.*/expected-spec\.json$ ) diff --git a/.prettierignore b/.prettierignore index 53f0d6eb2221..a76db7a610d6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,4 @@ airbyte-integrations/bases/base-normalization/integration_tests/normalization_test_output -airbyte-ci/connectors/pipelines/tests/test_changelog/result_files -airbyte-integrations/bases/connector-acceptance-test/unit_tests/data/docs airbyte-integrations/connectors/destination-*/src/test-integration/resources/expected-spec*.json airbyte-integrations/connectors/destination-*/src/test-integration/resources/golden_files/**/*.json airbyte-cdk/bulk/*/*/src/test-integration/resources/expected-spec-*.json diff --git a/Makefile b/Makefile index 40be4473fadf..db03fa8a6f70 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,8 @@ ##@ Makefile -##@ Define the default airbyte-ci version -AIRBYTE_CI_VERSION ?= latest - ## Detect the operating system OS := $(shell uname) -tools.airbyte-ci.install: tools.airbyte-ci.clean tools.airbyte-ci-binary.install tools.airbyte-ci.check - -tools.airbyte-ci-binary.install: ## Install airbyte-ci binary - @python airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_install.py ${AIRBYTE_CI_VERSION} - -tools.airbyte-ci-dev.install: ## Install the local development version of airbyte-ci - @python airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_dev_install.py - -tools.airbyte-ci.check: ## Check if airbyte-ci is installed correctly - @./airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_check.sh - -tools.airbyte-ci.clean: ## Clean airbyte-ci installations - @./airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_clean.sh - tools.git-hooks.clean: ## Clean git hooks @echo "Unset core.hooksPath" @git config --unset core.hooksPath || true @@ -39,14 +22,14 @@ tools.pre-commit.install.Darwin: @brew install pre-commit maven @echo "Pre-commit installation complete" -tools.git-hooks.install: tools.airbyte-ci.install tools.pre-commit.install.$(OS) tools.git-hooks.clean ## Setup pre-commit hooks +tools.git-hooks.install: tools.pre-commit.install.$(OS) tools.git-hooks.clean ## Setup pre-commit hooks @echo "Installing pre-commit hooks..." @pre-commit install --hook-type pre-push @echo "Pre-push hooks installed." -tools.install: tools.airbyte-ci.install tools.pre-commit.install.$(OS) +tools.install: tools.pre-commit.install.$(OS) version.bulk.cdk: @echo "Latest version of the Bulk CDK is $(shell curl -k --silent "https://airbyte.mycloudrepo.io/public/repositories/airbyte-public-jars/io/airbyte/bulk-cdk/bulk-cdk-core-load/maven-metadata.xml" | sed -ne 's:.*\(.*\):\1:p')" -.PHONY: tools.install tools.pre-commit.install tools.git-hooks.install tools.git-hooks.clean tools.airbyte-ci.install tools.airbyte-ci-dev.install tools.airbyte-ci.check tools.airbyte-ci.clean +.PHONY: tools.install tools.pre-commit.install tools.git-hooks.install tools.git-hooks.clean diff --git a/airbyte-cdk/bulk/core/base/changelog.md b/airbyte-cdk/bulk/core/base/changelog.md index b535f521667c..8408984e522d 100644 --- a/airbyte-cdk/bulk/core/base/changelog.md +++ b/airbyte-cdk/bulk/core/base/changelog.md @@ -1,3 +1,7 @@ +## Version 1.0.2 + +Improved null handling for array types. + ## Version 1.0.1 Bump transitive deps with CVEs in unmaintained json schema lib (mbknor-jackson-jsonschema): upgrade classgraph to 4.8.112 (CVE-2021-47621, XXE) and scala-library to 2.13.9 (CVE-2022-36944, deserialization gadget chain). diff --git a/airbyte-cdk/bulk/core/base/src/main/kotlin/io/airbyte/cdk/data/JsonCodec.kt b/airbyte-cdk/bulk/core/base/src/main/kotlin/io/airbyte/cdk/data/JsonCodec.kt index 6d5c1079b39c..9732149b4c1b 100644 --- a/airbyte-cdk/bulk/core/base/src/main/kotlin/io/airbyte/cdk/data/JsonCodec.kt +++ b/airbyte-cdk/bulk/core/base/src/main/kotlin/io/airbyte/cdk/data/JsonCodec.kt @@ -2,6 +2,7 @@ package io.airbyte.cdk.data import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.NullNode import io.airbyte.cdk.util.Jsons import java.math.BigDecimal import java.net.URI @@ -187,7 +188,9 @@ data object FloatCodec : JsonCodec { } val decoded: Float = encoded.floatValue() if (encode(decoded).doubleValue().compareTo(encoded.doubleValue()) != 0) { - throw IllegalArgumentException("invalid IEEE-754 32-bit floating point value $encoded") + throw IllegalArgumentException( + "invalid IEEE-754 32-bit floating point value $encoded (type ${encoded.javaClass.canonicalName})" + ) } return decoded } @@ -367,18 +370,23 @@ data class ArrayEncoder( override fun encode(decoded: List): JsonNode = Jsons.arrayNode().apply { for (e in decoded) { - add(elementEncoder.encode(e)) + // Note: in generics, T can be nullable! + if (e == null) add(NullCodec.encode(e)) else add(elementEncoder.encode(e)) } } } data class ArrayDecoder( val elementDecoder: JsonDecoder, -) : JsonDecoder> { - override fun decode(encoded: JsonNode): List { +) : JsonDecoder> { + override fun decode(encoded: JsonNode): List { if (!encoded.isArray) { throw IllegalArgumentException("invalid array value $encoded") } - return encoded.elements().asSequence().map { elementDecoder.decode(it) }.toList() + return encoded + .elements() + .asSequence() + .map { if (it == null || it is NullNode) null else elementDecoder.decode(it) } + .toList() } } diff --git a/airbyte-cdk/bulk/core/base/version.properties b/airbyte-cdk/bulk/core/base/version.properties index 47a478aeda9f..325db2cfb7dc 100644 --- a/airbyte-cdk/bulk/core/base/version.properties +++ b/airbyte-cdk/bulk/core/base/version.properties @@ -1 +1 @@ -version=1.0.1 +version=1.0.2 diff --git a/airbyte-cdk/bulk/core/extract/build.gradle b/airbyte-cdk/bulk/core/extract/build.gradle index fd6d04516f5e..1e6c78af7601 100644 --- a/airbyte-cdk/bulk/core/extract/build.gradle +++ b/airbyte-cdk/bulk/core/extract/build.gradle @@ -1,7 +1,8 @@ dependencies { - api "io.airbyte.bulk-cdk:bulk-cdk-core-base:1.0.1" + api "io.airbyte.bulk-cdk:bulk-cdk-core-base:1.0.2" implementation 'org.apache.commons:commons-lang3:3.17.0' implementation 'hu.webarticum:tree-printer:3.2.1' + // TODO: Use a published version. Use a variable to share the version with the api dependency. testFixturesApi testFixtures(project(':airbyte-cdk:bulk:core:bulk-cdk-core-base')) } diff --git a/airbyte-cdk/bulk/core/extract/changelog.md b/airbyte-cdk/bulk/core/extract/changelog.md index 38ebc0142fbf..51edb94df628 100644 --- a/airbyte-cdk/bulk/core/extract/changelog.md +++ b/airbyte-cdk/bulk/core/extract/changelog.md @@ -7,11 +7,13 @@ The Extract CDK provides functionality for source connectors including schema di
Expand to review -| Version | Date | Pull Request | Subject | -|---------|------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.2 | 2026-02-24 | [74007](https://github.com/airbytehq/airbyte/pull/74007) | Fix infinite loop when cursor-based incremental sync encounters an empty table with prior state by caching NULL cursor upper bound values (toolkit). | -| 1.0.1 | 2026-02-24 | | Bump bulk-cdk-core-base to 1.0.1 to pick up CVE fixes (CVE-2021-47621, CVE-2022-36944). | -| 1.0.0 | 2026-02-02 | [#72376](https://github.com/airbytehq/airbyte/pull/72376) | Initial independent release of bulk-cdk-core-extract. Separate versioning for extract package begins. | +| Version | Date | Pull Request | Subject | +|---------|------------|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.1.1 | 2026-04-02 | [76054](https://github.com/airbytehq/airbyte/pull/76054) | Clarified CDC termination condition. Ensure querySingleValue returns one result or throws. | +| 1.1.0 | 2026-03-30 | [75636](https://github.com/airbytehq/airbyte/pull/75636) | CDC positions are partially ordered. Added optional CDC startup hook. Field is now EmittedField. | +| 1.0.2 | 2026-02-24 | [74007](https://github.com/airbytehq/airbyte/pull/74007) | Fix infinite loop when cursor-based incremental sync encounters an empty table with prior state by caching NULL cursor upper bound values (toolkit). | +| 1.0.1 | 2026-02-24 | | Bump bulk-cdk-core-base to 1.0.1 to pick up CVE fixes (CVE-2021-47621, CVE-2022-36944). | +| 1.0.0 | 2026-02-02 | [#72376](https://github.com/airbytehq/airbyte/pull/72376) | Initial independent release of bulk-cdk-core-extract. Separate versioning for extract package begins. |
diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoverOperation.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoverOperation.kt index 11828abb1fbf..e236ef5b7183 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoverOperation.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoverOperation.kt @@ -28,7 +28,7 @@ class DiscoverOperation( listOf(null) + metadataQuerier.streamNamespaces() for (namespace in namespaces) { for (streamID in metadataQuerier.streamNames(namespace)) { - val fields: List = metadataQuerier.fields(streamID) + val fields: List = metadataQuerier.fields(streamID) if (fields.isEmpty()) { log.info { "Ignoring stream '${streamID.name}' in '${namespace ?: ""}' because no fields were discovered." diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoveredStream.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoveredStream.kt index ec22807fb60a..b295705670a5 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoveredStream.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/DiscoveredStream.kt @@ -8,6 +8,6 @@ import io.airbyte.cdk.StreamIdentifier data class DiscoveredStream( val id: StreamIdentifier, - val columns: List, + val columns: List, val primaryKeyColumnIDs: List>, ) diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/Field.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/Field.kt index 6fa15b4f20ab..1a64a82df34d 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/Field.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/Field.kt @@ -13,11 +13,16 @@ import io.airbyte.cdk.data.OffsetDateTimeCodec import java.time.OffsetDateTime /** Internal equivalent of a [io.airbyte.protocol.models.Field]. */ -sealed interface FieldOrMetaField { +sealed interface DataOrMetaField { val id: String val type: FieldType } +@Deprecated( + message = "Use `DataOrMetaField` directly instead.", + replaceWith = ReplaceWith("DataOrMetaField") +) +typealias FieldOrMetaField = DataOrMetaField /** * Root of our own type hierarchy for Airbyte record fields. * @@ -40,20 +45,32 @@ interface LosslessFieldType : FieldType { val jsonDecoder: JsonDecoder<*> } +interface DataField : DataOrMetaField + +@Deprecated( + message = "Use `EmittedField` directly instead.", + replaceWith = ReplaceWith("EmittedField") +) +typealias Field = EmittedField /** * Internal equivalent of [io.airbyte.protocol.models.Field] for values which come from the source * itself, instead of being generated by the connector during its operation. */ -data class Field( +data class EmittedField( + override val id: String, + override val type: FieldType, +) : DataField + +data class NonEmittedField( override val id: String, override val type: FieldType, -) : FieldOrMetaField +) : DataField /** * Internal equivalent of [io.airbyte.protocol.models.Field] for values which are generated by the * connector itself during its operation, instead of coming from the source. */ -interface MetaField : FieldOrMetaField { +interface MetaField : DataOrMetaField { companion object { const val META_PREFIX = "_ab_" diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetaFieldDecorator.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetaFieldDecorator.kt index 5b436f87ee65..1c62ec4b9812 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetaFieldDecorator.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetaFieldDecorator.kt @@ -15,7 +15,7 @@ import java.time.OffsetDateTime interface MetaFieldDecorator { /** [MetaField] to use as a global cursor, if applicable. */ - val globalCursor: FieldOrMetaField? + val globalCursor: DataOrMetaField? /** * All [MetaField]s to be found in [Global] stream records. diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetadataQuerier.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetadataQuerier.kt index 83d6f2e0e331..36258750a612 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetadataQuerier.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/discover/MetadataQuerier.kt @@ -14,7 +14,7 @@ interface MetadataQuerier : AutoCloseable { fun streamNames(streamNamespace: String?): List /** Returns all available fields in the given stream. */ - fun fields(streamID: StreamIdentifier): List + fun fields(streamID: StreamIdentifier): List /** Returns the primary key for the given stream, if it exists; empty list otherwise. */ fun primaryKey(streamID: StreamIdentifier): List> diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/NativeRecord.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/NativeRecord.kt index 593e68c22554..4420eec3277a 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/NativeRecord.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/NativeRecord.kt @@ -10,11 +10,12 @@ import io.airbyte.cdk.data.ArrayEncoder import io.airbyte.cdk.data.BigDecimalIntegerCodec import io.airbyte.cdk.data.ByteCodec import io.airbyte.cdk.data.CdcOffsetDateTimeCodec +import io.airbyte.cdk.data.JsonCodec import io.airbyte.cdk.data.JsonEncoder import io.airbyte.cdk.data.NullCodec import io.airbyte.cdk.data.OffsetDateTimeCodec import io.airbyte.cdk.data.UrlCodec -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField import io.airbyte.cdk.protocol.AirbyteValueProtobufEncoder import io.airbyte.cdk.util.Jsons import io.airbyte.protocol.protobuf.AirbyteRecordMessage @@ -43,6 +44,10 @@ fun NativeRecordPayload.toJson(parentNode: ObjectNode = Jsons.objectNode()): Obj return parentNode } +interface ProtobufAwareCustomConnectorJsonCodec : JsonCodec { + fun valueForProtobufEncoding(v: T): Any? +} + /** * Transforms a field value into a protobuf-compatible representation. Handles special conversions * for types that need preprocessing before protobuf encoding, such as ByteBuffer -> Base64 String, @@ -57,13 +62,17 @@ fun valueForProtobufEncoding(fve: FieldValueEncoder): Any? { is CdcOffsetDateTimeCodec -> (value as OffsetDateTime).format(OffsetDateTimeCodec.formatter) is ArrayEncoder<*> -> fve.encode().toString() + is ProtobufAwareCustomConnectorJsonCodec<*> -> + (fve.jsonEncoder as ProtobufAwareCustomConnectorJsonCodec).valueForProtobufEncoding( + value + ) else -> value } } } fun NativeRecordPayload.toProtobuf( - schema: Set, + schema: Set, recordMessageBuilder: AirbyteRecordMessageProtobuf.Builder, valueBuilder: AirbyteRecordMessage.AirbyteValueProtobuf.Builder ): AirbyteRecordMessageProtobuf.Builder { diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/OutputMessageRouter.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/OutputMessageRouter.kt index 1e1772688e81..ab1cc72a319a 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/OutputMessageRouter.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/output/OutputMessageRouter.kt @@ -6,7 +6,7 @@ package io.airbyte.cdk.output import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import io.airbyte.cdk.StreamIdentifier -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.output.sockets.NativeRecordPayload import io.airbyte.cdk.output.sockets.SocketJsonOutputConsumer import io.airbyte.cdk.output.sockets.SocketProtobufOutputConsumer @@ -40,7 +40,7 @@ class OutputMessageRouter( Map.ProtoEfficientStreamRecordConsumer> private lateinit var simpleEfficientStreamConsumers: Map var recordAcceptors: - Map?) -> Unit> + Map?) -> Unit> init { when (recordsDataChannelMedium) { @@ -64,7 +64,7 @@ class OutputMessageRouter( it.key to { record: NativeRecordPayload, - changes: Map? -> + changes: Map? -> it.value.accept(record, changes) } } @@ -90,7 +90,7 @@ class OutputMessageRouter( it.key to { record: NativeRecordPayload, - changes: Map? -> + changes: Map? -> it.value.accept(record, changes) } } @@ -106,7 +106,7 @@ class OutputMessageRouter( it.key to { record: NativeRecordPayload, - changes: Map? -> + changes: Map? -> it.value.accept(record, changes) } } diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/Feed.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/Feed.kt index d921a7456d9e..ccbd773abcdd 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/Feed.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/Feed.kt @@ -2,8 +2,8 @@ package io.airbyte.cdk.read import io.airbyte.cdk.StreamIdentifier -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField +import io.airbyte.cdk.discover.EmittedField /** * [Feed] identifies part of the data consumed during a READ operation. @@ -30,10 +30,10 @@ data class Global( */ data class Stream( val id: StreamIdentifier, - val schema: Set, + val schema: Set, val configuredSyncMode: ConfiguredSyncMode, - val configuredPrimaryKey: List?, - val configuredCursor: FieldOrMetaField?, + val configuredPrimaryKey: List?, + val configuredCursor: DataOrMetaField?, ) : Feed { val name: String get() = id.name @@ -44,8 +44,8 @@ data class Stream( override val label: String get() = id.toString() - val fields: List - get() = schema.filterIsInstance() + val fields: List + get() = schema.filterIsInstance() } /** List of [Stream]s this [Feed] emits records for. */ diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/FeedBootstrap.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/FeedBootstrap.kt index 20026ab07feb..3377b177e18b 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/FeedBootstrap.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/FeedBootstrap.kt @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.node.ObjectNode import io.airbyte.cdk.StreamIdentifier import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.MetaFieldDecorator import io.airbyte.cdk.output.DataChannelFormat import io.airbyte.cdk.output.DataChannelMedium @@ -116,13 +116,13 @@ sealed class FeedBootstrap( override fun accept( recordData: NativeRecordPayload, - changes: Map? + changes: Map? ) { if (changes.isNullOrEmpty()) { acceptWithoutChanges(recordData.toJson()) } else { val protocolChanges: List = - changes.map { (field: Field, fieldValueChange: FieldValueChange) -> + changes.map { (field: EmittedField, fieldValueChange: FieldValueChange) -> AirbyteRecordMessageMetaChange() .withField(field.id) .withChange(fieldValueChange.protocolChange()) @@ -230,7 +230,7 @@ sealed class FeedBootstrap( val valueVBuilder = AirbyteValueProtobuf.newBuilder()!! override fun accept( recordData: NativeRecordPayload, - changes: Map? + changes: Map? ) { if (changes.isNullOrEmpty()) { acceptWithoutChanges( @@ -240,7 +240,7 @@ sealed class FeedBootstrap( val rm = AirbyteRecordMessageMetaOuterClass.AirbyteRecordMessageMeta.newBuilder() val c = AirbyteRecordMessageMetaOuterClass.AirbyteRecordMessageMetaChange.newBuilder() - changes.forEach { (field: Field, fieldValueChange: FieldValueChange) -> + changes.forEach { (field: EmittedField, fieldValueChange: FieldValueChange) -> c.clear() .setField(field.id) .setChange(fieldValueChange.protobufChange()) @@ -493,7 +493,7 @@ interface StreamRecordConsumer { val stream: Stream - fun accept(recordData: NativeRecordPayload, changes: Map?) + fun accept(recordData: NativeRecordPayload, changes: Map?) fun close() } diff --git a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/StateManagerFactory.kt b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/StateManagerFactory.kt index e378c8036d3a..7fc4d70dcab3 100644 --- a/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/StateManagerFactory.kt +++ b/airbyte-cdk/bulk/core/extract/src/main/kotlin/io/airbyte/cdk/read/StateManagerFactory.kt @@ -14,8 +14,8 @@ import io.airbyte.cdk.command.StreamInputState import io.airbyte.cdk.data.AirbyteSchemaType import io.airbyte.cdk.data.ArrayAirbyteSchemaType import io.airbyte.cdk.data.LeafAirbyteSchemaType -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.MetaField import io.airbyte.cdk.discover.MetaFieldDecorator import io.airbyte.cdk.discover.MetadataQuerier @@ -197,16 +197,16 @@ class StateManagerFactory( jsonSchemaProperties.properties().associate { (id: String, schema: JsonNode) -> id to airbyteTypeFromJsonSchema(schema) } - val actualDataColumns: Map = + val actualDataColumns: Map = metadataQuerier.fields(streamID).associateBy { it.id } - fun dataColumnOrNull(id: String): Field? { + fun dataColumnOrNull(id: String): EmittedField? { if (MetaField.isMetaFieldID(id)) { // Ignore airbyte metadata columns. // These aren't actually present in the table. return null } - val actualColumn: Field? = actualDataColumns[id] + val actualColumn: EmittedField? = actualDataColumns[id] if (actualColumn == null) { handler.accept(FieldNotFound(streamID, id)) return null @@ -226,7 +226,7 @@ class StateManagerFactory( } return actualColumn } - val streamFields: List = + val streamFields: List = expectedSchema.keys.toList().filterNot(MetaField::isMetaFieldID).map { dataColumnOrNull(it) ?: return@toStream null } @@ -241,13 +241,13 @@ class StateManagerFactory( return null } - fun pkOrNull(pkColumnIDComponents: List>): List? { + fun pkOrNull(pkColumnIDComponents: List>): List? { if (pkColumnIDComponents.isEmpty()) { return null } val pkColumnIDs: List = pkColumnIDComponents.map { it.joinToString(separator = ".") } - val pk: List = pkColumnIDs.mapNotNull(::dataColumnOrNull) + val pk: List = pkColumnIDs.mapNotNull(::dataColumnOrNull) if (pk.size < pkColumnIDComponents.size) { handler.accept(InvalidPrimaryKey(streamID, pkColumnIDs)) return null @@ -255,7 +255,7 @@ class StateManagerFactory( return pk } - fun cursorOrNull(cursorColumnIDComponents: List): FieldOrMetaField? { + fun cursorOrNull(cursorColumnIDComponents: List): DataOrMetaField? { if (cursorColumnIDComponents.isEmpty()) { return null } @@ -265,14 +265,15 @@ class StateManagerFactory( } return dataColumnOrNull(cursorColumnID) } - val configuredPrimaryKey: List? = + val configuredPrimaryKey: List? = configuredStream.primaryKey?.asSequence()?.let { pkOrNull(it.toList()) } - val configuredCursor: FieldOrMetaField? = + val configuredCursor: DataOrMetaField? = configuredStream.cursorField?.asSequence()?.let { cursorOrNull(it.toList()) } + val sourceDefinedCursor = configuredStream.stream.sourceDefinedCursor val configuredSyncMode: ConfiguredSyncMode = when (configuredStream.syncMode) { SyncMode.INCREMENTAL -> - if (configuredCursor == null) { + if (configuredCursor == null && sourceDefinedCursor.not()) { handler.accept(InvalidIncrementalSyncMode(streamID)) ConfiguredSyncMode.FULL_REFRESH } else { diff --git a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/output/sockets/NativeRecordProtobufEncoderTest.kt b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/output/sockets/NativeRecordProtobufEncoderTest.kt index 73667ba0221a..313ae5aca566 100644 --- a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/output/sockets/NativeRecordProtobufEncoderTest.kt +++ b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/output/sockets/NativeRecordProtobufEncoderTest.kt @@ -26,7 +26,7 @@ import io.airbyte.cdk.data.OffsetTimeCodec import io.airbyte.cdk.data.ShortCodec import io.airbyte.cdk.data.TextCodec import io.airbyte.cdk.data.UrlCodec -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.FieldType import io.airbyte.cdk.protocol.AirbyteValueProtobufDecoder import io.airbyte.protocol.protobuf.AirbyteRecordMessage.AirbyteRecordMessageProtobuf @@ -77,8 +77,8 @@ class NativeRecordProtobufEncoderTest { val protoBuilder = AirbyteRecordMessageProtobuf.newBuilder().also { it.addData(0, valBuilder.clear()) } - fun fieldOf(airbyteSchemaType: AirbyteSchemaType, jsonEncoder: JsonEncoder<*>): Field = - Field( + fun fieldOf(airbyteSchemaType: AirbyteSchemaType, jsonEncoder: JsonEncoder<*>): EmittedField = + EmittedField( "id", object : FieldType { override val airbyteSchemaType = airbyteSchemaType diff --git a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/FeedBootstrapTest.kt b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/FeedBootstrapTest.kt index 257edf297fb3..547ff7c98a4b 100644 --- a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/FeedBootstrapTest.kt +++ b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/FeedBootstrapTest.kt @@ -12,7 +12,7 @@ import io.airbyte.cdk.data.LocalDateTimeCodec import io.airbyte.cdk.data.NullCodec import io.airbyte.cdk.data.TextCodec import io.airbyte.cdk.discover.CommonMetaField -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.IntFieldType import io.airbyte.cdk.discover.StringFieldType import io.airbyte.cdk.discover.TestMetaFieldDecorator @@ -37,8 +37,8 @@ class FeedBootstrapTest { @Inject lateinit var metaFieldDecorator: TestMetaFieldDecorator - val k = Field("k", IntFieldType) - val v = Field("v", StringFieldType) + val k = EmittedField("k", IntFieldType) + val v = EmittedField("v", StringFieldType) val stream: Stream = Stream( id = StreamIdentifier.from(StreamDescriptor().withName("tbl").withNamespace("ns")), diff --git a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/StreamStatusManagerTest.kt b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/StreamStatusManagerTest.kt index 0a06c9af9936..3fc7115583d5 100644 --- a/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/StreamStatusManagerTest.kt +++ b/airbyte-cdk/bulk/core/extract/src/test/kotlin/io/airbyte/cdk/read/StreamStatusManagerTest.kt @@ -5,7 +5,7 @@ package io.airbyte.cdk.read import io.airbyte.cdk.StreamIdentifier -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.IntFieldType import io.airbyte.protocol.models.v0.AirbyteStreamStatusTraceMessage import io.airbyte.protocol.models.v0.StreamDescriptor @@ -17,7 +17,7 @@ class StreamStatusManagerTest { val streamIncremental = Stream( id = StreamIdentifier.from(StreamDescriptor().withName("streamIncremental")), - schema = setOf(Field("v", IntFieldType)), + schema = setOf(EmittedField("v", IntFieldType)), configuredSyncMode = ConfiguredSyncMode.INCREMENTAL, configuredPrimaryKey = null, configuredCursor = null, @@ -25,7 +25,7 @@ class StreamStatusManagerTest { val streamFullRefresh = Stream( id = StreamIdentifier.from(StreamDescriptor().withName("streamFullRefresh")), - schema = setOf(Field("v", IntFieldType)), + schema = setOf(EmittedField("v", IntFieldType)), configuredSyncMode = ConfiguredSyncMode.FULL_REFRESH, configuredPrimaryKey = null, configuredCursor = null, diff --git a/airbyte-cdk/bulk/core/extract/src/testFixtures/kotlin/io/airbyte/cdk/discover/ResourceDrivenMetadataQuerierFactory.kt b/airbyte-cdk/bulk/core/extract/src/testFixtures/kotlin/io/airbyte/cdk/discover/ResourceDrivenMetadataQuerierFactory.kt index be430854814c..c76277c516fb 100644 --- a/airbyte-cdk/bulk/core/extract/src/testFixtures/kotlin/io/airbyte/cdk/discover/ResourceDrivenMetadataQuerierFactory.kt +++ b/airbyte-cdk/bulk/core/extract/src/testFixtures/kotlin/io/airbyte/cdk/discover/ResourceDrivenMetadataQuerierFactory.kt @@ -33,11 +33,11 @@ class ResourceDrivenMetadataQuerierFactory( val streamID: StreamIdentifier = StreamIdentifier.from(desc) map[streamID] = null val level2: Level2 = level1.metadata ?: continue - val columns: List = + val columns: List = level2.columns.map { (id: String, fullyQualifiedClassName: String) -> val fieldType: FieldType = Class.forName(fullyQualifiedClassName).kotlin.objectInstance as FieldType - Field(id, fieldType) + EmittedField(id, fieldType) } map[streamID] = TestStreamMetadata(columns, level2.primaryKeys) } @@ -60,7 +60,7 @@ class ResourceDrivenMetadataQuerierFactory( override fun fields( streamID: StreamIdentifier, - ): List { + ): List { if (isClosed) throw IllegalStateException() return metadata[streamID]?.fields ?: throw SQLException("query failed", "tbl") } @@ -81,7 +81,7 @@ class ResourceDrivenMetadataQuerierFactory( } data class TestStreamMetadata( - val fields: List, + val fields: List, val primaryKeys: List>, ) diff --git a/airbyte-cdk/bulk/core/extract/version.properties b/airbyte-cdk/bulk/core/extract/version.properties index 325db2cfb7dc..ef0464df9b25 100644 --- a/airbyte-cdk/bulk/core/extract/version.properties +++ b/airbyte-cdk/bulk/core/extract/version.properties @@ -1 +1 @@ -version=1.0.2 +version=1.1.1 diff --git a/airbyte-cdk/bulk/core/load/changelog.md b/airbyte-cdk/bulk/core/load/changelog.md index a3c514b8e25c..449647adefce 100644 --- a/airbyte-cdk/bulk/core/load/changelog.md +++ b/airbyte-cdk/bulk/core/load/changelog.md @@ -9,6 +9,7 @@ The Load CDK provides functionality for destination connectors including stream- | Version | Date | Pull Request | Subject | |---------|------------|--------------|-------------------------------------------------------------------------------------------------| +| 1.0.7 | 2026-03-27 | | Fix: update Iceberg sort order before schema evolution to prevent ValidationException when deleting columns referenced by the sort order. Handles Dedupe-to-Append mode switches and PK changes. | | 1.0.6 | 2026-03-12 | [#74715](https://github.com/airbytehq/airbyte/pull/74715) | Fix: drop temp table after successful upsert to prevent duplicate records across syncs. | | 1.0.5 | 2026-03-10 | [#74723](https://github.com/airbytehq/airbyte/pull/74723) | Fix schema evolution: defer identifier field update when replacing columns to avoid Iceberg conflict. | | 1.0.4 | 2026-03-05 | [#74328](https://github.com/airbytehq/airbyte/pull/74328) | Fix iceberg dedup: map PK NumberType to StringType instead of DecimalType for identifier field compatibility. | diff --git a/airbyte-cdk/bulk/core/load/version.properties b/airbyte-cdk/bulk/core/load/version.properties index 71a5aa490498..4659c1221749 100644 --- a/airbyte-cdk/bulk/core/load/version.properties +++ b/airbyte-cdk/bulk/core/load/version.properties @@ -1 +1 @@ -version=1.0.6 +version=1.0.7 diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReader.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReader.kt index 8f74623bf412..0efb3f16a6d5 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReader.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReader.kt @@ -46,7 +46,7 @@ import org.apache.kafka.connect.source.SourceRecord /** [PartitionReader] implementation for CDC with Debezium. */ @SuppressFBWarnings(value = ["NP_NONNULL_RETURN_VIOLATION"], justification = "Micronaut DI") -class CdcPartitionReader>( +class CdcPartitionReader>( val resourceAcquirer: ResourceAcquirer, val readerOps: CdcPartitionReaderDebeziumOperations, val upperBound: T, @@ -413,7 +413,9 @@ class CdcPartitionReader>( if (currentPosition == null) { return null } - val isProgressing = lastHeartbeatPosition?.let { currentPosition > it } ?: true + val isProgressing = + lastHeartbeatPosition == null || + currentPosition.isGreater(lastHeartbeatPosition) if (isProgressing) { lastHeartbeatPosition = currentPosition lastHeartbeatTime = now @@ -433,20 +435,21 @@ class CdcPartitionReader>( } } - if (currentPosition == null || currentPosition < upperBound) { - return null - } - // Close because the current event is past the sync upper bound. - return when (eventType) { - EventType.TOMBSTONE, - EventType.HEARTBEAT -> CloseReason.HEARTBEAT_OR_TOMBSTONE_REACHED_TARGET_POSITION - EventType.KEY_JSON_INVALID, - EventType.VALUE_JSON_INVALID, - EventType.RECORD_EMITTED, - EventType.RECORD_DISCARDED_BY_DESERIALIZE, - EventType.RECORD_DISCARDED_BY_STREAM_ID -> - CloseReason.RECORD_REACHED_TARGET_POSITION + if (currentPosition.isGreaterOrEqual(upperBound)) { + // Close because the current event is at or past the sync upper bound. + return when (eventType) { + EventType.TOMBSTONE, + EventType.HEARTBEAT -> + CloseReason.HEARTBEAT_OR_TOMBSTONE_REACHED_TARGET_POSITION + EventType.KEY_JSON_INVALID, + EventType.VALUE_JSON_INVALID, + EventType.RECORD_EMITTED, + EventType.RECORD_DISCARDED_BY_DESERIALIZE, + EventType.RECORD_DISCARDED_BY_STREAM_ID -> + CloseReason.RECORD_REACHED_TARGET_POSITION + } } + return null // Keep processing. } private fun position(sourceRecord: SourceRecord?): T? { diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderDebeziumOperations.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderDebeziumOperations.kt index 516176f80c09..5b67d1380c5e 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderDebeziumOperations.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderDebeziumOperations.kt @@ -8,7 +8,7 @@ import io.airbyte.cdk.command.OpaqueStateValue import io.airbyte.cdk.read.Stream import org.apache.kafka.connect.source.SourceRecord -interface CdcPartitionReaderDebeziumOperations> { +interface CdcPartitionReaderDebeziumOperations> { /** * Transforms a [DebeziumRecordKey] and a [DebeziumRecordValue] into a [DeserializedRecord]. diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreator.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreator.kt index 0c39ab4f3601..e0c2537d844e 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreator.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreator.kt @@ -16,7 +16,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging import java.util.concurrent.atomic.AtomicReference /** [PartitionsCreator] implementation for CDC with Debezium. */ -class CdcPartitionsCreator>( +class CdcPartitionsCreator>( val concurrencyResource: ConcurrencyResource, val resourceAcquirer: ResourceAcquirer, val feedBootstrap: GlobalFeedBootstrap, @@ -105,6 +105,8 @@ class CdcPartitionsCreator>( startingSchemaHistory = null } } + // Optional startup hook + creatorOps.runStartup(startingOffset) // Build and return PartitionReader instance, if applicable. val partitionReader = CdcPartitionReader( @@ -125,14 +127,14 @@ class CdcPartitionsCreator>( log.info { "Current offset is synthetic." } return listOf(partitionReader) } - if (upperBound <= lowerBound) { + if (lowerBound.isGreaterOrEqual(upperBound)) { // Handle completion due to reaching the WAL position upper bound. log.info { "Current position '$lowerBound' equals or exceeds target position '$upperBound'." } return emptyList() } - if (lowerBoundInPreviousRound != null && lowerBound <= lowerBoundInPreviousRound) { + if (lowerBoundInPreviousRound.isGreaterOrEqual(lowerBound)) { // Handle completion due to stalling. log.info { "Current position '$lowerBound' has not increased in the last round, " + diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorDebeziumOperations.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorDebeziumOperations.kt index 6666779db9c0..e49207657167 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorDebeziumOperations.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorDebeziumOperations.kt @@ -6,8 +6,27 @@ package io.airbyte.cdk.read.cdc import io.airbyte.cdk.command.OpaqueStateValue import io.airbyte.cdk.read.Stream +import java.util.concurrent.atomic.AtomicBoolean -interface CdcPartitionsCreatorDebeziumOperations> { +interface CdcPartitionsCreatorDebeziumOperations> { + + companion object { + private val hasRunStartup = AtomicBoolean(false) + + fun shouldRunStartup(): Boolean { + return hasRunStartup.compareAndSet(false, true) + } + } + + /** Run optional startup hook if it hasn't already been run */ + fun runStartup(offset: DebeziumOffset) { + if (shouldRunStartup()) { + startup(offset) + } + } + + /** Optional startup hook. */ + fun startup(offset: DebeziumOffset) {} /** Extracts the WAL position from a [DebeziumOffset]. */ fun position(offset: DebeziumOffset): T diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorFactory.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorFactory.kt index fc347ee772a2..da14c54c992d 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorFactory.kt @@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicReference @Singleton @Order(10) /** [PartitionsCreatorFactory] implementation for CDC with Debezium. */ -class CdcPartitionsCreatorFactory>( +class CdcPartitionsCreatorFactory>( val concurrencyResource: ConcurrencyResource, val resourceAcquirer: ResourceAcquirer, val cdcPartitionsCreatorDbzOps: CdcPartitionsCreatorDebeziumOperations, @@ -60,8 +60,8 @@ class CdcPartitionsCreatorFactory>( } @Singleton -class CdcPartitionsCreatorFactorySupplier, C : Comparable>( - val factory: T -) : PartitionsCreatorFactorySupplier { - override fun get(): T = factory +class CdcPartitionsCreatorFactorySupplier< + F : CdcPartitionsCreatorFactory

, P : PartiallyOrdered

>(val factory: F) : + PartitionsCreatorFactorySupplier { + override fun get(): F = factory } diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumOperations.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumOperations.kt index a90b8390a386..992dd5063bd5 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumOperations.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumOperations.kt @@ -6,5 +6,5 @@ package io.airbyte.cdk.read.cdc /** Stateless connector-specific Debezium operations. */ @Deprecated("Implement the two interfaces separately") -interface DebeziumOperations> : +interface DebeziumOperations> : CdcPartitionsCreatorDebeziumOperations, CdcPartitionReaderDebeziumOperations diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilder.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilder.kt index 5f74adbb9d75..7563196d5a7d 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilder.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilder.kt @@ -159,6 +159,9 @@ class DebeziumPropertiesBuilder(private val props: Properties = Properties()) { } } + fun withHeartbeatTimeout(timeout: Duration): DebeziumPropertiesBuilder = + with(AIRBYTE_HEARTBEAT_TIMEOUT_SECONDS, timeout.seconds.toString()) + companion object { private const val BYTE_VALUE_256_MB = (256 * 1024 * 1024).toString() diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DeserializedRecord.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DeserializedRecord.kt index 240a07a99d6e..79d70a94ce11 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DeserializedRecord.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/DeserializedRecord.kt @@ -4,12 +4,12 @@ package io.airbyte.cdk.read.cdc -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.output.sockets.NativeRecordPayload import io.airbyte.cdk.read.FieldValueChange /** [DeserializedRecord]s are used to generate Airbyte RECORD messages. */ data class DeserializedRecord( val data: NativeRecordPayload, - val changes: Map, + val changes: Map, ) diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/PartiallyOrdered.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/PartiallyOrdered.kt new file mode 100644 index 000000000000..c5e86d0b2561 --- /dev/null +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/main/kotlin/io/airbyte/cdk/read/cdc/PartiallyOrdered.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.cdk.read.cdc + +/** + * Represents the mathematical concept of partial ordering. Unlike Comparable, the compareTo method + * can return null when the two elements don't have a natural ordering. + * + * We use this interface to compare CDC positions to see when we have reached the target offset or + * to determine whether we are still making progress. We need partial ordering rather than a full + * ordering like Comparable because sometimes we are missing some fields in the offset and can't + * determine whether one offset is beyond another. In these cases, we typically continue until we + * encounter a fully populated offset. + * + * The supplied convenience methods handle the null checking and allow us to make these kinds of + * comparisons ergonomically. + */ +fun interface PartiallyOrdered { + fun compareTo(other: T): Int? +} + +// Convenience methods +fun > T?.isGreater(other: T?): Boolean { + if (this == null || other == null) return false + val comparison = this.compareTo(other) + return comparison != null && comparison > 0 +} + +fun > T?.isGreaterOrEqual(other: T?): Boolean { + if (this == null || other == null) return false + val comparison = this.compareTo(other) + return comparison != null && comparison >= 0 +} diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMongoTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMongoTest.kt index 20ef01c8953b..8299734a0fd4 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMongoTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMongoTest.kt @@ -27,9 +27,17 @@ import org.bson.Document import org.bson.conversions.Bson import org.junit.jupiter.api.Disabled +data class BsonTimestampPosition(val timestamp: BsonTimestamp) : + PartiallyOrdered { + override fun compareTo(other: BsonTimestampPosition): Int? { + val timeCmp = timestamp.time.compareTo(other.timestamp.time) + return if (timeCmp != 0) timeCmp else timestamp.inc.compareTo(other.timestamp.inc) + } +} + @Disabled class CdcPartitionReaderMongoTest : - AbstractCdcPartitionReaderTest( + AbstractCdcPartitionReaderTest( namespace = "test", ) { @@ -83,16 +91,19 @@ class CdcPartitionReaderMongoTest : } override fun createCdcPartitionsCreatorDbzOps(): - CdcPartitionsCreatorDebeziumOperations = TestCdcPartitionsCreatorDbzOps() + CdcPartitionsCreatorDebeziumOperations = + TestCdcPartitionsCreatorDbzOps() override fun createCdcPartitionReaderDbzOps(): - CdcPartitionReaderDebeziumOperations = TestCdcPartitionReaderDbzOps() + CdcPartitionReaderDebeziumOperations = TestCdcPartitionReaderDbzOps() inner class TestCdcPartitionsCreatorDbzOps : - AbstractCdcPartitionsCreatorDbzOps() { - override fun position(offset: DebeziumOffset): BsonTimestamp { + AbstractCdcPartitionsCreatorDbzOps() { + override fun position(offset: DebeziumOffset): BsonTimestampPosition { val offsetValue: ObjectNode = offset.wrapped.values.first() as ObjectNode - return BsonTimestamp(offsetValue["sec"].asInt(), offsetValue["ord"].asInt()) + return BsonTimestampPosition( + BsonTimestamp(offsetValue["sec"].asInt(), offsetValue["ord"].asInt()) + ) } override fun generateColdStartOffset(): DebeziumOffset { @@ -146,17 +157,22 @@ class CdcPartitionReaderMongoTest : } } - inner class TestCdcPartitionReaderDbzOps : AbstractCdcPartitionReaderDbzOps() { - override fun position(recordValue: DebeziumRecordValue): BsonTimestamp? { + inner class TestCdcPartitionReaderDbzOps : + AbstractCdcPartitionReaderDbzOps() { + override fun position(recordValue: DebeziumRecordValue): BsonTimestampPosition? { val resumeToken: String = recordValue.source["resume_token"]?.takeIf { it.isTextual }?.asText() ?: return null - return ResumeTokens.getTimestamp(ResumeTokens.fromData(resumeToken)) + return BsonTimestampPosition( + ResumeTokens.getTimestamp(ResumeTokens.fromData(resumeToken)) + ) } - override fun position(sourceRecord: SourceRecord): BsonTimestamp? { + override fun position(sourceRecord: SourceRecord): BsonTimestampPosition? { val offset: Map = sourceRecord.sourceOffset() val resumeTokenBase64: String = offset["resume_token"] as? String ?: return null - return ResumeTokens.getTimestamp(ResumeTokens.fromBase64(resumeTokenBase64)) + return BsonTimestampPosition( + ResumeTokens.getTimestamp(ResumeTokens.fromBase64(resumeTokenBase64)) + ) } override fun deserializeRecord( diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMySQLTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMySQLTest.kt index 1000b7f87480..13c410139335 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMySQLTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderMySQLTest.kt @@ -24,8 +24,8 @@ class CdcPartitionReaderMySQLTest : namespace = "test", ) { - data class Position(val file: String, val pos: Long) : Comparable { - override fun compareTo(other: Position): Int = + data class Position(val file: String, val pos: Long) : PartiallyOrdered { + override fun compareTo(other: Position): Int? = file.compareTo(other.file).takeUnless { it == 0 } ?: pos.compareTo(other.pos) } diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderPostgresTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderPostgresTest.kt index 74eff950da29..d09c3796ecc4 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderPostgresTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionReaderPostgresTest.kt @@ -18,8 +18,12 @@ import org.postgresql.replication.LogSequenceNumber import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.utility.DockerImageName +data class LsnPosition(val lsn: LogSequenceNumber) : PartiallyOrdered { + override fun compareTo(other: LsnPosition): Int? = lsn.asLong().compareTo(other.lsn.asLong()) +} + class CdcPartitionReaderPostgresTest : - AbstractCdcPartitionReaderTest>( + AbstractCdcPartitionReaderTest>( namespace = "public", ) { @@ -77,11 +81,10 @@ class CdcPartitionReaderPostgresTest : override fun createCdcPartitionReaderDbzOps() = TestCdcPartitionReaderDbzOps() - inner class TestCdcPartitionsCreatorDbzOps : - AbstractCdcPartitionsCreatorDbzOps() { - override fun position(offset: DebeziumOffset): LogSequenceNumber { + inner class TestCdcPartitionsCreatorDbzOps : AbstractCdcPartitionsCreatorDbzOps() { + override fun position(offset: DebeziumOffset): LsnPosition { val offsetValue: ObjectNode = offset.wrapped.values.first() as ObjectNode - return LogSequenceNumber.valueOf(offsetValue["lsn"].asLong()) + return LsnPosition(LogSequenceNumber.valueOf(offsetValue["lsn"].asLong())) } override fun generateWarmStartProperties(streams: List): Map = @@ -131,18 +134,17 @@ class CdcPartitionReaderPostgresTest : } } - inner class TestCdcPartitionReaderDbzOps : - AbstractCdcPartitionReaderDbzOps() { - override fun position(recordValue: DebeziumRecordValue): LogSequenceNumber? { + inner class TestCdcPartitionReaderDbzOps : AbstractCdcPartitionReaderDbzOps() { + override fun position(recordValue: DebeziumRecordValue): LsnPosition? { val lsn: Long = recordValue.source["lsn"]?.takeIf { it.isIntegralNumber }?.asLong() ?: return null - return LogSequenceNumber.valueOf(lsn) + return LsnPosition(LogSequenceNumber.valueOf(lsn)) } - override fun position(sourceRecord: SourceRecord): LogSequenceNumber? { + override fun position(sourceRecord: SourceRecord): LsnPosition? { val offset: Map = sourceRecord.sourceOffset() val lsn: Long = offset["lsn"] as? Long ?: return null - return LogSequenceNumber.valueOf(lsn) + return LsnPosition(LogSequenceNumber.valueOf(lsn)) } } } diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorTest.kt index e7c93a81efa0..c09855c5a693 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/CdcPartitionsCreatorTest.kt @@ -6,7 +6,7 @@ package io.airbyte.cdk.read.cdc import io.airbyte.cdk.ConfigErrorException import io.airbyte.cdk.StreamIdentifier -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.StringFieldType import io.airbyte.cdk.discover.TestMetaFieldDecorator import io.airbyte.cdk.read.ConcurrencyResource @@ -18,9 +18,11 @@ import io.airbyte.cdk.read.ResourceAcquirer import io.airbyte.cdk.read.Stream import io.airbyte.cdk.util.Jsons import io.airbyte.protocol.models.v0.StreamDescriptor +import io.mockk.Runs import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import io.mockk.just import java.util.concurrent.atomic.AtomicReference import kotlinx.coroutines.runBlocking import org.junit.Assert.assertThrows @@ -29,7 +31,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -typealias CreatorPosition = Long +data class CreatorPosition(val value: Long) : PartiallyOrdered { + override fun compareTo(other: CreatorPosition): Int? = value.compareTo(other.value) +} /** These unit tests play out the scenarios possible in [CdcPartitionsCreator.run]. */ @ExtendWith(MockKExtension::class) @@ -46,7 +50,8 @@ class CdcPartitionsCreatorTest { val stream = Stream( id = StreamIdentifier.from(StreamDescriptor().withName("test")), - schema = setOf(Field("test", StringFieldType), TestMetaFieldDecorator.GlobalCursor), + schema = + setOf(EmittedField("test", StringFieldType), TestMetaFieldDecorator.GlobalCursor), configuredSyncMode = ConfiguredSyncMode.INCREMENTAL, configuredPrimaryKey = null, configuredCursor = TestMetaFieldDecorator.GlobalCursor, @@ -79,8 +84,9 @@ class CdcPartitionsCreatorTest { every { globalFeedBootstrap.feed } returns global every { globalFeedBootstrap.feeds } returns listOf(global, stream) every { globalFeedBootstrap.streamRecordConsumers() } returns emptyMap() - every { creatorOps.position(syntheticOffset) } returns 123L - every { creatorOps.position(incumbentOffset) } returns 123L + every { creatorOps.position(syntheticOffset) } returns CreatorPosition(123L) + every { creatorOps.position(incumbentOffset) } returns CreatorPosition(123L) + every { creatorOps.runStartup(any()) } just Runs every { creatorOps.generateColdStartOffset() } returns syntheticOffset every { creatorOps.generateColdStartProperties(streams) } returns emptyMap() every { creatorOps.generateWarmStartProperties(streams) } returns emptyMap() @@ -91,13 +97,13 @@ class CdcPartitionsCreatorTest { every { globalFeedBootstrap.currentState } returns null every { globalFeedBootstrap.currentState(stream) } returns null val syntheticOffset = DebeziumOffset(mapOf(Jsons.nullNode() to Jsons.nullNode())) - every { creatorOps.position(syntheticOffset) } returns 123L + every { creatorOps.position(syntheticOffset) } returns CreatorPosition(123L) every { creatorOps.generateColdStartOffset() } returns syntheticOffset upperBoundReference.set(null) val readers: List = runBlocking { creator.run() } Assertions.assertEquals(1, readers.size) val reader = readers.first() as CdcPartitionReader<*> - Assertions.assertEquals(123L, reader.upperBound) + Assertions.assertEquals(CreatorPosition(123L), reader.upperBound) Assertions.assertEquals(syntheticOffset, reader.startingOffset) } @@ -108,11 +114,11 @@ class CdcPartitionsCreatorTest { val deserializedState = ValidDebeziumWarmStartState(offset = incumbentOffset, schemaHistory = null) every { creatorOps.deserializeState(Jsons.objectNode()) } returns deserializedState - upperBoundReference.set(1_000_000L) + upperBoundReference.set(CreatorPosition(1_000_000L)) val readers: List = runBlocking { creator.run() } Assertions.assertEquals(1, readers.size) val reader = readers.first() as CdcPartitionReader<*> - Assertions.assertEquals(1_000_000L, reader.upperBound) + Assertions.assertEquals(CreatorPosition(1_000_000L), reader.upperBound) Assertions.assertEquals(deserializedState.offset, reader.startingOffset) } @@ -123,7 +129,7 @@ class CdcPartitionsCreatorTest { val deserializedState = ValidDebeziumWarmStartState(offset = incumbentOffset, schemaHistory = null) every { creatorOps.deserializeState(Jsons.objectNode()) } returns deserializedState - upperBoundReference.set(1L) + upperBoundReference.set(CreatorPosition(1L)) val readers: List = runBlocking { creator.run() } Assertions.assertEquals(emptyList(), readers) } diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilderTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilderTest.kt index 34ae5158ed68..d6bdd5280323 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilderTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/test/kotlin/io/airbyte/cdk/read/cdc/DebeziumPropertiesBuilderTest.kt @@ -5,7 +5,7 @@ package io.airbyte.cdk.read.cdc import io.airbyte.cdk.StreamIdentifier -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.IntFieldType import io.airbyte.cdk.discover.OffsetDateTimeFieldType import io.airbyte.cdk.discover.StringFieldType @@ -41,7 +41,7 @@ class DebeziumPropertiesBuilderTest { @Test fun testWithStreams() { - fun stream(namespace: String, name: String, vararg fields: Field): Stream = + fun stream(namespace: String, name: String, vararg fields: EmittedField): Stream = Stream( id = StreamIdentifier.from( @@ -54,13 +54,18 @@ class DebeziumPropertiesBuilderTest { ) val streams: List = listOf( - stream("schema1", "table1", Field("k", IntFieldType), Field("v", StringFieldType)), + stream( + "schema1", + "table1", + EmittedField("k", IntFieldType), + EmittedField("v", StringFieldType) + ), stream( "schema2", "table2", - Field("id", IntFieldType), - Field("ts", OffsetDateTimeFieldType), - Field("msg", StringFieldType) + EmittedField("id", IntFieldType), + EmittedField("ts", OffsetDateTimeFieldType), + EmittedField("msg", StringFieldType) ), ) val actual: Map = diff --git a/airbyte-cdk/bulk/toolkits/extract-cdc/src/testFixtures/kotlin/io/airbyte/cdk/read/cdc/AbstractCdcPartitionReaderTest.kt b/airbyte-cdk/bulk/toolkits/extract-cdc/src/testFixtures/kotlin/io/airbyte/cdk/read/cdc/AbstractCdcPartitionReaderTest.kt index 6f410e1fde5b..6e517e5867a5 100644 --- a/airbyte-cdk/bulk/toolkits/extract-cdc/src/testFixtures/kotlin/io/airbyte/cdk/read/cdc/AbstractCdcPartitionReaderTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-cdc/src/testFixtures/kotlin/io/airbyte/cdk/read/cdc/AbstractCdcPartitionReaderTest.kt @@ -14,7 +14,7 @@ import io.airbyte.cdk.StreamIdentifier import io.airbyte.cdk.command.OpaqueStateValue import io.airbyte.cdk.data.IntCodec import io.airbyte.cdk.data.TextCodec -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.IntFieldType import io.airbyte.cdk.discover.TestMetaFieldDecorator import io.airbyte.cdk.output.BufferingOutputConsumer @@ -60,7 +60,7 @@ import org.junit.jupiter.api.extension.ExtendWith */ @SuppressFBWarnings(value = ["NP_NONNULL_RETURN_VIOLATION"], justification = "Micronaut DI") @ExtendWith(MockKExtension::class) -abstract class AbstractCdcPartitionReaderTest, C : AutoCloseable>( +abstract class AbstractCdcPartitionReaderTest, C : AutoCloseable>( namespace: String?, val heartbeat: Duration = Duration.ofMillis(100), val timeout: Duration = Duration.ofSeconds(10), @@ -73,7 +73,7 @@ abstract class AbstractCdcPartitionReaderTest, C : AutoCloseab val stream = Stream( id = StreamIdentifier.from(StreamDescriptor().withName("tbl").withNamespace(namespace)), - schema = setOf(Field("v", IntFieldType), TestMetaFieldDecorator.GlobalCursor), + schema = setOf(EmittedField("v", IntFieldType), TestMetaFieldDecorator.GlobalCursor), configuredSyncMode = ConfiguredSyncMode.INCREMENTAL, configuredPrimaryKey = null, configuredCursor = TestMetaFieldDecorator.GlobalCursor, @@ -115,7 +115,7 @@ abstract class AbstractCdcPartitionReaderTest, C : AutoCloseab override val stream: Stream = this@AbstractCdcPartitionReaderTest.stream override fun accept( recordData: NativeRecordPayload, - changes: Map? + changes: Map? ) { outputConsumer.accept( AirbyteRecordMessage() @@ -290,7 +290,7 @@ abstract class AbstractCdcPartitionReaderTest, C : AutoCloseab data class Update(override val id: Int, val v: Int) : Record data class Delete(override val id: Int, val ignore: Boolean = false) : Record - abstract inner class AbstractCdcPartitionsCreatorDbzOps> : + abstract inner class AbstractCdcPartitionsCreatorDbzOps> : CdcPartitionsCreatorDebeziumOperations { override fun deserializeState(opaqueStateValue: OpaqueStateValue): DebeziumWarmStartState { @@ -318,7 +318,7 @@ abstract class AbstractCdcPartitionReaderTest, C : AutoCloseab } } - abstract inner class AbstractCdcPartitionReaderDbzOps> : + abstract inner class AbstractCdcPartitionReaderDbzOps> : CdcPartitionReaderDebeziumOperations { override fun deserializeRecord( diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactory.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactory.kt index bda87e982b9d..afa0428d6ec6 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactory.kt @@ -54,10 +54,11 @@ interface JdbcAirbyteStreamFactory : AirbyteStreamFactory, MetaFieldDecorator { if (discoveredStream.primaryKeyColumnIDs.isEmpty()) { return false } - val allColumnsByID: Map = discoveredStream.columns.associateBy { it.id } + val allColumnsByID: Map = + discoveredStream.columns.associateBy { it.id } return discoveredStream.primaryKeyColumnIDs.all { idComponents: List -> val id: String = idComponents.joinToString(separator = ".") - val field: Field? = allColumnsByID[id] + val field: EmittedField? = allColumnsByID[id] field != null && isPossiblePrimaryKeyElement(field) } } @@ -76,7 +77,7 @@ interface JdbcAirbyteStreamFactory : AirbyteStreamFactory, MetaFieldDecorator { * objects. For instance if the [Field.type] does not map to a [LosslessFieldType] then the * field can't reliably round-trip checkpoint values during a resumable initial sync. */ - fun isPossiblePrimaryKeyElement(field: Field): Boolean = + fun isPossiblePrimaryKeyElement(field: EmittedField): Boolean = when (field.type) { !is LosslessFieldType -> false CharacterStreamFieldType, @@ -94,6 +95,6 @@ interface JdbcAirbyteStreamFactory : AirbyteStreamFactory, MetaFieldDecorator { * to round-trip the column values, we need to be able to query the max value from the source at * the start of the sync. */ - fun isPossibleCursor(field: Field): Boolean = + fun isPossibleCursor(field: EmittedField): Boolean = isPossiblePrimaryKeyElement(field) && field.type !is BooleanFieldType } diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcMetadataQuerier.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcMetadataQuerier.kt index d87d9de44550..90cf769b63b8 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcMetadataQuerier.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/discover/JdbcMetadataQuerier.kt @@ -34,6 +34,7 @@ class JdbcMetadataQuerier( val checkQueries: JdbcCheckQueries, jdbcConnectionFactory: JdbcConnectionFactory, ) : MetadataQuerier { + val conn: Connection = jdbcConnectionFactory.get() private val log = KotlinLogging.logger {} @@ -126,7 +127,6 @@ class JdbcMetadataQuerier( log.info { "Querying column names for catalog discovery." } try { val dbmd: DatabaseMetaData = conn.metaData - fun addColumnsFromQuery( catalog: String?, schema: String?, @@ -160,11 +160,15 @@ class JdbcMetadataQuerier( ?.value if (patterns != null && patterns.isNotEmpty()) { for (pattern in patterns) { - addColumnsFromQuery(catalog, schema, pattern, isPseudoColumn = true) + if (constants.includePseudoColumns) { + addColumnsFromQuery(catalog, schema, pattern, isPseudoColumn = true) + } addColumnsFromQuery(catalog, schema, pattern, isPseudoColumn = false) } } else { - addColumnsFromQuery(catalog, schema, null, isPseudoColumn = true) + if (constants.includePseudoColumns) { + addColumnsFromQuery(catalog, schema, null, isPseudoColumn = true) + } addColumnsFromQuery(catalog, schema, null, isPseudoColumn = false) } } @@ -247,9 +251,9 @@ class JdbcMetadataQuerier( override fun fields( streamID: StreamIdentifier, - ): List { + ): List { val table: TableName = findTableName(streamID) ?: return listOf() - return columnMetadata(table).map { Field(it.label, fieldTypeMapper.toFieldType(it)) } + return columnMetadata(table).map { EmittedField(it.label, fieldTypeMapper.toFieldType(it)) } } fun columnMetadata(table: TableName): List { @@ -279,7 +283,7 @@ class JdbcMetadataQuerier( ): String { val querySpec = SelectQuerySpec( - SelectColumns(columnIDs.map { Field(it, NullFieldType) }), + SelectColumns(columnIDs.map { EmittedField(it, NullFieldType) }), From(table.name, table.namespace()), limit = Limit(0), ) @@ -400,7 +404,7 @@ class JdbcMetadataQuerier( selectQueryGenerator, fieldTypeMapper, checkQueries, - jdbcConnectionFactory, + jdbcConnectionFactory ) } } diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/DefaultJdbcConstants.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/DefaultJdbcConstants.kt index e783338937b5..6782b78e1e99 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/DefaultJdbcConstants.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/DefaultJdbcConstants.kt @@ -31,6 +31,8 @@ data class DefaultJdbcConstants( /** Whether the namespace field denotes a JDBC schema or a JDBC catalog. */ val namespaceKind: NamespaceKind = NamespaceKind.SCHEMA, val maxSequentialQueryLimit: Long? = MAX_SEQUENTIAL_QUERY_LIMIT_NULL, + /** Whether to fetch pseudo-columns when querying column metadata. */ + val includePseudoColumns: Boolean = true, ) { enum class NamespaceKind { diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcAccessor.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcAccessor.kt index e5a084891194..534699653aa6 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcAccessor.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcAccessor.kt @@ -15,6 +15,7 @@ import java.sql.Timestamp import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime +import java.time.OffsetDateTime /** Combination of [JdbcGetter] and [JdbcSetter]. */ interface JdbcAccessor : JdbcGetter, JdbcSetter @@ -306,7 +307,15 @@ data object DateAccessor : JdbcAccessor { override fun get( rs: ResultSet, colIdx: Int, - ): LocalDate? = rs.getDate(colIdx)?.takeUnless { rs.wasNull() }?.toLocalDate() + ): LocalDate? { + val dateStr = rs.getString(colIdx) + return when { + rs.wasNull() -> null + dateStr == "infinity" || dateStr == "-infinity" -> + throw IllegalStateException("Date '$dateStr' is not supported") + else -> rs.getDate(colIdx).toLocalDate() + } + } override fun set( stmt: PreparedStatement, @@ -336,7 +345,15 @@ data object TimestampAccessor : JdbcAccessor { override fun get( rs: ResultSet, colIdx: Int, - ): LocalDateTime? = rs.getTimestamp(colIdx)?.takeUnless { rs.wasNull() }?.toLocalDateTime() + ): LocalDateTime? { + val timestampStr = rs.getString(colIdx) + return when { + rs.wasNull() -> null + timestampStr == "infinity" || timestampStr == "-infinity" -> + throw IllegalStateException("Timestamp '$timestampStr' is not supported") + else -> rs.getTimestamp(colIdx).toLocalDateTime() + } + } override fun set( stmt: PreparedStatement, @@ -347,6 +364,21 @@ data object TimestampAccessor : JdbcAccessor { } } +data object TimestampTzGetter : JdbcGetter { + override fun get( + rs: ResultSet, + colIdx: Int, + ): OffsetDateTime? { + val timestampStr = rs.getString(colIdx) + return when { + rs.wasNull() -> null + timestampStr == "infinity" || timestampStr == "-infinity" -> + throw IllegalStateException("Timestamp '$timestampStr' is not supported") + else -> rs.getObject(colIdx, OffsetDateTime::class.java) + } + } +} + data object XmlAccessor : JdbcAccessor { override fun get( rs: ResultSet, @@ -416,6 +448,9 @@ data class AnySetter( data class ArrayGetter( val elementGetter: JdbcGetter, ) : JdbcGetter> { + // TODO: Allow partial success. + // Catch exceptions for individual values and replace with null. + // This requires returning both a list of values and a list of exceptions. override fun get( rs: ResultSet, colIdx: Int, diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcConnectionFactory.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcConnectionFactory.kt index 7ab36e78c8eb..a06f6d3a80de 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcConnectionFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcConnectionFactory.kt @@ -23,7 +23,7 @@ private val log = KotlinLogging.logger {} * SSH tunnel session is shared by many connections. */ @Singleton -class JdbcConnectionFactory( +open class JdbcConnectionFactory( val config: JdbcSourceConfiguration, ) : Supplier { diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcFieldTypes.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcFieldTypes.kt index e6bba19b8da9..9bcafa2106f7 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcFieldTypes.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/jdbc/JdbcFieldTypes.kt @@ -273,7 +273,7 @@ data object OffsetTimeFieldType : data object OffsetDateTimeFieldType : LosslessJdbcFieldType( LeafAirbyteSchemaType.TIMESTAMP_WITH_TIMEZONE, - ObjectGetter(OffsetDateTime::class.java), + TimestampTzGetter, OffsetDateTimeCodec, OffsetDateTimeCodec, AnyAccessor, diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartition.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartition.kt index 71c37a9b5cb8..f2b5b1ff0895 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartition.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartition.kt @@ -5,9 +5,9 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ObjectNode import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField +import io.airbyte.cdk.output.sockets.toJson import io.airbyte.cdk.util.Jsons /** Base class for default implementations of [JdbcPartition]. */ @@ -57,7 +57,7 @@ class DefaultJdbcUnsplittableSnapshotPartition( class DefaultJdbcUnsplittableSnapshotWithCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - val cursor: Field, + val cursor: EmittedField, ) : DefaultJdbcUnsplittablePartition(selectQueryGenerator, streamState), JdbcCursorPartition { @@ -79,7 +79,7 @@ class DefaultJdbcUnsplittableSnapshotWithCursorPartition( sealed class DefaultJdbcSplittablePartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - val checkpointColumns: List, + val checkpointColumns: List, ) : DefaultJdbcPartition(selectQueryGenerator, streamState), JdbcSplittablePartition { @@ -118,10 +118,10 @@ sealed class DefaultJdbcSplittablePartition( val where: WhereNode get() { - val zippedLowerBound: List> = + val zippedLowerBound: List> = lowerBound?.let { checkpointColumns.zip(it) } ?: listOf() val lowerBoundDisj: List = - zippedLowerBound.mapIndexed { idx: Int, (gtCol: Field, gtValue: JsonNode) -> + zippedLowerBound.mapIndexed { idx: Int, (gtCol: EmittedField, gtValue: JsonNode) -> val lastLeaf: WhereClauseLeafNode = if (isLowerBoundIncluded && idx == checkpointColumns.size - 1) { GreaterOrEqual(gtCol, gtValue) @@ -129,15 +129,16 @@ sealed class DefaultJdbcSplittablePartition( Greater(gtCol, gtValue) } And( - zippedLowerBound.take(idx).map { (eqCol: Field, eqValue: JsonNode) -> + zippedLowerBound.take(idx).map { (eqCol: EmittedField, eqValue: JsonNode) -> Equal(eqCol, eqValue) } + listOf(lastLeaf), ) } - val zippedUpperBound: List> = + val zippedUpperBound: List> = upperBound?.let { checkpointColumns.zip(it) } ?: listOf() val upperBoundDisj: List = - zippedUpperBound.mapIndexed { idx: Int, (leqCol: Field, leqValue: JsonNode) -> + zippedUpperBound.mapIndexed { idx: Int, (leqCol: EmittedField, leqValue: JsonNode) + -> val lastLeaf: WhereClauseLeafNode = if (idx < zippedUpperBound.size - 1) { Lesser(leqCol, leqValue) @@ -145,7 +146,7 @@ sealed class DefaultJdbcSplittablePartition( LesserOrEqual(leqCol, leqValue) } And( - zippedUpperBound.take(idx).map { (eqCol: Field, eqValue: JsonNode) -> + zippedUpperBound.take(idx).map { (eqCol: EmittedField, eqValue: JsonNode) -> Equal(eqCol, eqValue) } + listOf(lastLeaf), ) @@ -177,7 +178,7 @@ sealed class DefaultJdbcSplittablePartition( class DefaultJdbcSplittableSnapshotPartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - primaryKey: List, + primaryKey: List, override val lowerBound: List?, override val upperBound: List?, ) : DefaultJdbcSplittablePartition(selectQueryGenerator, streamState, primaryKey) { @@ -193,10 +194,11 @@ class DefaultJdbcSplittableSnapshotPartition( ) } - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.snapshotCheckpoint( primaryKey = checkpointColumns, - primaryKeyCheckpoint = checkpointColumns.map { lastRecord[it.id] ?: Jsons.nullNode() }, + primaryKeyCheckpoint = + checkpointColumns.map { lastRecord.data.toJson()[it.id] ?: Jsons.nullNode() }, ) } @@ -206,8 +208,8 @@ class DefaultJdbcSplittableSnapshotPartition( sealed class DefaultJdbcCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - checkpointColumns: List, - val cursor: Field, + checkpointColumns: List, + val cursor: EmittedField, private val explicitCursorUpperBound: JsonNode?, ) : DefaultJdbcSplittablePartition(selectQueryGenerator, streamState, checkpointColumns), @@ -229,10 +231,10 @@ sealed class DefaultJdbcCursorPartition( class DefaultJdbcSplittableSnapshotWithCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - primaryKey: List, + primaryKey: List, override val lowerBound: List?, override val upperBound: List?, - cursor: Field, + cursor: EmittedField, cursorUpperBound: JsonNode?, ) : DefaultJdbcCursorPartition( @@ -260,10 +262,11 @@ class DefaultJdbcSplittableSnapshotWithCursorPartition( ) } - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.snapshotWithCursorCheckpoint( primaryKey = checkpointColumns, - primaryKeyCheckpoint = checkpointColumns.map { lastRecord[it.id] ?: Jsons.nullNode() }, + primaryKeyCheckpoint = + checkpointColumns.map { lastRecord.data.toJson()[it.id] ?: Jsons.nullNode() }, cursor, cursorUpperBound, ) @@ -276,7 +279,7 @@ class DefaultJdbcSplittableSnapshotWithCursorPartition( class DefaultJdbcCursorIncrementalPartition( selectQueryGenerator: SelectQueryGenerator, streamState: DefaultJdbcStreamState, - cursor: Field, + cursor: EmittedField, val cursorLowerBound: JsonNode, override val isLowerBoundIncluded: Boolean, cursorUpperBound: JsonNode?, @@ -300,9 +303,9 @@ class DefaultJdbcCursorIncrementalPartition( cursorCheckpoint = cursorUpperBound, ) - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.cursorIncrementalCheckpoint( cursor, - cursorCheckpoint = lastRecord[cursor.id] ?: Jsons.nullNode(), + cursorCheckpoint = lastRecord.data.toJson()[cursor.id] ?: Jsons.nullNode(), ) } diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactory.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactory.kt index 8bcd6e20c54e..6149801b4893 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactory.kt @@ -9,8 +9,8 @@ import io.airbyte.cdk.ConfigErrorException import io.airbyte.cdk.StreamIdentifier import io.airbyte.cdk.command.JdbcSourceConfiguration import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.output.CatalogValidationFailureHandler import io.airbyte.cdk.output.InvalidCursor import io.airbyte.cdk.output.InvalidPrimaryKey @@ -56,14 +56,14 @@ class DefaultJdbcPartitionFactory( } val sv: DefaultJdbcStreamStateValue = Jsons.treeToValue(opaqueStateValue, DefaultJdbcStreamStateValue::class.java) - val pkMap: Map = + val pkMap: Map = sv.pkMap(stream) ?: run { handler.accept(ResetStream(stream.id)) streamState.reset() return coldStart(streamState) } - val cursorPair: Pair? = + val cursorPair: Pair? = if (sv.cursors.isEmpty()) { null } else { @@ -97,7 +97,7 @@ class DefaultJdbcPartitionFactory( ) } } else { - val (cursor: Field, cursorCheckpoint: JsonNode) = cursorPair + val (cursor: EmittedField, cursorCheckpoint: JsonNode) = cursorPair if (!isCursorBasedIncremental) { handler.accept(ResetStream(stream.id)) streamState.reset() @@ -130,11 +130,11 @@ class DefaultJdbcPartitionFactory( } } - private fun DefaultJdbcStreamStateValue.pkMap(stream: Stream): Map? { + private fun DefaultJdbcStreamStateValue.pkMap(stream: Stream): Map? { if (primaryKey.isEmpty()) { return mapOf() } - val fields: List = stream.configuredPrimaryKey ?: listOf() + val fields: List = stream.configuredPrimaryKey ?: listOf() if (primaryKey.keys != fields.map { it.id }.toSet()) { handler.accept( InvalidPrimaryKey(stream.id, primaryKey.keys.toList()), @@ -144,7 +144,9 @@ class DefaultJdbcPartitionFactory( return fields.associateWith { primaryKey[it.id]!! } } - private fun DefaultJdbcStreamStateValue.cursorPair(stream: Stream): Pair? { + private fun DefaultJdbcStreamStateValue.cursorPair( + stream: Stream + ): Pair? { if (cursors.size > 1) { handler.accept( InvalidCursor(stream.id, cursors.keys.toString()), @@ -152,8 +154,8 @@ class DefaultJdbcPartitionFactory( return null } val cursorLabel: String = cursors.keys.first() - val cursor: FieldOrMetaField? = stream.schema.find { it.id == cursorLabel } - if (cursor !is Field) { + val cursor: DataOrMetaField? = stream.schema.find { it.id == cursorLabel } + if (cursor !is EmittedField) { handler.accept( InvalidCursor(stream.id, cursorLabel), ) @@ -170,7 +172,7 @@ class DefaultJdbcPartitionFactory( private fun coldStart(streamState: DefaultJdbcStreamState): DefaultJdbcPartition { val stream: Stream = streamState.stream - val pkChosenFromCatalog: List = stream.configuredPrimaryKey ?: listOf() + val pkChosenFromCatalog: List = stream.configuredPrimaryKey ?: listOf() if (stream.configuredSyncMode == ConfiguredSyncMode.FULL_REFRESH || configuration.global) { if (pkChosenFromCatalog.isEmpty()) { return DefaultJdbcUnsplittableSnapshotPartition( @@ -186,8 +188,8 @@ class DefaultJdbcPartitionFactory( upperBound = null, ) } - val cursorChosenFromCatalog: Field = - stream.configuredCursor as? Field ?: throw ConfigErrorException("no cursor") + val cursorChosenFromCatalog: EmittedField = + stream.configuredCursor as? EmittedField ?: throw ConfigErrorException("no cursor") if (pkChosenFromCatalog.isEmpty()) { return DefaultJdbcUnsplittableSnapshotWithCursorPartition( selectQueryGenerator, diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcStreamStateValue.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcStreamStateValue.kt index cf814764b853..280db3be2abf 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcStreamStateValue.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/DefaultJdbcStreamStateValue.kt @@ -7,7 +7,7 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.JsonNode import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.util.Jsons /** @@ -26,7 +26,7 @@ data class DefaultJdbcStreamStateValue( /** Value representing the progress of a ongoing snapshot not involving cursor columns. */ fun snapshotCheckpoint( - primaryKey: List, + primaryKey: List, primaryKeyCheckpoint: List, ): OpaqueStateValue = when (primaryKeyCheckpoint.first().isNull) { @@ -41,9 +41,9 @@ data class DefaultJdbcStreamStateValue( /** Value representing the progress of an ongoing snapshot involving cursor columns. */ fun snapshotWithCursorCheckpoint( - primaryKey: List, + primaryKey: List, primaryKeyCheckpoint: List, - cursor: Field, + cursor: EmittedField, cursorUpperBound: JsonNode, ): OpaqueStateValue = when (primaryKeyCheckpoint.first().isNull) { @@ -59,7 +59,7 @@ data class DefaultJdbcStreamStateValue( /** Value representing the progress of an ongoing incremental cursor read. */ fun cursorIncrementalCheckpoint( - cursor: Field, + cursor: EmittedField, cursorCheckpoint: JsonNode, ): OpaqueStateValue = when (cursorCheckpoint.isNull) { diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartition.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartition.kt index fe76774bf4ff..979a9cfe2436 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartition.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartition.kt @@ -4,7 +4,6 @@ package io.airbyte.cdk.read -import com.fasterxml.jackson.databind.node.ObjectNode import io.airbyte.cdk.command.OpaqueStateValue /** @@ -45,7 +44,7 @@ interface JdbcSplittablePartition> : JdbcPartition { fun resumableQuery(limit: Long): SelectQuery /** State value to emit when the partition is read up to (and including) [lastRecord]. */ - fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue + fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue } /** A [JdbcPartition] which allows cursor-based incremental reads. */ diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionReader.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionReader.kt index a093b4497f77..b6a5904898d4 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionReader.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionReader.kt @@ -1,16 +1,15 @@ /* Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ package io.airbyte.cdk.read -import com.fasterxml.jackson.databind.node.ObjectNode import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import io.airbyte.cdk.TransientErrorException import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.output.DataChannelMedium.* +import io.airbyte.cdk.discover.EmittedField +import io.airbyte.cdk.jdbc.JdbcConnectionFactory +import io.airbyte.cdk.output.DataChannelMedium.SOCKET +import io.airbyte.cdk.output.DataChannelMedium.STDIO import io.airbyte.cdk.output.OutputMessageRouter import io.airbyte.cdk.output.sockets.NativeRecordPayload -import io.airbyte.cdk.output.sockets.toJson -import io.airbyte.cdk.util.Jsons import io.airbyte.protocol.models.v0.AirbyteStateMessage import io.airbyte.protocol.models.v0.AirbyteStreamStatusTraceMessage import java.time.Duration @@ -28,7 +27,7 @@ sealed class JdbcPartitionReader

>( ) : PartitionReader { lateinit var outputMessageRouter: OutputMessageRouter - lateinit var outputRoute: ((NativeRecordPayload, Map?) -> Unit) + lateinit var outputRoute: ((NativeRecordPayload, Map?) -> Unit) protected var partitionId: String = generatePartitionId(4) val streamState: JdbcStreamState<*> = partition.streamState @@ -143,6 +142,7 @@ class JdbcNonResumablePartitionReader

>( numRecords.incrementAndGet() } } + streamState.validatePartition(partition, JdbcConnectionFactory(sharedState.configuration)) runComplete.set(true) } @@ -172,7 +172,7 @@ class JdbcResumablePartitionReader

>( val incumbentLimit = AtomicLong() val numRecords = AtomicLong() - val lastRecord = AtomicReference(null) + val lastRecord = AtomicReference(null) val runComplete = AtomicBoolean(false) override suspend fun run() { @@ -196,7 +196,7 @@ class JdbcResumablePartitionReader

>( .use { result: SelectQuerier.Result -> for (row in result) { out(row) - lastRecord.set(row.data.toJson(Jsons.objectNode())) + lastRecord.set(row) // Check activity periodically to handle timeout. if (numRecords.incrementAndGet() % fetchSize == 0L) { coroutineContext.ensureActive() @@ -233,7 +233,7 @@ class JdbcResumablePartitionReader

>( streamState.updateLimitState { it.down } } } - val checkpointState: OpaqueStateValue = partition.incompleteState(lastRecord.get()!!) + val checkpointState: OpaqueStateValue = partition.incompleteState(lastRecord.get()) return PartitionReadCheckpoint( checkpointState, numRecords.get(), diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreator.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreator.kt index 7c8b53e78725..e062b6d5f962 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreator.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreator.kt @@ -100,8 +100,8 @@ abstract class JdbcPartitionsCreator< } /** Collects a sample of rows in the unsplit partition. */ - fun collectSample( - recordMapper: (ObjectNode) -> T, + open fun collectSample( + recordMapper: (SelectQuerier.ResultRow) -> T, ): Sample { val values = mutableListOf() var previousWeight = 0L @@ -116,7 +116,7 @@ abstract class JdbcPartitionsCreator< val samplingQuery: SelectQuery = partition.samplingQuery(sampleRateInvPow2) selectQuerier.executeQuery(samplingQuery).use { for (row in it) { - values.add(recordMapper(row.data.toJson())) + values.add(recordMapper(row)) } } if (values.size < sharedState.maxSampleSize) { @@ -162,8 +162,9 @@ class JdbcSequentialPartitionsCreator< } if (streamState.fetchSize == null) { if (sharedState.withSampling) { - val rowByteSizeSample: Sample = - collectSample(sharedState.rowByteSizeEstimator()::apply) + val rowByteSizeSample: Sample = collectSample { + sharedState.rowByteSizeEstimator().apply(it.data.toJson()) + } val expectedTableByteSize: Long = rowByteSizeSample.sampledValues.sum() * rowByteSizeSample.valueWeight log.info { "Table memory size estimated at ${expectedTableByteSize shr 20} MiB." } @@ -187,13 +188,13 @@ class JdbcSequentialPartitionsCreator< return listOf(JdbcNonResumablePartitionReader(partition)) } // Happy path. - log.info { "Table will be read by sequential partition reader(s)." } + log.info { "Table will be read by sequential partition reader." } return listOf(JdbcResumablePartitionReader(partition)) } } /** Concurrent JDBC implementation of [PartitionsCreator]. */ -class JdbcConcurrentPartitionsCreator< +open class JdbcConcurrentPartitionsCreator< A : JdbcSharedState, S : JdbcStreamState, P : JdbcPartition, @@ -223,12 +224,14 @@ class JdbcConcurrentPartitionsCreator< return listOf(JdbcNonResumablePartitionReader(partition)) } // Sample the table for partition split boundaries and for record byte sizes. - val sample: Sample> = collectSample { record: ObjectNode -> - val boundary: OpaqueStateValue? = - (partition as? JdbcSplittablePartition<*>)?.incompleteState(record) - val rowByteSize: Long = sharedState.rowByteSizeEstimator().apply(record) - boundary to rowByteSize - } + val sample: Sample> = + collectSample { record: SelectQuerier.ResultRow -> + val boundary: OpaqueStateValue? = + (partition as? JdbcSplittablePartition<*>)?.incompleteState(record) + val rowByteSize: Long = + sharedState.rowByteSizeEstimator().apply(record.data.toJson()) + boundary to rowByteSize + } if (sample.kind == Sample.Kind.EMPTY) { log.info { "Sampling query found that the table was empty." } return listOf(CheckpointOnlyPartitionReader()) diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcStreamState.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcStreamState.kt index ebdcc72d7834..4a4c4dddf5a4 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcStreamState.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/JdbcStreamState.kt @@ -5,6 +5,7 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.JsonNode +import io.airbyte.cdk.jdbc.JdbcConnectionFactory /** * Encapsulates database-specific transient state for a particular [stream]. @@ -43,4 +44,9 @@ interface JdbcStreamState { /** Resets the transient state to its initial setting. */ fun reset() + + fun validatePartition( + partition: JdbcPartition<*>, + jdbcConnectionFactory: JdbcConnectionFactory + ) {} } diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerier.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerier.kt index a5bb60a5b60c..fe5a8e9fee7b 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerier.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerier.kt @@ -4,7 +4,8 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.node.ObjectNode import io.airbyte.cdk.data.JsonEncoder import io.airbyte.cdk.data.NullCodec -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField +import io.airbyte.cdk.discover.NonEmittedField import io.airbyte.cdk.jdbc.JdbcConnectionFactory import io.airbyte.cdk.jdbc.JdbcFieldType import io.airbyte.cdk.output.sockets.FieldValueEncoder @@ -41,7 +42,8 @@ interface SelectQuerier { interface ResultRow { val data: NativeRecordPayload - val changes: Map + val changes: Map + val nonEmittedData: NativeRecordPayload } } @@ -57,7 +59,8 @@ class JdbcSelectQuerier( data class ResultRow( override val data: NativeRecordPayload = mutableMapOf(), - override var changes: MutableMap = mutableMapOf(), + override var changes: MutableMap = mutableMapOf(), + override var nonEmittedData: NativeRecordPayload = mutableMapOf(), ) : SelectQuerier.ResultRow class Result( @@ -137,25 +140,36 @@ class JdbcSelectQuerier( log.debug { "Getting value #$colIdx for $column." } val jdbcFieldType: JdbcFieldType<*> = column.type as JdbcFieldType<*> try { - @Suppress("UNCHECKED_CAST") - resultRow.data[column.id] = - FieldValueEncoder( - jdbcFieldType.jdbcGetter.get(rs!!, colIdx), - jdbcFieldType.jsonEncoder as JsonEncoder, - ) + if (column is NonEmittedField) { + @Suppress("UNCHECKED_CAST") // TODO: See if we can avoid an unchecked cast + resultRow.nonEmittedData[column.id] = + FieldValueEncoder( + jdbcFieldType.jdbcGetter.get(rs!!, colIdx), + jdbcFieldType.jsonEncoder as JsonEncoder, + ) + } else { + @Suppress("UNCHECKED_CAST") + resultRow.data[column.id] = + FieldValueEncoder( + jdbcFieldType.jdbcGetter.get(rs!!, colIdx), + jdbcFieldType.jsonEncoder as JsonEncoder, + ) + } } catch (e: Exception) { - resultRow.data[column.id] = - FieldValueEncoder( - null, - NullCodec // Use NullCodec for null values - ) // Use NullCodec for null values if (!hasLoggedException) { log.warn(e) { "Error deserializing value in column $column." } hasLoggedException = true } else { log.debug(e) { "Error deserializing value in column $column." } } - resultRow.changes.set(column, FieldValueChange.RETRIEVAL_FAILURE_TOTAL) + if (column is EmittedField) { + resultRow.changes[column] = FieldValueChange.RETRIEVAL_FAILURE_TOTAL + resultRow.data[column.id] = + FieldValueEncoder( + null, + NullCodec // Use NullCodec for null values + ) // Use NullCodec for null values + } } colIdx++ } @@ -189,3 +203,51 @@ class JdbcSelectQuerier( } } } + +private val throwNoResultsIllegalState = { + throw IllegalStateException("Query unexpectedly produced no results") +} +private val throwMultipleResultsIllegalState = { + throw IllegalStateException("Query unexpectedly produced multiple results") +} + +/** + * Convenience function for executing a query that is expected to return exactly one row and + * extracting a single value from that row. + * + * @param jdbcConnectionFactory Factory for creating JDBC connections + * @param query SQL query string to execute (should return exactly one row) + * @param bindParameters Optional lambda to bind parameters to the PreparedStatement before + * execution + * @param withResultSet Lambda function to process the ResultSet and extract the desired value + * @param noResultsCase Lambda to execute if the query returns no results + * @param multipleResultsCase Lambda to execute if the query returns multiple results + * @return The value extracted from the single result row using the withRS function + */ +fun querySingleValue( + jdbcConnectionFactory: JdbcConnectionFactory, + query: String, + bindParameters: ((PreparedStatement) -> Unit)? = null, + withResultSet: (ResultSet) -> T, + noResultsCase: () -> Unit = throwNoResultsIllegalState, + multipleResultsCase: () -> Unit = throwMultipleResultsIllegalState, +): T { + jdbcConnectionFactory.get().use { connection -> + connection.prepareStatement(query).use { stmt -> + bindParameters?.invoke(stmt) + stmt.executeQuery().use { rs -> + if (!rs.next()) { + noResultsCase() + // if non-throwing noResultsCase was supplied, we still need to throw + throwNoResultsIllegalState() + } + if (!rs.isLast) { + multipleResultsCase() + // if non-throwing multipleResultsCase was supplied, we still need to throw + throwMultipleResultsIllegalState() + } + return withResultSet(rs) + } + } + } +} diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuery.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuery.kt index 48c3175559e6..833f4b015494 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuery.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuery.kt @@ -2,7 +2,7 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.JsonNode -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.DataField import io.airbyte.cdk.jdbc.LosslessJdbcFieldType /** @@ -11,7 +11,7 @@ import io.airbyte.cdk.jdbc.LosslessJdbcFieldType */ data class SelectQuery( val sql: String, - val columns: List, + val columns: List, val bindings: List, ) { data class Binding( diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerySpec.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerySpec.kt index 1d821605479d..23388479f35b 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerySpec.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/main/kotlin/io/airbyte/cdk/read/SelectQuerySpec.kt @@ -2,7 +2,7 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.JsonNode -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.DataField import java.math.BigDecimal /** @@ -21,19 +21,19 @@ data class SelectQuerySpec( ) sealed interface SelectNode { - val columns: List + val columns: List } data class SelectColumns( - override val columns: List, + override val columns: List, ) : SelectNode { - constructor(vararg columns: Field) : this(columns.toList()) + constructor(vararg columns: DataField) : this(columns.toList()) } data class SelectColumnMaxValue( - val column: Field, + val column: DataField, ) : SelectNode { - override val columns: List + override val columns: List get() = listOf(column) } @@ -65,6 +65,13 @@ data class FromSample( get() = 1L shl sampleRateInvPow2 } +fun FromNode.optimize(): FromNode = + when (this) { + NoFrom -> this + is From -> this + is FromSample -> where?.let { this.copy(where = where.optimize()) } ?: this + } + sealed interface WhereNode data object NoWhere : WhereNode @@ -88,41 +95,41 @@ data class Or( } sealed interface WhereClauseLeafNode : WhereClauseNode { - val column: Field + val column: DataField val bindingValue: JsonNode } data class GreaterOrEqual( - override val column: Field, + override val column: DataField, override val bindingValue: JsonNode, ) : WhereClauseLeafNode data class Greater( - override val column: Field, + override val column: DataField, override val bindingValue: JsonNode, ) : WhereClauseLeafNode data class LesserOrEqual( - override val column: Field, + override val column: DataField, override val bindingValue: JsonNode, ) : WhereClauseLeafNode data class Lesser( - override val column: Field, + override val column: DataField, override val bindingValue: JsonNode, ) : WhereClauseLeafNode data class Equal( - override val column: Field, + override val column: DataField, override val bindingValue: JsonNode, ) : WhereClauseLeafNode sealed interface OrderByNode data class OrderBy( - val columns: List, + val columns: List, ) : OrderByNode { - constructor(vararg columns: Field) : this(columns.toList()) + constructor(vararg columns: DataField) : this(columns.toList()) } data object NoOrderBy : OrderByNode @@ -136,7 +143,7 @@ data class Limit( data object NoLimit : LimitNode fun SelectQuerySpec.optimize(): SelectQuerySpec = - SelectQuerySpec(select.optimize(), from, where.optimize(), orderBy.optimize(), limit) + SelectQuerySpec(select.optimize(), from.optimize(), where.optimize(), orderBy.optimize(), limit) fun SelectNode.optimize(): SelectNode = when (this) { diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactoryTest.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactoryTest.kt index 26ac2e73ca0f..36269a13edd0 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactoryTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/discover/JdbcAirbyteStreamFactoryTest.kt @@ -32,7 +32,12 @@ class JdbcAirbyteStreamFactoryTest { `when`(streamId.namespace).thenReturn("test_namespace") `when`(discoveredStream.id).thenReturn(streamId) `when`(discoveredStream.columns) - .thenReturn(listOf(Field("id", BigIntegerFieldType), Field("name", StringFieldType))) + .thenReturn( + listOf( + EmittedField("id", BigIntegerFieldType), + EmittedField("name", StringFieldType) + ) + ) } @Test @@ -85,7 +90,7 @@ class JdbcAirbyteStreamFactoryTest { `when`(config.isCdc()).thenReturn(false) `when`(discoveredStream.primaryKeyColumnIDs).thenReturn(emptyList()) `when`(discoveredStream.columns) - .thenReturn(listOf(Field("non_cursor_col", BooleanFieldType))) + .thenReturn(listOf(EmittedField("non_cursor_col", BooleanFieldType))) val factory = H2SourceOperations() val stream = factory.create(config, discoveredStream) diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactoryTest.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactoryTest.kt index f6ac9bca6a45..1f667792eb30 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactoryTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/DefaultJdbcPartitionFactoryTest.kt @@ -313,7 +313,7 @@ class DefaultJdbcPartitionFactoryTest { stream.namespace, sampleRateInvPow2 = 8, DefaultJdbcConstants.TABLE_SAMPLE_SIZE, - Where(Or(listOf(And(listOf(Greater(id, IntCodec.encode(22))))))), + Where(Greater(id, IntCodec.encode(22))), ), NoWhere, OrderBy(id), @@ -372,7 +372,7 @@ class DefaultJdbcPartitionFactoryTest { stream.namespace, sampleRateInvPow2 = 8, DefaultJdbcConstants.TABLE_SAMPLE_SIZE, - Where(Or(listOf(And(listOf(Greater(id, IntCodec.encode(22))))))), + Where(Greater(id, IntCodec.encode(22))), ), NoWhere, OrderBy(id) @@ -456,30 +456,8 @@ class DefaultJdbcPartitionFactoryTest { DefaultJdbcConstants.TABLE_SAMPLE_SIZE, Where( And( - Or( - listOf( - And( - listOf( - GreaterOrEqual( - ts, - LocalDateCodec.encode(cursorValue) - ) - ) - ) - ) - ), - Or( - listOf( - And( - listOf( - LesserOrEqual( - ts, - LocalDateCodec.encode(cursorUpperBound) - ) - ) - ) - ) - ) + GreaterOrEqual(ts, LocalDateCodec.encode(cursorValue)), + LesserOrEqual(ts, LocalDateCodec.encode(cursorUpperBound)) ) ), ), diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreatorTest.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreatorTest.kt index d86e7932c161..c20633cdca34 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreatorTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcPartitionsCreatorTest.kt @@ -146,16 +146,7 @@ class JdbcPartitionsCreatorTest { stream().namespace, sampleRateInvPow2 = 16, sampleSize = 4, - where = - Where( - Or( - listOf( - And( - listOf(Greater(id, IntCodec.encode(22))) - ) - ) - ) - ) + where = Where(Greater(id, IntCodec.encode(22))) ), NoWhere, OrderBy(id) @@ -172,16 +163,7 @@ class JdbcPartitionsCreatorTest { stream().namespace, sampleRateInvPow2 = 8, sampleSize = 4, - where = - Where( - Or( - listOf( - And( - listOf(Greater(id, IntCodec.encode(22))) - ) - ) - ) - ) + where = Where(Greater(id, IntCodec.encode(22))) ), NoWhere, OrderBy(id) @@ -279,16 +261,7 @@ class JdbcPartitionsCreatorTest { stream().namespace, sampleRateInvPow2 = 16, sampleSize = 4, - where = - Where( - Or( - listOf( - And( - listOf(Greater(id, IntCodec.encode(22))) - ) - ) - ) - ) + where = Where(Greater(id, IntCodec.encode(22))) ), NoWhere, OrderBy(id) @@ -305,16 +278,7 @@ class JdbcPartitionsCreatorTest { stream().namespace, sampleRateInvPow2 = 8, sampleSize = 4, - where = - Where( - Or( - listOf( - And( - listOf(Greater(id, IntCodec.encode(22))) - ) - ) - ) - ) + where = Where(Greater(id, IntCodec.encode(22))) ), NoWhere, OrderBy(id) @@ -327,7 +291,7 @@ class JdbcPartitionsCreatorTest { ) ), ) - val expectedFetchSize = 674 // adjust this as needed based on inputs + val expectedFetchSize = 681 // adjust this as needed based on inputs val factory = sharedState.factory() val initialPartition = factory.create(stream.bootstrap(opaqueStateValue(pk = 22))).asPartition() diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcSelectQuerierTest.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcSelectQuerierTest.kt index 00ba619c3c2a..04c7724fb2ca 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcSelectQuerierTest.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/JdbcSelectQuerierTest.kt @@ -2,7 +2,7 @@ package io.airbyte.cdk.read import com.fasterxml.jackson.databind.JsonNode -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.h2.H2TestFixture import io.airbyte.cdk.h2source.H2SourceConfiguration import io.airbyte.cdk.h2source.H2SourceConfigurationFactory @@ -32,7 +32,8 @@ class JdbcSelectQuerierTest { h2.execute("INSERT INTO kv (k, v) VALUES (1, 'foo'), (2, 'bar'), (3, NULL);") } - val columns: List = listOf(Field("k", IntFieldType), Field("v", StringFieldType)) + val columns: List = + listOf(EmittedField("k", IntFieldType), EmittedField("v", StringFieldType)) @Test fun testVanilla() { diff --git a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/TestFixtures.kt b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/TestFixtures.kt index c811cbdd7d50..5c4392bcb4e1 100644 --- a/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/TestFixtures.kt +++ b/airbyte-cdk/bulk/toolkits/extract-jdbc/src/test/kotlin/io/airbyte/cdk/read/TestFixtures.kt @@ -11,7 +11,9 @@ import io.airbyte.cdk.StreamIdentifier import io.airbyte.cdk.command.JdbcSourceConfiguration import io.airbyte.cdk.command.OpaqueStateValue import io.airbyte.cdk.command.TableFilter -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.data.IntCodec +import io.airbyte.cdk.data.LocalDateCodec +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.MetaField import io.airbyte.cdk.discover.MetaFieldDecorator import io.airbyte.cdk.jdbc.DefaultJdbcConstants @@ -23,6 +25,7 @@ import io.airbyte.cdk.output.BufferingOutputConsumer import io.airbyte.cdk.output.CatalogValidationFailure import io.airbyte.cdk.output.DataChannelFormat import io.airbyte.cdk.output.DataChannelMedium +import io.airbyte.cdk.output.sockets.FieldValueEncoder import io.airbyte.cdk.output.sockets.NativeRecordPayload import io.airbyte.cdk.ssh.SshConnectionOptions import io.airbyte.cdk.ssh.SshTunnelMethodConfiguration @@ -35,9 +38,9 @@ import org.junit.jupiter.api.Assertions object TestFixtures { - val id = Field("id", IntFieldType) - val ts = Field("ts", LocalDateFieldType) - val msg = Field("msg", StringFieldType) + val id = EmittedField("id", IntFieldType) + val ts = EmittedField("ts", LocalDateFieldType) + val msg = EmittedField("msg", StringFieldType) fun stream( withPK: Boolean = true, @@ -67,14 +70,15 @@ object TestFixtures { fun record( pk: Int? = null, cursor: LocalDate? = null, - ): ObjectNode = - Jsons.readTree( - listOfNotNull( - """ "${id.id}" : $pk """.takeIf { pk != null }, - """ "${ts.id}" : "$cursor" """.takeIf { cursor != null }, - ) - .joinToString(",", "{", "}") - ) as ObjectNode + ): SelectQuerier.ResultRow = + JdbcSelectQuerier.ResultRow( + mutableMapOf( + "id" to FieldValueEncoder(pk, IntCodec), + "ts" to FieldValueEncoder(cursor, LocalDateCodec) + ), + mutableMapOf(), + mutableMapOf(), + ) fun sharedState( global: Boolean = false, @@ -171,7 +175,8 @@ object TestFixtures { override fun next(): SelectQuerier.ResultRow = object : SelectQuerier.ResultRow { override val data: NativeRecordPayload = wrapped.next() - override val changes: Map = emptyMap() + override val changes: Map = emptyMap() + override val nonEmittedData: NativeRecordPayload = mutableMapOf() } override fun close() {} } diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartition.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartition.kt index 78bb696210f7..ee97e48c6196 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartition.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartition.kt @@ -5,9 +5,9 @@ package io.airbyte.cdk import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.ObjectNode import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField +import io.airbyte.cdk.output.sockets.toJson import io.airbyte.cdk.read.And import io.airbyte.cdk.read.DefaultJdbcStreamStateValue import io.airbyte.cdk.read.Equal @@ -25,6 +25,7 @@ import io.airbyte.cdk.read.Or import io.airbyte.cdk.read.OrderBy import io.airbyte.cdk.read.SelectColumnMaxValue import io.airbyte.cdk.read.SelectColumns +import io.airbyte.cdk.read.SelectQuerier import io.airbyte.cdk.read.SelectQuery import io.airbyte.cdk.read.SelectQueryGenerator import io.airbyte.cdk.read.SelectQuerySpec @@ -91,7 +92,7 @@ class TriggerUnsplittableSnapshotPartition( class TriggerUnsplittableSnapshotWithCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, - val cursor: Field, + val cursor: EmittedField, config: TriggerTableConfig, ) : TriggerUnsplittablePartition(selectQueryGenerator, streamState, config), @@ -115,7 +116,7 @@ sealed class TriggerSplittablePartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, config: TriggerTableConfig, - val checkpointColumns: List, + val checkpointColumns: List, val triggerCdcPartitionState: TriggerCdcPartitionState? = null, ) : TriggerPartition(selectQueryGenerator, streamState, config), @@ -181,10 +182,10 @@ sealed class TriggerSplittablePartition( val where: Where get() { - val zippedLowerBound: List> = + val zippedLowerBound: List> = lowerBound?.let { checkpointColumns.zip(it) } ?: listOf() val lowerBoundDisj: List = - zippedLowerBound.mapIndexed { idx: Int, (gtCol: Field, gtValue: JsonNode) -> + zippedLowerBound.mapIndexed { idx: Int, (gtCol: EmittedField, gtValue: JsonNode) -> val lastLeaf: WhereClauseLeafNode = if (isLowerBoundIncluded && idx == checkpointColumns.size - 1) { GreaterOrEqual(gtCol, gtValue) @@ -192,15 +193,16 @@ sealed class TriggerSplittablePartition( Greater(gtCol, gtValue) } And( - zippedLowerBound.take(idx).map { (eqCol: Field, eqValue: JsonNode) -> + zippedLowerBound.take(idx).map { (eqCol: EmittedField, eqValue: JsonNode) -> Equal(eqCol, eqValue) } + listOf(lastLeaf), ) } - val zippedUpperBound: List> = + val zippedUpperBound: List> = upperBound?.let { checkpointColumns.zip(it) } ?: listOf() val upperBoundDisj: List = - zippedUpperBound.mapIndexed { idx: Int, (leqCol: Field, leqValue: JsonNode) -> + zippedUpperBound.mapIndexed { idx: Int, (leqCol: EmittedField, leqValue: JsonNode) + -> val lastLeaf: WhereClauseLeafNode = if (idx < zippedUpperBound.size - 1) { Lesser(leqCol, leqValue) @@ -208,7 +210,7 @@ sealed class TriggerSplittablePartition( LesserOrEqual(leqCol, leqValue) } And( - zippedUpperBound.take(idx).map { (eqCol: Field, eqValue: JsonNode) -> + zippedUpperBound.take(idx).map { (eqCol: EmittedField, eqValue: JsonNode) -> Equal(eqCol, eqValue) } + listOf(lastLeaf), ) @@ -224,7 +226,7 @@ class TriggerSplittableSnapshotPartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, config: TriggerTableConfig, - primaryKey: List, + primaryKey: List, triggerCdcPartitionState: TriggerCdcPartitionState? = null, override val lowerBound: List?, override val upperBound: List?, @@ -248,10 +250,11 @@ class TriggerSplittableSnapshotPartition( ) } - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.snapshotCheckpoint( primaryKey = checkpointColumns, - primaryKeyCheckpoint = checkpointColumns.map { lastRecord[it.id] ?: Jsons.nullNode() }, + primaryKeyCheckpoint = + checkpointColumns.map { lastRecord.data.toJson()[it.id] ?: Jsons.nullNode() }, ) } @@ -262,9 +265,9 @@ sealed class TriggerCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, config: TriggerTableConfig, - checkpointColumns: List, + checkpointColumns: List, triggerCdcPartitionState: TriggerCdcPartitionState? = null, - val cursor: Field, + val cursor: EmittedField, private val explicitCursorUpperBound: JsonNode?, ) : TriggerSplittablePartition( @@ -301,11 +304,11 @@ class TriggerSplittableSnapshotWithCursorPartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, config: TriggerTableConfig, - primaryKey: List, + primaryKey: List, triggerCdcPartitionState: TriggerCdcPartitionState? = null, override val lowerBound: List?, override val upperBound: List?, - cursor: Field, + cursor: EmittedField, cursorUpperBound: JsonNode?, ) : TriggerCursorPartition( @@ -335,10 +338,11 @@ class TriggerSplittableSnapshotWithCursorPartition( ) } - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.snapshotWithCursorCheckpoint( primaryKey = checkpointColumns, - primaryKeyCheckpoint = checkpointColumns.map { lastRecord[it.id] ?: Jsons.nullNode() }, + primaryKeyCheckpoint = + checkpointColumns.map { lastRecord.data.toJson()[it.id] ?: Jsons.nullNode() }, cursor, cursorUpperBound, ) @@ -352,7 +356,7 @@ class TriggerCursorIncrementalPartition( selectQueryGenerator: SelectQueryGenerator, streamState: TriggerStreamState, config: TriggerTableConfig, - cursor: Field, + cursor: EmittedField, triggerCdcPartitionState: TriggerCdcPartitionState? = null, val cursorLowerBound: JsonNode, override val isLowerBoundIncluded: Boolean, @@ -379,10 +383,10 @@ class TriggerCursorIncrementalPartition( cursorCheckpoint = cursorUpperBound, ) - override fun incompleteState(lastRecord: ObjectNode): OpaqueStateValue = + override fun incompleteState(lastRecord: SelectQuerier.ResultRow): OpaqueStateValue = DefaultJdbcStreamStateValue.cursorIncrementalCheckpoint( cursor, - cursorCheckpoint = lastRecord[cursor.id] ?: Jsons.nullNode(), + cursorCheckpoint = lastRecord.data.toJson()[cursor.id] ?: Jsons.nullNode(), ) } diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionFactory.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionFactory.kt index 6c85d0b6fb01..a4c612866164 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionFactory.kt @@ -6,8 +6,8 @@ package io.airbyte.cdk import com.fasterxml.jackson.databind.JsonNode import io.airbyte.cdk.command.OpaqueStateValue -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.output.CatalogValidationFailureHandler import io.airbyte.cdk.output.InvalidCursor import io.airbyte.cdk.output.InvalidPrimaryKey @@ -77,14 +77,14 @@ class TriggerPartitionFactory( } val sv: DefaultJdbcStreamStateValue = Jsons.treeToValue(opaqueStateValue, DefaultJdbcStreamStateValue::class.java) - val pkMap: Map = + val pkMap: Map = sv.pkMap(stream) ?: run { handler.accept(ResetStream(stream.id)) streamState.reset() return coldStart(streamState) } - val cursorPair: Pair? = + val cursorPair: Pair? = if (sv.cursors.isEmpty()) { null } else { @@ -119,7 +119,7 @@ class TriggerPartitionFactory( ) } } else { - val (cursor: Field, cursorCheckpoint: JsonNode) = cursorPair + val (cursor: EmittedField, cursorCheckpoint: JsonNode) = cursorPair val triggerCdcPartitionState = if (cursor.id == config.CURSOR_FIELD.id) TriggerCdcPartitionState.INCREMENTAL else null @@ -160,11 +160,11 @@ class TriggerPartitionFactory( } } - private fun DefaultJdbcStreamStateValue.pkMap(stream: Stream): Map? { + private fun DefaultJdbcStreamStateValue.pkMap(stream: Stream): Map? { if (primaryKey.isEmpty()) { return mapOf() } - val fields: List = stream.configuredPrimaryKey ?: listOf() + val fields: List = stream.configuredPrimaryKey ?: listOf() if (primaryKey.keys != fields.map { it.id }.toSet()) { handler.accept( InvalidPrimaryKey(stream.id, primaryKey.keys.toList()), @@ -174,7 +174,9 @@ class TriggerPartitionFactory( return fields.associateWith { primaryKey[it.id]!! } } - private fun DefaultJdbcStreamStateValue.cursorPair(stream: Stream): Pair? { + private fun DefaultJdbcStreamStateValue.cursorPair( + stream: Stream + ): Pair? { if (cursors.size > 1) { handler.accept( InvalidCursor(stream.id, cursors.keys.toString()), @@ -182,10 +184,10 @@ class TriggerPartitionFactory( return null } val cursorLabel: String = cursors.keys.first() - val cursor: FieldOrMetaField? = + val cursor: DataOrMetaField? = stream.schema.find { it.id == cursorLabel } ?: config.COMMON_FIELDS.find { it.id == cursorLabel } - if (cursor !is Field) { + if (cursor !is EmittedField) { handler.accept( InvalidCursor(stream.id, cursorLabel), ) @@ -202,7 +204,7 @@ class TriggerPartitionFactory( private fun coldStart(streamState: TriggerStreamState): TriggerPartition { val stream: Stream = streamState.stream - val pkChosenFromCatalog: List = stream.configuredPrimaryKey ?: listOf() + val pkChosenFromCatalog: List = stream.configuredPrimaryKey ?: listOf() if (stream.configuredSyncMode == ConfiguredSyncMode.FULL_REFRESH) { if (pkChosenFromCatalog.isEmpty()) { return TriggerUnsplittableSnapshotPartition( @@ -220,8 +222,8 @@ class TriggerPartitionFactory( upperBound = null, ) } - val cursorChosenFromCatalog: Field = - stream.configuredCursor as? Field ?: throw ConfigErrorException("no cursor") + val cursorChosenFromCatalog: EmittedField = + stream.configuredCursor as? EmittedField ?: throw ConfigErrorException("no cursor") if (pkChosenFromCatalog.isEmpty()) { return TriggerUnsplittableSnapshotWithCursorPartition( selectQueryGenerator, diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionReader.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionReader.kt index c0fe1eca49e5..9564d1942595 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionReader.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionReader.kt @@ -6,8 +6,8 @@ package io.airbyte.cdk import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import io.airbyte.cdk.data.NullCodec import io.airbyte.cdk.discover.CommonMetaField -import io.airbyte.cdk.discover.Field -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.output.DataChannelMedium.SOCKET import io.airbyte.cdk.output.DataChannelMedium.STDIO import io.airbyte.cdk.output.OutputMessageRouter @@ -39,7 +39,7 @@ abstract class TriggerPartitionReader

>( private val nullValueEncoder = FieldValueEncoder(null, NullCodec) lateinit var outputMessageRouter: OutputMessageRouter - lateinit var outputRoute: ((NativeRecordPayload, Map?) -> Unit) + lateinit var outputRoute: ((NativeRecordPayload, Map?) -> Unit) private val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') private fun generatePartitionId(length: Int): String = @@ -149,7 +149,7 @@ abstract class TriggerPartitionReader

>( // Validate the data field name with the schema field name so they have the same case. private fun validateDataFieldName( payload: NativeRecordPayload, - schema: Set, + schema: Set, ): NativeRecordPayload { val validatedPayload: NativeRecordPayload = mutableMapOf() for (field in schema) { diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionsCreator.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionsCreator.kt index 574205a943ec..cde69637faf4 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionsCreator.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerPartitionsCreator.kt @@ -115,7 +115,7 @@ class TriggerPartitionsCreator< /** Collects a sample of rows in the unsplit partition. */ fun collectSample( - recordMapper: (ObjectNode) -> T, + recordMapper: (SelectQuerier.ResultRow) -> T, ): Sample { val values = mutableListOf() var previousWeight = 0L @@ -130,7 +130,7 @@ class TriggerPartitionsCreator< val samplingQuery: SelectQuery = partition.samplingQuery(sampleRateInvPow2) selectQuerier.executeQuery(samplingQuery).use { for (row in it) { - values.add(recordMapper(row.data.toJson())) + values.add(recordMapper(row)) } } if (values.size < sharedState.maxSampleSize) { @@ -171,12 +171,14 @@ class TriggerPartitionsCreator< return listOf(TriggerNonResumablePartitionReader(partition, config, deleteQuerier)) } // Sample the table for partition split boundaries and for record byte sizes. - val sample: Sample> = collectSample { record: ObjectNode -> - val boundary: OpaqueStateValue? = - (partition as? JdbcSplittablePartition<*>)?.incompleteState(record) - val rowByteSize: Long = sharedState.rowByteSizeEstimator().apply(record) - boundary to rowByteSize - } + val sample: Sample> = + collectSample { record: SelectQuerier.ResultRow -> + val boundary: OpaqueStateValue? = + (partition as? JdbcSplittablePartition<*>)?.incompleteState(record) + val rowByteSize: Long = + sharedState.rowByteSizeEstimator().apply(record.data.toJson()) + boundary to rowByteSize + } if (sample.kind == Sample.Kind.EMPTY) { log.info { "Sampling query found that the table was empty." } return listOf(CheckpointOnlyPartitionReader()) diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerTableConfig.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerTableConfig.kt index a67daf89902c..cc32a900906d 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerTableConfig.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/TriggerTableConfig.kt @@ -4,7 +4,7 @@ package io.airbyte.cdk -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.FieldType import io.airbyte.cdk.jdbc.BigIntegerFieldType import io.airbyte.cdk.jdbc.StringFieldType @@ -19,14 +19,14 @@ abstract class TriggerTableConfig() { abstract val cursorFieldType: FieldType abstract val cdcEnabled: Boolean - val CURSOR_FIELD: Field + val CURSOR_FIELD: EmittedField get() = - Field( + EmittedField( id = TRIGGER_TABLE_PREFIX + "change_time", type = cursorFieldType, ) - val COMMON_FIELDS: List + val COMMON_FIELDS: List get() = listOf( CHANGE_ID_FIELD, @@ -34,12 +34,16 @@ abstract class TriggerTableConfig() { OPERATION_TYPE_FIELD, ) - fun getTriggerTableSchemaFromStream(stream: Stream): List { - val result = mutableListOf() + fun getTriggerTableSchemaFromStream(stream: Stream): List { + val result = mutableListOf() stream.schema.forEach { field -> - result.add(Field(id = TRIGGER_TABLE_PREFIX + field.id + "_before", type = field.type)) - result.add(Field(id = TRIGGER_TABLE_PREFIX + field.id + "_after", type = field.type)) + result.add( + EmittedField(id = TRIGGER_TABLE_PREFIX + field.id + "_before", type = field.type) + ) + result.add( + EmittedField(id = TRIGGER_TABLE_PREFIX + field.id + "_after", type = field.type) + ) } return COMMON_FIELDS + result } @@ -48,12 +52,12 @@ abstract class TriggerTableConfig() { const val TRIGGER_TABLE_PREFIX = "_ab_trigger_" const val TRIGGER_TABLE_NAMESPACE = "_ab_cdc" val CHANGE_ID_FIELD = - Field( + EmittedField( id = TRIGGER_TABLE_PREFIX + "change_id", type = BigIntegerFieldType, ) val OPERATION_TYPE_FIELD = - Field( + EmittedField( id = TRIGGER_TABLE_PREFIX + "operation_type", type = StringFieldType, ) diff --git a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/operations/TriggerStreamFactory.kt b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/operations/TriggerStreamFactory.kt index c7f550b0ce08..3072f2ee1582 100644 --- a/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/operations/TriggerStreamFactory.kt +++ b/airbyte-cdk/bulk/toolkits/extract-trigger/src/main/kotlin/io/airbyte/cdk/operations/TriggerStreamFactory.kt @@ -8,7 +8,7 @@ import io.airbyte.cdk.TriggerTableConfig import io.airbyte.cdk.command.OpaqueStateValue import io.airbyte.cdk.data.NullCodec import io.airbyte.cdk.discover.CommonMetaField -import io.airbyte.cdk.discover.FieldOrMetaField +import io.airbyte.cdk.discover.DataOrMetaField import io.airbyte.cdk.discover.JdbcAirbyteStreamFactory import io.airbyte.cdk.discover.MetaField import io.airbyte.cdk.output.sockets.FieldValueEncoder @@ -22,7 +22,7 @@ import java.time.OffsetDateTime @Primary class TriggerStreamFactory(private val config: TriggerTableConfig) : JdbcAirbyteStreamFactory { - override val globalCursor: FieldOrMetaField = config.CURSOR_FIELD + override val globalCursor: DataOrMetaField = config.CURSOR_FIELD override val globalMetaFields: Set = setOf( diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceConnectorTest.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceConnectorTest.kt index 6eb4b4ecce50..95e0db92f6df 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceConnectorTest.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceConnectorTest.kt @@ -1,6 +1,9 @@ /* * Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ + +@file:Suppress("DEPRECATION") + package io.airbyte.cdk.test.fixtures.legacy import com.fasterxml.jackson.databind.JsonNode diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceDatabaseTypeTest.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceDatabaseTypeTest.kt index de0646245abe..8305c9f9ae5b 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceDatabaseTypeTest.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/AbstractSourceDatabaseTypeTest.kt @@ -116,13 +116,13 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { @Throws(Exception::class) open fun testDataContent() { // Class used to make easier the error reporting - class MissedRecords( // Stream that is missing any value + data class MissedRecords( // Stream that is missing any value var streamName: String?, // Which are the values that has not being gathered from the source var missedValues: List ) - class UnexpectedRecord(val streamName: String, val unexpectedValue: String?) + data class UnexpectedRecord(val streamName: String, val unexpectedValue: String?) val catalog = configuredCatalog val allMessages = runRead(catalog) @@ -136,8 +136,7 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { val testByName: MutableMap = HashMap() // If there is no expected value in the test set we don't include it in the list to be - // asserted - // (even if the table contains records) + // asserted (even if the table contains records) testDataHolders.forEach( Consumer { testDataHolder: TestDataHolder -> if (!testDataHolder.expectedValues.isEmpty()) { @@ -145,7 +144,7 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { testDataHolder.expectedValues testByName[testDataHolder.nameWithTestPrefix] = testDataHolder } else { - LOGGER.warn("Missing expected values for type: " + testDataHolder.sourceType) + LOGGER.warn { "Missing expected values for type: ${testDataHolder.sourceType}" } } } ) @@ -201,7 +200,6 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { for (errors in errorsByStream.values) { errorStrings.add(StringUtils.join(errors, "\n")) } - Assertions.assertTrue(errorsByStream.isEmpty(), StringUtils.join(errorStrings, "\n")) } @@ -232,7 +230,7 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { for (test in testDataHolders) { database!!.query { ctx: DSLContext -> ctx.fetch(test.createSqlQuery) - LOGGER.info("Table {} is created.", test.nameWithTestPrefix) + LOGGER.info { "Table ${test.nameSpace}.${test.nameWithTestPrefix} is created." } null } } @@ -243,11 +241,9 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { for (test in testDataHolders) { database!!.query { ctx: DSLContext -> test.insertSqlQueries.forEach(Consumer { sql: String -> ctx.fetch(sql) }) - LOGGER.info( - "Inserted {} rows in Ttable {}", - test.insertSqlQueries.size, - test.nameWithTestPrefix - ) + LOGGER.info { + "Inserted ${test.insertSqlQueries.size} rows in table ${test.nameWithTestPrefix}" + } null } } @@ -343,7 +339,7 @@ abstract class AbstractSourceDatabaseTypeTest : AbstractSourceConnectorTest() { } protected fun printMarkdownTestTable() { - LOGGER.info(markdownTestTable) + LOGGER.info { markdownTestTable } } @Throws(SQLException::class) diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ConnectorConfigUpdater.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ConnectorConfigUpdater.kt index 81e0b54fd2d2..202543394ecd 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ConnectorConfigUpdater.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ConnectorConfigUpdater.kt @@ -2,6 +2,8 @@ * Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ +@file:Suppress("DEPRECATION") + package io.airbyte.cdk.test.fixtures.legacy import com.google.common.hash.Hashing @@ -55,12 +57,9 @@ class ConnectorConfigUpdater( "update source" )!! - LOGGER.info( - "Persisted updated configuration for source {}. New config hash: {}.", - sourceId, - Hashing.sha256() - .hashString(updatedSource.connectionConfiguration.asText(), StandardCharsets.UTF_8) - ) + LOGGER.info { + "Persisted updated configuration for source $sourceId. New config hash: ${Hashing.sha256().hashString(updatedSource.connectionConfiguration.asText(), StandardCharsets.UTF_8)}." + } } /** @@ -91,15 +90,9 @@ class ConnectorConfigUpdater( "update destination" )!! - LOGGER.info( - "Persisted updated configuration for destination {}. New config hash: {}.", - destinationId, - Hashing.sha256() - .hashString( - updatedDestination.connectionConfiguration.asText(), - StandardCharsets.UTF_8 - ) - ) + LOGGER.info { + "Persisted updated configuration for destination $destinationId. New config hash: ${Hashing.sha256().hashString(updatedDestination.connectionConfiguration.asText(), StandardCharsets.UTF_8)}." + } } companion object {} diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ContainerFactory.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ContainerFactory.kt index e93f305e9a33..03cd7fe18995 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ContainerFactory.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ContainerFactory.kt @@ -183,11 +183,9 @@ abstract class ContainerFactory> { imageName: DockerImageName, containerModifiersWithNames: List, String>> ): C { - LOGGER.info( - "Creating new container based on {} with {}.", - imageName, - containerModifiersWithNames.map { it.second } - ) + LOGGER.info { + "Creating new container based on $imageName with ${containerModifiersWithNames.map { it.second }}." + } val container = createNewContainer(imageName) @Suppress("unchecked_cast") val logConsumer: Slf4jLogConsumer = @@ -205,12 +203,9 @@ abstract class ContainerFactory> { } container.withLogConsumer(logConsumer) for (resolvedNamedContainerModifier in containerModifiersWithNames) { - LOGGER.info( - "Calling {} in {} on new container based on {}.", - resolvedNamedContainerModifier.second, - javaClass.name, - imageName - ) + LOGGER.info { + "Calling ${resolvedNamedContainerModifier.second} in ${javaClass.name} on new container based on $imageName." + } resolvedNamedContainerModifier.first.accept(container) } container.start() diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteSource.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteSource.kt index 18544229147e..ba984047a539 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteSource.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteSource.kt @@ -12,7 +12,6 @@ import java.nio.file.Path import java.time.Duration import java.time.temporal.ChronoUnit import java.util.* -import java.util.List import java.util.concurrent.TimeUnit private val LOGGER = KotlinLogging.logger {} @@ -97,7 +96,7 @@ internal constructor( // stdout logs are logged elsewhere since stdout also contains data LineGobbler.gobble( sourceProcess!!.errorStream, - { msg: String -> LOGGER.error(msg) }, + { msg: String -> LOGGER.error { msg } }, "airbyte-source", CONTAINER_LOG_MDC_BUILDER ) @@ -105,7 +104,7 @@ internal constructor( logInitialStateAsJSON(sourceConfig) val acceptedMessageTypes = - List.of( + listOf( AirbyteMessage.Type.RECORD, AirbyteMessage.Type.STATE, AirbyteMessage.Type.TRACE, @@ -114,7 +113,7 @@ internal constructor( messageIterator = streamFactory .create(IOs.newBufferedReader(sourceProcess!!.inputStream)) - .peek { message: AirbyteMessage -> heartbeatMonitor.beat() } + .peek { _: AirbyteMessage -> heartbeatMonitor.beat() } .filter { message: AirbyteMessage -> acceptedMessageTypes.contains(message.type) } .iterator() } @@ -141,11 +140,11 @@ internal constructor( @Throws(Exception::class) override fun close() { if (sourceProcess == null) { - LOGGER.debug("Source process already exited") + LOGGER.debug { "Source process already exited" } return } - LOGGER.debug("Closing source process") + LOGGER.debug { "Closing source process" } TestHarnessUtils.gentleClose( sourceProcess, GRACEFUL_SHUTDOWN_DURATION.toMillis(), @@ -156,20 +155,20 @@ internal constructor( val message = if (sourceProcess!!.isAlive) "Source has not terminated " else "Source process exit with code " + exitValue - LOGGER.warn("$message. This warning is normal if the job was cancelled.") + LOGGER.warn { "$message. This warning is normal if the job was cancelled." } } } @Throws(Exception::class) override fun cancel() { - LOGGER.info("Attempting to cancel source process...") + LOGGER.info { "Attempting to cancel source process..." } if (sourceProcess == null) { - LOGGER.info("Source process no longer exists, cancellation is a no-op.") + LOGGER.info { "Source process no longer exists, cancellation is a no-op." } } else { - LOGGER.info("Source process exists, cancelling...") + LOGGER.info { "Source process exists, cancelling..." } TestHarnessUtils.cancelProcess(sourceProcess) - LOGGER.info("Cancelled source process!") + LOGGER.info { "Cancelled source process!" } } } @@ -179,11 +178,11 @@ internal constructor( } if (sourceConfig.state == null) { - LOGGER.info("source starting state | empty") + LOGGER.info { "source starting state | empty" } return } - LOGGER.info("source starting state | " + Jsons.serialize(sourceConfig.state!!.state)) + LOGGER.info { "source starting state | ${Jsons.serialize(sourceConfig.state!!.state)}" } } companion object { diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteStreamFactory.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteStreamFactory.kt index db323b7fbf04..ddaa75959546 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteStreamFactory.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultAirbyteStreamFactory.kt @@ -125,7 +125,7 @@ class DefaultAirbyteStreamFactory : AirbyteStreamFactory { // we log as info all the lines that are not valid json // some sources actually log their process on stdout, we // want to make sure this info is available in the logs. - containerLogMdcBuilder.build().use { mdcScope -> logger.info(line) } + containerLogMdcBuilder.build().use { _ -> logger.info { line } } } return jsonLine.stream() } @@ -133,7 +133,7 @@ class DefaultAirbyteStreamFactory : AirbyteStreamFactory { protected fun validate(json: JsonNode): Boolean { val res = protocolValidator.test(json) if (!res) { - logger.error("Validation failed: {}", Jsons.serialize(json)) + logger.error { "Validation failed: ${Jsons.serialize(json)}" } } return res } @@ -141,7 +141,7 @@ class DefaultAirbyteStreamFactory : AirbyteStreamFactory { protected fun toAirbyteMessage(json: JsonNode?): Stream { val m = Jsons.tryObject(json, AirbyteMessage::class.java) if (m.isEmpty) { - logger.error("Deserialization failed: {}", Jsons.serialize(json)) + logger.error { "Deserialization failed: ${Jsons.serialize(json)}" } } return m.stream() } @@ -149,7 +149,7 @@ class DefaultAirbyteStreamFactory : AirbyteStreamFactory { protected fun filterLog(message: AirbyteMessage): Boolean { val isLog = message.type == AirbyteMessage.Type.LOG if (isLog) { - containerLogMdcBuilder.build().use { mdcScope -> internalLog(message.log) } + containerLogMdcBuilder.build().use { _ -> internalLog(message.log) } } return !isLog } @@ -163,18 +163,18 @@ class DefaultAirbyteStreamFactory : AirbyteStreamFactory { when (logMessage.level) { AirbyteLogMessage.Level.FATAL, - AirbyteLogMessage.Level.ERROR -> logger.error(combinedMessage) - AirbyteLogMessage.Level.WARN -> logger.warn(combinedMessage) - AirbyteLogMessage.Level.DEBUG -> logger.debug(combinedMessage) - AirbyteLogMessage.Level.TRACE -> logger.trace(combinedMessage) - else -> logger.info(combinedMessage) + AirbyteLogMessage.Level.ERROR -> logger.error { combinedMessage } + AirbyteLogMessage.Level.WARN -> logger.warn { combinedMessage } + AirbyteLogMessage.Level.DEBUG -> logger.debug { combinedMessage } + AirbyteLogMessage.Level.TRACE -> logger.trace { combinedMessage } + else -> logger.info { combinedMessage } } } // Human-readable byte size from // https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java - private fun humanReadableByteCountSI(bytes: Long): String { - var bytes = bytes + private fun humanReadableByteCountSI(initialBytes: Long): String { + var bytes = initialBytes if (-1000 < bytes && bytes < 1000) { return "$bytes B" } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultCheckConnectionTestHarness.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultCheckConnectionTestHarness.kt index a5472b0f3151..9f426826898b 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultCheckConnectionTestHarness.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultCheckConnectionTestHarness.kt @@ -20,11 +20,11 @@ constructor( private lateinit var process: Process @Throws(TestHarnessException::class) - override fun run(input: StandardCheckConnectionInput, jobRoot: Path): ConnectorJobOutput { + override fun run(inputType: StandardCheckConnectionInput, jobRoot: Path): ConnectorJobOutput { LineGobbler.startSection("CHECK") try { - val inputConfig = input.connectionConfiguration!! + val inputConfig = inputType.connectionConfiguration!! val process = integrationLauncher.check( jobRoot, @@ -36,7 +36,7 @@ constructor( val jobOutput = ConnectorJobOutput().withOutputType(ConnectorJobOutput.OutputType.CHECK_CONNECTION) - LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error(msg) }) + LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error { msg } }) val messagesByType = TestHarnessUtils.getMessagesByType(process, streamFactory, 30) val connectionStatus = @@ -45,7 +45,7 @@ constructor( .map { obj: AirbyteMessage -> obj.connectionStatus } .firstOrNull() - if (input.actorId != null && input.actorType != null) { + if (inputType.actorId != null && inputType.actorType != null) { val optionalConfigMsg = TestHarnessUtils.getMostRecentConfigControlMessage(messagesByType) if ( @@ -55,15 +55,15 @@ constructor( optionalConfigMsg.get() ) ) { - when (input.actorType!!) { + when (inputType.actorType!!) { ActorType.SOURCE -> connectorConfigUpdater.updateSource( - input.actorId, + inputType.actorId, optionalConfigMsg.get().config ) ActorType.DESTINATION -> connectorConfigUpdater.updateDestination( - input.actorId, + inputType.actorId, optionalConfigMsg.get().config ) } @@ -76,13 +76,11 @@ constructor( ConnectorJobOutput.OutputType.CHECK_CONNECTION, messagesByType ) - failureReason.ifPresent { failureReason: FailureReason -> - jobOutput.failureReason = failureReason - } + failureReason.ifPresent { jobOutput.failureReason = it } val exitCode = process.exitValue() if (exitCode != 0) { - LOGGER.warn("Check connection job subprocess finished with exit code {}", exitCode) + LOGGER.warn { "Check connection job subprocess finished with exit code $exitCode" } } if (connectionStatus != null) { @@ -95,7 +93,7 @@ constructor( ) ) .withMessage(connectionStatus.message) - LOGGER.info("Check connection job received output: {}", output) + LOGGER.info { "Check connection job received output: $output" } jobOutput.checkConnection = output } else if (failureReason.isEmpty) { TestHarnessUtils.throwWorkerException( @@ -106,7 +104,7 @@ constructor( LineGobbler.endSection("CHECK") return jobOutput } catch (e: Exception) { - LOGGER.error("Unexpected error while checking connection: ", e) + LOGGER.error(e) { "Unexpected error while checking connection: " } LineGobbler.endSection("CHECK") throw TestHarnessException("Unexpected error while getting checking connection.", e) } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultDiscoverCatalogTestHarness.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultDiscoverCatalogTestHarness.kt index 9f40e0ca4d04..8ad0521cd05b 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultDiscoverCatalogTestHarness.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultDiscoverCatalogTestHarness.kt @@ -2,6 +2,8 @@ * Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ +@file:Suppress("DEPRECATION") + package io.airbyte.cdk.test.fixtures.legacy import io.airbyte.api.client.AirbyteApiClient @@ -26,12 +28,9 @@ constructor( @Volatile private lateinit var process: Process @Throws(TestHarnessException::class) - override fun run( - discoverSchemaInput: StandardDiscoverCatalogInput, - jobRoot: Path - ): ConnectorJobOutput { + override fun run(inputType: StandardDiscoverCatalogInput, jobRoot: Path): ConnectorJobOutput { try { - val inputConfig = discoverSchemaInput.connectionConfiguration!! + val inputConfig = inputType.connectionConfiguration!! process = integrationLauncher.discover( jobRoot, @@ -43,7 +42,7 @@ constructor( ConnectorJobOutput() .withOutputType(ConnectorJobOutput.OutputType.DISCOVER_CATALOG_ID) - LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error(msg) }) + LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error { msg } }) val messagesByType = TestHarnessUtils.getMessagesByType(process, streamFactory, 30) @@ -63,7 +62,7 @@ constructor( ) ) { connectorConfigUpdater.updateSource( - UUID.fromString(discoverSchemaInput.sourceId), + UUID.fromString(inputType.sourceId), optionalConfigMsg.get().config ) jobOutput.connectorConfigurationUpdated = true @@ -78,7 +77,7 @@ constructor( val exitCode = process.exitValue() if (exitCode != 0) { - LOGGER.warn("Discover job subprocess finished with exit codee {}", exitCode) + LOGGER.warn { "Discover job subprocess finished with exit codee $exitCode" } } if (catalog != null) { @@ -86,10 +85,7 @@ constructor( AirbyteApiClient.retryWithJitter( { airbyteApiClient.sourceApi.writeDiscoverCatalogResult( - buildSourceDiscoverSchemaWriteRequestBody( - discoverSchemaInput, - catalog - ) + buildSourceDiscoverSchemaWriteRequestBody(inputType, catalog) ) }, WRITE_DISCOVER_CATALOG_LOGS_TAG diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultGetSpecTestHarness.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultGetSpecTestHarness.kt index 2a00e6360232..461a015e4137 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultGetSpecTestHarness.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DefaultGetSpecTestHarness.kt @@ -18,19 +18,19 @@ constructor( private lateinit var process: Process @Throws(TestHarnessException::class) - override fun run(config: JobGetSpecConfig, jobRoot: Path): ConnectorJobOutput { + override fun run(inputType: JobGetSpecConfig, jobRoot: Path): ConnectorJobOutput { try { val process = integrationLauncher.spec(jobRoot) this.process = process val jobOutput = ConnectorJobOutput().withOutputType(ConnectorJobOutput.OutputType.SPEC) - LineGobbler.gobble(process!!.errorStream, { msg: String -> LOGGER.error(msg) }) + LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error { msg } }) val messagesByType = TestHarnessUtils.getMessagesByType(process, streamFactory, 30) val spec = messagesByType - .getOrDefault(AirbyteMessage.Type.SPEC, ArrayList())!! + .getOrDefault(AirbyteMessage.Type.SPEC, ArrayList()) .map { obj: AirbyteMessage -> obj.spec } .firstOrNull() @@ -39,13 +39,11 @@ constructor( ConnectorJobOutput.OutputType.SPEC, messagesByType ) - failureReason!!.ifPresent { failureReason: FailureReason -> - jobOutput.failureReason = failureReason - } + failureReason.ifPresent { jobOutput.failureReason = it } - val exitCode = process!!.exitValue() + val exitCode = process.exitValue() if (exitCode != 0) { - LOGGER.warn("Spec job subprocess finished with exit code {}", exitCode) + LOGGER.warn { "Spec job subprocess finished with exit code $exitCode" } } if (spec != null) { @@ -60,7 +58,7 @@ constructor( return jobOutput } catch (e: Exception) { throw TestHarnessException( - String.format("Error while getting spec from image %s", config.dockerImage), + String.format("Error while getting spec from image %s", inputType.dockerImage), e ) } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DockerProcessFactory.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DockerProcessFactory.kt index f381c0e562e7..09f34541b208 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DockerProcessFactory.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/DockerProcessFactory.kt @@ -46,7 +46,7 @@ class DockerProcessFactory( jobType: String?, jobId: String, attempt: Int, - jobRoot: Path, + jobPath: Path, imageName: String, usesIsolatedPool: Boolean, usesStdin: Boolean, @@ -56,7 +56,7 @@ class DockerProcessFactory( allowedHosts: AllowedHosts?, labels: Map?, jobMetadata: Map, - internalToExternalPorts: Map?, + portMapping: Map?, additionalEnvironmentVariables: Map, vararg args: String ): Process { @@ -65,12 +65,12 @@ class DockerProcessFactory( throw TestHarnessException("Could not find image: $imageName") } - if (!jobRoot.toFile().exists()) { - Files.createDirectory(jobRoot) + if (!jobPath.toFile().exists()) { + Files.createDirectory(jobPath) } for ((key, value) in files) { - IOs.writeFile(jobRoot, key, value) + IOs.writeFile(jobPath, key, value) } val cmd: MutableList = @@ -81,7 +81,7 @@ class DockerProcessFactory( "--init", "-i", "-w", - rebasePath(jobRoot).toString(), // rebases the job root on the job data mount + rebasePath(jobPath).toString(), // rebases the job root on the job data mount "--log-driver", "none" ) @@ -93,12 +93,9 @@ class DockerProcessFactory( attempt, DOCKER_NAME_LEN_LIMIT ) - LOGGER.info( - "Creating docker container = {} with resources {} and allowedHosts {}", - containerName, - resourceRequirements, - allowedHosts - ) + LOGGER.info { + "Creating docker container = $containerName with resources $resourceRequirements and allowedHosts $allowedHosts" + } cmd.add("--name") cmd.add(containerName) cmd.addAll(localDebuggingOptions(containerName)) @@ -159,7 +156,7 @@ class DockerProcessFactory( cmd.add(imageName) cmd.addAll(args) - LOGGER.info("Preparing command: {}", Joiner.on(" ").join(cmd)) + LOGGER.info { "Preparing command: ${Joiner.on(" ").join(cmd)}" } return ProcessBuilder(cmd).start() } catch (e: IOException) { @@ -177,8 +174,8 @@ class DockerProcessFactory( fun checkImageExists(imageName: String?): Boolean { try { val process = ProcessBuilder(imageExistsScriptPath.toString(), imageName).start() - LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error(msg) }) - LineGobbler.gobble(process.inputStream, { msg: String -> LOGGER.info(msg) }) + LineGobbler.gobble(process.errorStream, { msg: String -> LOGGER.error { msg } }) + LineGobbler.gobble(process.inputStream, { msg: String -> LOGGER.info { msg } }) TestHarnessUtils.gentleClose(process, 10, TimeUnit.MINUTES) @@ -241,7 +238,7 @@ class DockerProcessFactory( .map( Function { imageName: String -> ProcessFactory.Companion.extractShortImageName(containerName) - .startsWith(imageName!!) + .startsWith(imageName) } ) .orElse(false) && diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/EntrypointEnvChecker.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/EntrypointEnvChecker.kt index d3dfef9aab7b..44451430a724 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/EntrypointEnvChecker.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/EntrypointEnvChecker.kt @@ -47,8 +47,7 @@ object EntrypointEnvChecker { emptyMap() ) - val stdout = - BufferedReader(InputStreamReader(process!!.inputStream, StandardCharsets.UTF_8)) + val stdout = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)) var outputLine: String? = null diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/IOs.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/IOs.kt index 57b2e3746a56..2745c73244b1 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/IOs.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/IOs.kt @@ -3,7 +3,6 @@ */ package io.airbyte.cdk.test.fixtures.legacy -import com.google.common.base.Charsets import java.io.* import java.nio.charset.StandardCharsets import java.nio.file.Files diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/Jsons.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/Jsons.kt index fb93106da480..603362e3619a 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/Jsons.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/Jsons.kt @@ -15,7 +15,6 @@ import com.fasterxml.jackson.databind.ObjectWriter import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.google.common.base.Charsets import com.google.common.base.Preconditions import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ProcessFactory.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ProcessFactory.kt index 711557bdbe3f..d758046b1e60 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ProcessFactory.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/ProcessFactory.kt @@ -1,6 +1,7 @@ /* * Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ + package io.airbyte.cdk.test.fixtures.legacy import java.nio.file.Path @@ -69,7 +70,8 @@ interface ProcessFactory { lenLimit: Int ): String { var imageName = extractShortImageName(fullImagePath) - val randSuffix = RandomStringUtils.randomAlphabetic(5).lowercase(Locale.getDefault()) + val randSuffix = + RandomStringUtils.secure().nextAlphabetic(5).lowercase(Locale.getDefault()) val suffix = "$jobType-$jobId-$attempt-$randSuffix" var processName = "$imageName-$suffix" diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SourceAcceptanceTest.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SourceAcceptanceTest.kt index d832831f5b72..9fa79622f66c 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SourceAcceptanceTest.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SourceAcceptanceTest.kt @@ -59,7 +59,7 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { * Specification for integration. Will be passed to integration where appropriate in each test. * Should be valid. */ - @get:Throws(Exception::class) protected abstract val spec: ConnectorSpecification + @get:Throws(Exception::class) protected abstract val spec: ConnectorSpecification? /** * The catalog to use to validate the output of read operations. This will be used as follows: @@ -75,15 +75,11 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { /** a JSON file representing the state file to use when testing incremental syncs */ @get:Throws(Exception::class) protected abstract val state: JsonNode? + private val log = KotlinLogging.logger {} /** Verify that a spec operation issued to the connector returns a valid spec. */ @Test - @Throws(Exception::class) open fun testGetSpec() { - Assertions.assertEquals( - spec, - runSpec(), - "Expected spec output by integration to be equal to spec provided by test runner" - ) + Assertions.assertDoesNotThrow { runSpec() } } /** @@ -95,7 +91,7 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { fun testCheckConnection() { Assertions.assertEquals( StandardCheckConnectionOutput.Status.SUCCEEDED, - runCheck()!!.status, + runCheck().status, "Expected check connection operation to succeed" ) } @@ -138,7 +134,7 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { @Throws(Exception::class) open fun testFullRefreshRead() { if (!sourceSupportsFullRefresh()) { - LOGGER.info("Test skipped. Source does not support full refresh.") + LOGGER.info { "Test skipped. Source does not support full refresh." } return } @@ -167,7 +163,7 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { @Throws(Exception::class) open fun testIdenticalFullRefreshes() { if (!sourceSupportsFullRefresh()) { - LOGGER.info("Test skipped. Source does not support full refresh.") + LOGGER.info { "Test skipped. Source does not support full refresh." } return } @@ -273,8 +269,8 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { assert(Objects.nonNull(latestState)) val secondSyncRecords = filterRecords(runRead(configuredCatalog, latestState)) Assertions.assertTrue( - secondSyncRecords.isEmpty(), - "Expected the second incremental sync to produce no records when given the first sync's output state." + secondSyncRecords.size <= configuredCatalog.streams.size, + "Expected the second incremental sync to produce no more than the one record per stream when given the first sync's output state." ) } @@ -295,7 +291,7 @@ abstract class SourceAcceptanceTest : AbstractSourceConnectorTest() { } if (!sourceSupportsFullRefresh()) { - LOGGER.info("Test skipped. Source does not support full refresh.") + LOGGER.info { "Test skipped. Source does not support full refresh." } return } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SshBastionContainer.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SshBastionContainer.kt index d2deb55b5bf0..4c758b66db2c 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SshBastionContainer.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/SshBastionContainer.kt @@ -4,7 +4,6 @@ package io.airbyte.cdk.test.fixtures.legacy import com.fasterxml.jackson.databind.JsonNode -import com.google.common.collect.ImmutableMap import java.io.IOException import java.util.* import java.util.function.Consumer @@ -50,61 +49,55 @@ class SshBastionContainer : AutoCloseable { if (innerAddress) getInnerContainerAddress(container!!) else getOuterContainerAddress(container!!) return Jsons.jsonNode( - ImmutableMap.builder() - .put("tunnel_host", Objects.requireNonNull(containerAddress!!.left)) - .put("tunnel_method", tunnelMethod) - .put("tunnel_port", containerAddress.right) - .put("tunnel_user", SSH_USER) - .put( - "tunnel_user_password", + mapOf( + "tunnel_host" to Objects.requireNonNull(containerAddress.left), + "tunnel_method" to tunnelMethod, + "tunnel_port" to containerAddress.right, + "tunnel_user" to SSH_USER, + "tunnel_user_password" to if (tunnelMethod == SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH) SSH_PASSWORD - else "" - ) - .put( - "ssh_key", + else "", + "ssh_key" to if (tunnelMethod == SshTunnel.TunnelMethod.SSH_KEY_AUTH) container!!.execInContainer("cat", "var/bastion/id_rsa").stdout - else "" - ) - .build() + else "", + ) ) } @Throws(IOException::class, InterruptedException::class) fun getTunnelConfig( tunnelMethod: SshTunnel.TunnelMethod, - builderWithSchema: ImmutableMap.Builder, + builderWithSchema: MutableMap, innerAddress: Boolean ): JsonNode? { - return Jsons.jsonNode( - builderWithSchema - .put("tunnel_method", getTunnelMethod(tunnelMethod, innerAddress)) - .build() - ) + builderWithSchema["tunnel_method"] = getTunnelMethod(tunnelMethod, innerAddress) + return Jsons.jsonNode(builderWithSchema) } - fun getBasicDbConfigBuider(db: JdbcDatabaseContainer<*>): ImmutableMap.Builder { + fun getBasicDbConfigBuider(db: JdbcDatabaseContainer<*>): MutableMap { return getBasicDbConfigBuider(db, db.databaseName) } fun getBasicDbConfigBuider( db: JdbcDatabaseContainer<*>, schemas: MutableList - ): ImmutableMap.Builder { - return getBasicDbConfigBuider(db, db.databaseName).put("schemas", schemas) + ): MutableMap { + return getBasicDbConfigBuider(db, db.databaseName).also { it["schemas"] = schemas } } fun getBasicDbConfigBuider( db: JdbcDatabaseContainer<*>, schemaName: String - ): ImmutableMap.Builder { - return ImmutableMap.builder() - .put("host", Objects.requireNonNull(HostPortResolver.resolveHost(db))) - .put("username", db.username) - .put("password", db.password) - .put("port", HostPortResolver.resolvePort(db)) - .put("database", schemaName) - .put("ssl", false) + ): MutableMap { + return mutableMapOf( + "host" to Objects.requireNonNull(HostPortResolver.resolveHost(db)), + "username" to db.username, + "password" to db.password, + "port" to HostPortResolver.resolvePort(db), + "database" to schemaName, + "ssl" to false, + ) } fun stopAndCloseContainers(db: JdbcDatabaseContainer<*>) { diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestDatabase.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestDatabase.kt index 60eb907844ad..97dd7b28e305 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestDatabase.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestDatabase.kt @@ -5,6 +5,7 @@ package io.airbyte.cdk.test.fixtures.legacy import com.fasterxml.jackson.databind.node.ObjectNode import com.google.common.collect.ImmutableMap +import io.airbyte.cdk.test.fixtures.legacy.JdbcUtils.MODE_KEY import io.github.oshai.kotlinlogging.KotlinLogging import java.io.IOException import java.io.UncheckedIOException @@ -51,7 +52,7 @@ protected constructor(val container: C) : AutoCloseable { private val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS") init { - LOGGER!!.info(formatLogLine("creating database $databaseName")) + LOGGER.info { formatLogLine("creating database $databaseName") } } protected fun formatLogLine(logLine: String?): String { @@ -162,7 +163,7 @@ protected constructor(val container: C) : AutoCloseable { try { database.query { ctx: DSLContext -> sql.forEach { statement: String -> - LOGGER!!.info("executing SQL statement {}", statement) + LOGGER.info { "executing SQL statement $statement" } ctx.execute(statement) } null @@ -178,14 +179,14 @@ protected constructor(val container: C) : AutoCloseable { return } try { - LOGGER!!.info( + LOGGER.info { formatLogLine( String.format("executing command %s", Strings.join(cmd.asIterable(), " ")) ) - ) + } val exec = container.execInContainer(*cmd.toTypedArray()) if (exec!!.exitCode == 0) { - LOGGER.info( + LOGGER.info { formatLogLine( String.format( "execution success\nstdout:\n%s\nstderr:\n%s", @@ -193,9 +194,9 @@ protected constructor(val container: C) : AutoCloseable { exec.stderr ) ) - ) + } } else { - LOGGER.error( + LOGGER.error { formatLogLine( String.format( "execution failure, code %s\nstdout:\n%s\nstderr:\n%s", @@ -204,7 +205,7 @@ protected constructor(val container: C) : AutoCloseable { exec.stderr ) ) - ) + } } } catch (e: IOException) { throw UncheckedIOException(e) @@ -239,7 +240,7 @@ protected constructor(val container: C) : AutoCloseable { override fun close() { execSQL(cleanupSQL.stream()) execInContainer(inContainerUndoBootstrapCmd()) - LOGGER!!.info("closing database databaseId=$databaseId") + LOGGER.info { "closing database databaseId=$databaseId" } } open class ConfigBuilder, B : ConfigBuilder>( @@ -284,7 +285,8 @@ protected constructor(val container: C) : AutoCloseable { } open fun withoutSsl(): B { - return with(JdbcUtils.SSL_KEY, false) + // return with(JdbcUtils.SSL_KEY, false) + return withSsl(mutableMapOf(MODE_KEY to "disable")) } open fun withSsl(sslMode: MutableMap): B { @@ -292,7 +294,7 @@ protected constructor(val container: C) : AutoCloseable { } companion object { - @JvmField val DEFAULT_CDC_REPLICATION_INITIAL_WAIT: Duration = Duration.ofSeconds(5) + @JvmField val DEFAULT_CDC_REPLICATION_INITIAL_WAIT: Duration = Duration.ofSeconds(15) } } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestEnvConfigs.kt b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestEnvConfigs.kt index e60b2349dbd5..1d7810157502 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestEnvConfigs.kt +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/legacy/TestEnvConfigs.kt @@ -45,7 +45,7 @@ class TestEnvConfigs private constructor(envMap: Map) { try { return@getEnvOrDefault DeploymentMode.valueOf(s) } catch (e: IllegalArgumentException) { - LOGGER.info(s + " not recognized, defaulting to " + DeploymentMode.OSS) + LOGGER.info { "$s not recognized, defaulting to ${DeploymentMode.OSS}" } return@getEnvOrDefault DeploymentMode.OSS } } @@ -75,7 +75,7 @@ class TestEnvConfigs private constructor(envMap: Map) { // an empty string. Change this logic if this assumption no longer holds. val jobSharedEnvMap = JOB_SHARED_ENVS.entries.associate { - it.key to Exceptions.swallowWithDefault({ it.value.apply(this) ?: "" }, "") + it.key to Exceptions.swallowWithDefault({ it.value.apply(this) }, "") } return MoreMaps.merge(jobPrefixedEnvMap, jobSharedEnvMap) } @@ -95,14 +95,11 @@ class TestEnvConfigs private constructor(envMap: Map) { isSecret: Boolean ): T { val value = getEnv.apply(key) - if (value != null && !value.isEmpty()) { + if (value.isNotEmpty()) { return parser.apply(value) } else { - LOGGER.info( - "Using default value for environment variable {}: '{}'", - key, - if (isSecret) "*****" else defaultValue - ) + val displayValue = if (isSecret) "*****" else defaultValue.toString() + LOGGER.info { "Using default value for environment variable $key: '$displayValue'" } return defaultValue } } @@ -113,8 +110,6 @@ class TestEnvConfigs private constructor(envMap: Map) { fun getEnsureEnv(name: String): String { val value = getEnv(name) - checkNotNull(value != null) { "$name environment variable cannot be null" } - return value } diff --git a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/resources/ssh-tunnel-spec.json b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/resources/ssh-tunnel-spec.json index 8b2551186dae..97f9fdeb672e 100644 --- a/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/resources/ssh-tunnel-spec.json +++ b/airbyte-cdk/bulk/toolkits/legacy-source-integration-tests/src/testFixtures/resources/ssh-tunnel-spec.json @@ -114,6 +114,6 @@ ], "description": "Whether to initiate an SSH tunnel before connecting to the database, and if so, which kind of authentication to use.", "title": "SSH Tunnel Method", - "order": 9, + "order": 11, "type": "object" } diff --git a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizer.kt b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizer.kt index f58d56b84841..ec4a63e6a143 100644 --- a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizer.kt +++ b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizer.kt @@ -7,13 +7,17 @@ package io.airbyte.cdk.load.toolkits.iceberg.parquet import io.airbyte.cdk.ConfigErrorException import io.airbyte.cdk.load.toolkits.iceberg.parquet.IcebergTypesComparator.Companion.PARENT_CHILD_SEPARATOR import io.airbyte.cdk.load.toolkits.iceberg.parquet.IcebergTypesComparator.Companion.splitIntoParentAndLeaf +import io.github.oshai.kotlinlogging.KotlinLogging import jakarta.inject.Singleton import org.apache.iceberg.Schema +import org.apache.iceberg.SortDirection import org.apache.iceberg.Table import org.apache.iceberg.UpdateSchema import org.apache.iceberg.types.Type import org.apache.iceberg.types.Type.PrimitiveType +private val logger = KotlinLogging.logger {} + /** Describes how the [IcebergTableSynchronizer] handles column type changes. */ enum class ColumnTypeChangeBehavior { /** @@ -86,6 +90,27 @@ class IcebergTableSynchronizer( return SchemaUpdateResult(existingSchema, pendingUpdates = emptyList()) } + // Update the sort order before creating the UpdateSchema, because: + // 1. Deleting a column referenced by the sort order will cause + // SortOrder.checkCompatibility to throw ValidationException on commit. + // 2. UpdateSchema captures the table's metadata version at creation time. + // If we replace the sort order after creating it, the commit would fail + // with a stale metadata error. + val columnsBeingDeleted = buildList { + addAll(diff.removedColumns) + if (columnTypeChangeBehavior == ColumnTypeChangeBehavior.OVERWRITE) { + // In OVERWRITE mode, type-changed columns are deleted and re-added + // with new field IDs. The old sort field references become invalid. + addAll(diff.updatedDataTypes) + } + } + replaceSortOrderIfNeeded( + table = table, + columnsBeingDeleted = columnsBeingDeleted, + identifierFieldsChanged = diff.identifierFieldsChanged, + incomingIdentifierFieldNames = incomingSchema.identifierFieldNames(), + ) + val update: UpdateSchema = table.updateSchema().allowIncompatibleChanges() // 1) Remove columns that no longer exist in the incoming schema @@ -267,6 +292,90 @@ class IcebergTableSynchronizer( return SchemaUpdateResult(newSchema, pendingUpdates = listOf(update)) } } + + /** + * Update the table's sort order if it would conflict with pending schema changes. + * + * Sort orders are set at table creation from identifier fields (PKs) and never updated. This + * causes [org.apache.iceberg.exceptions.ValidationException] when schema evolution deletes a + * column referenced by the sort order. + * + * This method handles three cases: + * 1. Identifier fields changed → rebuild sort order from new identifiers (covers + * ``` + * Dedupe→Append, PK changes within Dedupe) + * ``` + * 2. Columns being deleted conflict with sort order → remove those fields + * 3. Neither → no-op + * + * Must be called BEFORE creating the [UpdateSchema], since this commits a metadata change and + * the subsequent UpdateSchema needs the refreshed metadata version. + */ + private fun replaceSortOrderIfNeeded( + table: Table, + columnsBeingDeleted: List, + identifierFieldsChanged: Boolean, + incomingIdentifierFieldNames: Set, + ) { + val currentSortOrder = table.sortOrder() + + // If the table has no sort order, there's nothing to conflict and nothing to update. + // (Append→Dedupe would need a sort order added, but that case requires a reset.) + if (currentSortOrder.isUnsorted) { + return + } + + if (identifierFieldsChanged) { + // Rebuild sort order from the new identifier fields. + // For Dedupe→Append: incoming identifiers are empty → unsorted. + // For PK changes within Dedupe: new identifiers → new sort order. + val builder = table.replaceSortOrder() + for (fieldName in incomingIdentifierFieldNames) { + // Only include fields that exist in the current schema. Fields being + // added in the same schema change can't be referenced yet. + if (table.schema().findField(fieldName) != null) { + builder.asc(fieldName) + } + } + logger.info { + "Replacing sort order due to identifier field change. " + + "New sort fields: ${incomingIdentifierFieldNames.ifEmpty { setOf("(unsorted)") }}" + } + builder.commit() + table.refresh() + return + } + + // No identifier change — check if any deleted columns conflict with the sort order. + if (columnsBeingDeleted.isEmpty()) { + return + } + + val schema = table.schema() + val fieldIdsBeingDeleted = + columnsBeingDeleted.mapNotNull { schema.findField(it)?.fieldId() }.toSet() + + val hasConflict = currentSortOrder.fields().any { it.sourceId() in fieldIdsBeingDeleted } + if (!hasConflict) { + return + } + + // Rebuild the sort order, keeping only fields that aren't being deleted. + val builder = table.replaceSortOrder() + for (sortField in currentSortOrder.fields()) { + if (sortField.sourceId() !in fieldIdsBeingDeleted) { + val fieldName = schema.findColumnName(sortField.sourceId()) + when (sortField.direction()) { + SortDirection.ASC -> builder.asc(fieldName, sortField.nullOrder()) + SortDirection.DESC -> builder.desc(fieldName, sortField.nullOrder()) + else -> builder.asc(fieldName, sortField.nullOrder()) + } + } + } + logger.info { "Replacing sort order to remove fields being deleted: $columnsBeingDeleted" } + builder.commit() + table.refresh() + } } data class SchemaUpdateResult(val schema: Schema, val pendingUpdates: List) diff --git a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/test/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizerTest.kt b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/test/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizerTest.kt index fe39cb577ee3..4d1ccecfdd84 100644 --- a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/test/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizerTest.kt +++ b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/test/kotlin/io/airbyte/cdk/load/toolkits/iceberg/parquet/IcebergTableSynchronizerTest.kt @@ -12,9 +12,16 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.spyk import io.mockk.verify +import java.nio.file.Files +import org.apache.hadoop.conf.Configuration +import org.apache.iceberg.FileFormat import org.apache.iceberg.Schema +import org.apache.iceberg.SortOrder import org.apache.iceberg.Table +import org.apache.iceberg.TableProperties.DEFAULT_FILE_FORMAT import org.apache.iceberg.UpdateSchema +import org.apache.iceberg.catalog.TableIdentifier +import org.apache.iceberg.hadoop.HadoopCatalog import org.apache.iceberg.types.Type.PrimitiveType import org.apache.iceberg.types.Types import org.assertj.core.api.Assertions.assertThat @@ -50,6 +57,9 @@ class IcebergTableSynchronizerTest { // By default, let table.schema() return an empty schema. Tests can override this as needed. every { mockTable.schema() } returns Schema(listOf()) + // By default, the table has no sort order. Tests using real tables handle this themselves. + every { mockTable.sortOrder() } returns SortOrder.unsorted() + // Table.updateSchema() should return the mock UpdateSchema every { mockTable.updateSchema().allowIncompatibleChanges() } returns mockUpdateSchema @@ -473,4 +483,275 @@ class IcebergTableSynchronizerTest { assertThat(pendingUpdates).hasSize(1) assertThat(pendingUpdates.first()).isSameAs(mockIdentifierUpdateSchema) } + + // ================================================================================== + // Sort order tests — use real HadoopCatalog tables (not mocks) because mocked tests + // never exercise Iceberg's SortOrder.checkCompatibility validation. + // ================================================================================== + + private fun createRealSynchronizer() = + IcebergTableSynchronizer( + IcebergTypesComparator(), + IcebergSuperTypeFinder(IcebergTypesComparator()), + ) + + /** Schema matching the customer's custom insight streams (Dedupe with 3 PKs). */ + private fun dedupeSchema() = + Schema( + listOf( + Types.NestedField.required(1, "_airbyte_raw_id", Types.StringType.get()), + Types.NestedField.required( + 2, + "_airbyte_extracted_at", + Types.TimestampType.withZone() + ), + Types.NestedField.required(3, "_airbyte_meta", Types.StringType.get()), + Types.NestedField.required(4, "_airbyte_generation_id", Types.LongType.get()), + Types.NestedField.required(5, "ad_id", Types.StringType.get()), + Types.NestedField.required(6, "date_start", Types.DateType.get()), + Types.NestedField.required(7, "account_id", Types.StringType.get()), + Types.NestedField.optional(8, "impressions", Types.LongType.get()), + ), + setOf(5, 6, 7), // identifier fields: ad_id, date_start, account_id + ) + + private fun dedupeSortOrder(schema: Schema): SortOrder = + SortOrder.builderFor(schema).asc("ad_id").asc("date_start").asc("account_id").build() + + private fun createTableWithSortOrder( + catalog: HadoopCatalog, + tableId: TableIdentifier, + schema: Schema, + sortOrder: SortOrder, + ): Table { + catalog.createNamespace(tableId.namespace()) + return catalog + .buildTable(tableId, schema) + .withProperty(DEFAULT_FILE_FORMAT, FileFormat.PARQUET.name.lowercase()) + .withSortOrder(sortOrder) + .create() + } + + private fun createTableWithoutSortOrder( + catalog: HadoopCatalog, + tableId: TableIdentifier, + schema: Schema, + ): Table { + catalog.createNamespace(tableId.namespace()) + return catalog + .buildTable(tableId, schema) + .withProperty(DEFAULT_FILE_FORMAT, FileFormat.PARQUET.name.lowercase()) + .create() + } + + /** + * Scenario A: Source upgrade removes a PK column (ad_id) that the sort order references. After + * fix: schema evolution should succeed and sort order should retain only the remaining PK + * columns. + */ + @Test + fun `scenario A - removing a sort-order column should update sort order and succeed`() { + val warehousePath = Files.createTempDirectory("iceberg-test-warehouse") + val catalog = HadoopCatalog(Configuration(), warehousePath.toString()) + val tableId = TableIdentifier.of("db", "scenario_a") + + val schema = dedupeSchema() + val table = createTableWithSortOrder(catalog, tableId, schema, dedupeSortOrder(schema)) + + // Incoming schema: ad_id removed, identifiers updated + val incomingSchema = + Schema( + listOf( + Types.NestedField.required(1, "_airbyte_raw_id", Types.StringType.get()), + Types.NestedField.required( + 2, + "_airbyte_extracted_at", + Types.TimestampType.withZone() + ), + Types.NestedField.required(3, "_airbyte_meta", Types.StringType.get()), + Types.NestedField.required(4, "_airbyte_generation_id", Types.LongType.get()), + // ad_id (field 5) removed + Types.NestedField.required(6, "date_start", Types.DateType.get()), + Types.NestedField.required(7, "account_id", Types.StringType.get()), + Types.NestedField.optional(8, "impressions", Types.LongType.get()), + ), + setOf(6, 7), // identifier fields: date_start, account_id + ) + + val synchronizer = createRealSynchronizer() + synchronizer.maybeApplySchemaChanges( + table, + incomingSchema, + ColumnTypeChangeBehavior.SAFE_SUPERTYPE, + ) + + // ad_id should be gone from the schema + table.refresh() + assertThat(table.schema().findField("ad_id")).isNull() + + // Sort order should have 2 remaining fields (date_start, account_id) + val updatedSortOrder = table.sortOrder() + assertThat(updatedSortOrder.fields()).hasSize(2) + assertThat(updatedSortOrder.fields().map { table.schema().findColumnName(it.sourceId()) }) + .containsExactly("date_start", "account_id") + + catalog.dropTable(tableId) + warehousePath.toFile().deleteRecursively() + } + + /** + * Scenario B: Stream switches from Dedupe to Append. Incoming schema has no identifier fields. + * The sort order should be cleared to unsorted. + */ + @Test + fun `scenario B - dedupe to append should clear sort order`() { + val warehousePath = Files.createTempDirectory("iceberg-test-warehouse") + val catalog = HadoopCatalog(Configuration(), warehousePath.toString()) + val tableId = TableIdentifier.of("db", "scenario_b") + + val schema = dedupeSchema() + val table = createTableWithSortOrder(catalog, tableId, schema, dedupeSortOrder(schema)) + + // Incoming schema: same columns but NO identifier fields (Append mode) + val incomingSchema = + Schema( + listOf( + Types.NestedField.required(1, "_airbyte_raw_id", Types.StringType.get()), + Types.NestedField.required( + 2, + "_airbyte_extracted_at", + Types.TimestampType.withZone() + ), + Types.NestedField.required(3, "_airbyte_meta", Types.StringType.get()), + Types.NestedField.required(4, "_airbyte_generation_id", Types.LongType.get()), + Types.NestedField.optional(5, "ad_id", Types.StringType.get()), + Types.NestedField.optional(6, "date_start", Types.DateType.get()), + Types.NestedField.optional(7, "account_id", Types.StringType.get()), + Types.NestedField.optional(8, "impressions", Types.LongType.get()), + ), + // No identifier fields — Append mode + ) + + val synchronizer = createRealSynchronizer() + synchronizer.maybeApplySchemaChanges( + table, + incomingSchema, + ColumnTypeChangeBehavior.SAFE_SUPERTYPE, + ) + + table.refresh() + assertThat(table.sortOrder().isUnsorted).isTrue() + + catalog.dropTable(tableId) + warehousePath.toFile().deleteRecursively() + } + + /** + * Scenario D (with column deletion): PKs change within Dedupe and the old PK column is also + * removed from the schema. Sort order should be updated to match new PKs, and the column + * deletion should succeed. + * + * This is the exact customer scenario: ad_id removed from both PKs and schema. + */ + @Test + fun `scenario D - pk change with column deletion should update sort order`() { + val warehousePath = Files.createTempDirectory("iceberg-test-warehouse") + val catalog = HadoopCatalog(Configuration(), warehousePath.toString()) + val tableId = TableIdentifier.of("db", "scenario_d_with_delete") + + val schema = dedupeSchema() + val table = createTableWithSortOrder(catalog, tableId, schema, dedupeSortOrder(schema)) + + // Incoming schema: ad_id removed from both columns and identifiers + val incomingSchema = + Schema( + listOf( + Types.NestedField.required(1, "_airbyte_raw_id", Types.StringType.get()), + Types.NestedField.required( + 2, + "_airbyte_extracted_at", + Types.TimestampType.withZone() + ), + Types.NestedField.required(3, "_airbyte_meta", Types.StringType.get()), + Types.NestedField.required(4, "_airbyte_generation_id", Types.LongType.get()), + // ad_id removed + Types.NestedField.required(6, "date_start", Types.DateType.get()), + Types.NestedField.required(7, "account_id", Types.StringType.get()), + Types.NestedField.optional(8, "impressions", Types.LongType.get()), + ), + setOf(6, 7), // identifier fields: date_start, account_id + ) + + val synchronizer = createRealSynchronizer() + synchronizer.maybeApplySchemaChanges( + table, + incomingSchema, + ColumnTypeChangeBehavior.SAFE_SUPERTYPE, + ) + + table.refresh() + assertThat(table.schema().findField("ad_id")).isNull() + + val updatedSortOrder = table.sortOrder() + assertThat(updatedSortOrder.fields()).hasSize(2) + assertThat(updatedSortOrder.fields().map { table.schema().findColumnName(it.sourceId()) }) + .containsExactly("date_start", "account_id") + + catalog.dropTable(tableId) + warehousePath.toFile().deleteRecursively() + } + + /** + * Scenario D (standalone): PKs change within Dedupe but the old PK column remains in the schema + * as a non-PK column. Sort order should be updated to match new PKs. + */ + @Test + fun `scenario D - pk change without column deletion should update sort order`() { + val warehousePath = Files.createTempDirectory("iceberg-test-warehouse") + val catalog = HadoopCatalog(Configuration(), warehousePath.toString()) + val tableId = TableIdentifier.of("db", "scenario_d_standalone") + + val schema = dedupeSchema() + val table = createTableWithSortOrder(catalog, tableId, schema, dedupeSortOrder(schema)) + + // Incoming schema: ad_id still present but no longer an identifier + val incomingSchema = + Schema( + listOf( + Types.NestedField.required(1, "_airbyte_raw_id", Types.StringType.get()), + Types.NestedField.required( + 2, + "_airbyte_extracted_at", + Types.TimestampType.withZone() + ), + Types.NestedField.required(3, "_airbyte_meta", Types.StringType.get()), + Types.NestedField.required(4, "_airbyte_generation_id", Types.LongType.get()), + Types.NestedField.optional(5, "ad_id", Types.StringType.get()), + Types.NestedField.required(6, "date_start", Types.DateType.get()), + Types.NestedField.required(7, "account_id", Types.StringType.get()), + Types.NestedField.optional(8, "impressions", Types.LongType.get()), + ), + setOf(6, 7), // identifier fields: date_start, account_id (ad_id removed from PKs) + ) + + val synchronizer = createRealSynchronizer() + synchronizer.maybeApplySchemaChanges( + table, + incomingSchema, + ColumnTypeChangeBehavior.SAFE_SUPERTYPE, + ) + + table.refresh() + // ad_id should still exist as a column + assertThat(table.schema().findField("ad_id")).isNotNull() + + // Sort order should reflect new PKs only + val updatedSortOrder = table.sortOrder() + assertThat(updatedSortOrder.fields()).hasSize(2) + assertThat(updatedSortOrder.fields().map { table.schema().findColumnName(it.sourceId()) }) + .containsExactly("date_start", "account_id") + + catalog.dropTable(tableId) + warehousePath.toFile().deleteRecursively() + } } diff --git a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/connector/IntegrationTestOperations.kt b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/connector/IntegrationTestOperations.kt index 654206de3263..b8e3172f460e 100644 --- a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/connector/IntegrationTestOperations.kt +++ b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/connector/IntegrationTestOperations.kt @@ -26,10 +26,17 @@ class IntegrationTestOperations( return streams } + @Deprecated("Use the correctly named 'read' function") fun sync( catalog: ConfiguredAirbyteCatalog, state: List = listOf(), vararg featureFlags: FeatureFlag + ): BufferingOutputConsumer = read(catalog, state, *featureFlags) + + fun read( + catalog: ConfiguredAirbyteCatalog, + state: List = listOf(), + vararg featureFlags: FeatureFlag ): BufferingOutputConsumer { return CliRunner.source("read", configSpec, catalog, state, *featureFlags).run() } diff --git a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/BaseConnectorTest.kt b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/BaseConnectorTest.kt index 10fd375eed80..55b5a72e28f2 100644 --- a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/BaseConnectorTest.kt +++ b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/BaseConnectorTest.kt @@ -8,7 +8,7 @@ import io.airbyte.cdk.command.ConfigurationSpecification import io.airbyte.cdk.command.SourceConfiguration import io.airbyte.cdk.command.SourceConfigurationFactory import io.airbyte.cdk.discover.DiscoveredStream -import io.airbyte.cdk.discover.Field +import io.airbyte.cdk.discover.EmittedField import io.airbyte.cdk.discover.JdbcAirbyteStreamFactory import io.airbyte.cdk.jdbc.BigIntegerFieldType import io.airbyte.cdk.jdbc.LocalDateTimeFieldType @@ -151,7 +151,7 @@ abstract class BaseConnectorTest( val discoveredStream = DiscoveredStream( id = StreamIdentifier.from(desc), - columns = table.columns.map { Field(it.name, it.jdbcType) }, + columns = table.columns.map { EmittedField(it.name, it.jdbcType) }, primaryKeyColumnIDs = table.columns .filter { it.isPrimaryKey } diff --git a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/CursorBasedSyncTest.kt b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/CursorBasedSyncTest.kt new file mode 100644 index 000000000000..6eac537e9d9b --- /dev/null +++ b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/CursorBasedSyncTest.kt @@ -0,0 +1,233 @@ +/* Copyright (c) 2026 Airbyte, Inc., all rights reserved. */ +package io.airbyte.cdk.test.fixtures.tests + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.ObjectNode +import io.airbyte.cdk.ClockFactory +import io.airbyte.cdk.command.ConfigurationSpecification +import io.airbyte.cdk.output.BufferingOutputConsumer +import io.airbyte.cdk.test.fixtures.cleanup.TestAssetResourceNamer +import io.airbyte.cdk.test.fixtures.connector.IntegrationTestOperations +import io.airbyte.cdk.test.fixtures.connector.TestDbExecutor +import io.airbyte.cdk.util.Jsons +import io.airbyte.protocol.models.v0.AirbyteMessage +import io.airbyte.protocol.models.v0.AirbyteRecordMessage +import io.airbyte.protocol.models.v0.AirbyteStateMessage +import io.airbyte.protocol.models.v0.AirbyteStream +import io.airbyte.protocol.models.v0.AirbyteTraceMessage +import io.airbyte.protocol.models.v0.CatalogHelpers +import io.airbyte.protocol.models.v0.ConfiguredAirbyteCatalog +import io.airbyte.protocol.models.v0.SyncMode +import io.github.oshai.kotlinlogging.KotlinLogging +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DynamicContainer +import org.junit.jupiter.api.DynamicNode +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.function.Executable + +abstract class CursorBasedSyncTest { + private val log = KotlinLogging.logger {} + + abstract val configSpec: ConfigurationSpecification + abstract val executor: TestDbExecutor + abstract val setupDdl: List + abstract val testCases: List + + @TestFactory + @Timeout(300) + fun tests(): Iterable { + log.info { "Executing setup DDL statements." } + for (stmt in setupDdl) { + executor.executeUpdate(stmt) + } + log.info { "Inserting initial rows." } + for (stmt in testCases.flatMap { it.initialDml }) { + executor.executeUpdate(stmt) + } + val actual = DiscoverAndSyncAll(IntegrationTestOperations(configSpec), executor, testCases) + val discoverAndSyncAllTest = DynamicTest.dynamicTest("discover-and-sync-all", actual) + val testCaseNodes = + testCases.map { + DynamicContainer.dynamicContainer(it.testName, dynamicTests(actual, it)) + } + return listOf(discoverAndSyncAllTest) + testCaseNodes + } + + private fun dynamicTests(actual: DiscoverAndSyncAll, testCase: TestCase): List { + return listOf( + DynamicTest.dynamicTest("sync1-records") { + assertRecords( + "sync1", + testCase, + actual.sync1MessagesByStream, + testCase.expectedSync1Values + ) + }, + DynamicTest.dynamicTest("sync2-records") { + assertRecords( + "sync2", + testCase, + actual.sync2MessagesByStream, + testCase.expectedSync2Values + ) + }, + ) + } + + private fun assertRecords( + syncLabel: String, + testCase: TestCase, + messagesByStream: Map, + expectedJsonValues: List, + ) { + val consumer = messagesByStream[testCase.tableName.uppercase()] + Assertions.assertNotNull(consumer) + val actualRecords = extractCursorFieldData(consumer!!.records(), testCase.cursorField) + val expectedRecords = + expectedJsonValues.map { Jsons.readTree("""{"${testCase.cursorField}":$it}""") } + log.info { + "$syncLabel ${testCase.testName}: emitted records ${Jsons.writeValueAsString(sortedRecords(actualRecords))}" + } + Assertions.assertEquals( + Jsons.writeValueAsString(sortedRecords(expectedRecords)), + Jsons.writeValueAsString(sortedRecords(actualRecords)), + ) + } + + private fun extractCursorFieldData( + records: List, + cursorField: String, + ): List = + records.mapNotNull { record -> + val data = record.data as? ObjectNode ?: return@mapNotNull null + data.deepCopy().apply { + for (fieldName in data.fieldNames()) { + if (fieldName != cursorField) remove(fieldName) + } + } + } + + private fun sortedRecords(records: List): JsonNode = + Jsons.createArrayNode().apply { addAll(records.sortedBy { it.toString() }) } + + class DiscoverAndSyncAll( + private val ops: IntegrationTestOperations, + private val executor: TestDbExecutor, + private val testCases: List, + ) : Executable { + private val log = KotlinLogging.logger {} + + lateinit var sync1MessagesByStream: Map + lateinit var sync2MessagesByStream: Map + + override fun execute() { + log.info { "Running JDBC DISCOVER operation." } + val streams = ops.discover() + val catalog = configuredCatalog(streams) + + log.info { "Running JDBC READ (sync 1)." } + val sync1Output = ops.read(catalog) + Assertions.assertNotEquals(emptyList(), sync1Output.states()) + Assertions.assertNotEquals(emptyList(), sync1Output.records()) + sync1MessagesByStream = byStream(sync1Output.messages()) + + log.info { "Inserting additional rows." } + for (stmt in testCases.flatMap { it.additionalDml }) { + executor.executeUpdate(stmt) + } + + log.info { "Running JDBC READ (sync 2)." } + val sync2Output = ops.read(catalog, sync1Output.states()) + sync2MessagesByStream = byStream(sync2Output.messages()) + + log.info { "Done." } + } + + private fun configuredCatalog( + streams: Map + ): ConfiguredAirbyteCatalog { + val configuredStreams = + testCases.mapNotNull { testCase -> + streams[testCase.tableName]?.let { stream -> + CatalogHelpers.toDefaultConfiguredStream(stream) + .withSyncMode(SyncMode.INCREMENTAL) + .withCursorField(listOf(testCase.cursorField)) + } + } + return ConfiguredAirbyteCatalog().withStreams(configuredStreams) + } + + private fun byStream(messages: List): Map { + val result = + testCases.associate { + it.tableName.uppercase() to BufferingOutputConsumer(ClockFactory().fixed()) + } + for (msg in messages) { + result[streamName(msg) ?: continue]?.accept(msg) + } + return result + } + + private fun streamName(msg: AirbyteMessage): String? = + when (msg.type) { + AirbyteMessage.Type.RECORD -> msg.record?.stream + AirbyteMessage.Type.STATE -> msg.state?.stream?.streamDescriptor?.name + AirbyteMessage.Type.TRACE -> + when (msg.trace?.type) { + AirbyteTraceMessage.Type.ERROR -> msg.trace?.error?.streamDescriptor?.name + AirbyteTraceMessage.Type.ESTIMATE -> msg.trace?.estimate?.name + AirbyteTraceMessage.Type.STREAM_STATUS -> + msg.trace?.streamStatus?.streamDescriptor?.name + AirbyteTraceMessage.Type.ANALYTICS -> null + null -> null + } + else -> null + } + } + + data class TestCase( + val namespace: String, + val sqlType: String, + val cursorField: String, + // SQL literal → expected JSON output, inserted before sync 1; all must appear in sync 1 + val initialRows: Map, + // SQL literal → expected JSON output, inserted before sync 1 at the max cursor value; + // must appear in both sync 1 and sync 2 due to the >= lower bound used by the bulk CDK + val boundaryRows: Map = emptyMap(), + // SQL literal → expected JSON output, inserted between syncs; all must appear in sync 2 + val additionalRows: Map, + val testName: String = + sqlType + .replace("\\[]".toRegex(), " array") + .replace("[^a-zA-Z0-9]".toRegex(), " ") + .trim() + .replace(" +".toRegex(), "_") + .uppercase(), + ) { + companion object { + val testAssetResourceNamer = TestAssetResourceNamer() + } + + val tableName: String = testAssetResourceNamer.getName() + + val initialDml: List + get() = + (initialRows.keys + boundaryRows.keys).map { + "INSERT INTO \"$namespace\".\"$tableName\" (\"$cursorField\") VALUES ($it)" + } + + val additionalDml: List + get() = + additionalRows.keys.map { + "INSERT INTO \"$namespace\".\"$tableName\" (\"$cursorField\") VALUES ($it)" + } + + val expectedSync1Values: List + get() = initialRows.values.toList() + boundaryRows.values.toList() + + val expectedSync2Values: List + get() = boundaryRows.values.toList() + additionalRows.values.toList() + } +} diff --git a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/FieldTypeMapperTest.kt b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/FieldTypeMapperTest.kt index ea5dca34c934..4b13872cecc1 100644 --- a/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/FieldTypeMapperTest.kt +++ b/airbyte-cdk/bulk/toolkits/source-tests/src/testFixtures/kotlin/io/airbyte/cdk/test/fixtures/tests/FieldTypeMapperTest.kt @@ -9,6 +9,7 @@ import io.airbyte.cdk.data.AirbyteSchemaType import io.airbyte.cdk.data.LeafAirbyteSchemaType import io.airbyte.cdk.discover.MetaField import io.airbyte.cdk.output.BufferingOutputConsumer +import io.airbyte.cdk.test.fixtures.cleanup.TestAssetResourceNamer import io.airbyte.cdk.test.fixtures.connector.IntegrationTestOperations import io.airbyte.cdk.test.fixtures.connector.TestDbExecutor import io.airbyte.cdk.util.Jsons @@ -47,10 +48,6 @@ abstract class FieldTypeMapperTest { return testCases.find { streamName.uppercase() in it.streamNamesToRecordData.keys } } - companion object { - lateinit var ops: IntegrationTestOperations - } - @TestFactory @Timeout(300) fun tests(): Iterable { @@ -67,8 +64,8 @@ abstract class FieldTypeMapperTest { val discoverAndReadAllTest: DynamicNode = DynamicTest.dynamicTest("discover-and-read-all", actual) val testCases: List = - allStreamNamesAndRecordData.keys.map { streamName: String -> - DynamicContainer.dynamicContainer(streamName, dynamicTests(actual, streamName)) + testCases.map { + DynamicContainer.dynamicContainer(it.testName, dynamicTests(actual, it.tableName)) } return listOf(discoverAndReadAllTest) + testCases } @@ -95,7 +92,7 @@ abstract class FieldTypeMapperTest { } val testCase: TestCase = findTestCase(streamName)!! val jsonSchema: JsonNode = actualStream!!.jsonSchema?.get("properties")!! - val actualSchema: JsonNode? = jsonSchema[testCase.columnName.uppercase()] + val actualSchema: JsonNode? = jsonSchema[testCase.columnName] Assertions.assertNotNull(actualSchema) val expectedSchema: JsonNode = testCase.airbyteSchemaType.asJsonSchema() Assertions.assertEquals(expectedSchema, actualSchema) @@ -149,7 +146,7 @@ abstract class FieldTypeMapperTest { jdbcStreams = ops.discover() jdbcConfiguredCatalog = configuredCatalog(jdbcStreams) log.info { "Running JDBC READ operation." } - jdbcReadOutput = ops.sync(jdbcConfiguredCatalog) + jdbcReadOutput = ops.read(jdbcConfiguredCatalog) Assertions.assertNotEquals(emptyList(), jdbcReadOutput.states()) Assertions.assertNotEquals(emptyList(), jdbcReadOutput.records()) Assertions.assertEquals(emptyList(), jdbcReadOutput.logs()) @@ -213,25 +210,26 @@ abstract class FieldTypeMapperTest { val sqlType: String, val values: Map, val airbyteSchemaType: AirbyteSchemaType = LeafAirbyteSchemaType.STRING, + val testName: String = + sqlType + .replace("\\[]".toRegex(), "_array") + .replace("[^a-zA-Z0-9]".toRegex(), " ") + .trim() + .replace(" +".toRegex(), "_") + .uppercase() ) { - val id: String - get() = - sqlType - .replace("[^a-zA-Z0-9]".toRegex(), " ") - .trim() - .replace(" +".toRegex(), "_") - .lowercase() + companion object { + val testAssetResourceNamer = TestAssetResourceNamer() + } - val tableName: String - get() = "tbl_$id" + val tableName = testAssetResourceNamer.getName() - val columnName: String - get() = "col_$id" + val columnName = "TYPE_COL" val dml: List get() { return values.keys.map { - "INSERT INTO $namespace.$tableName ($columnName) VALUES ($it)" + "INSERT INTO \"$namespace\".\"$tableName\" (\"$columnName\") VALUES ($it)" } } @@ -239,10 +237,157 @@ abstract class FieldTypeMapperTest { get() { return mapOf( tableName.uppercase() to - values.values.map { - Jsons.readTree("""{"${columnName.uppercase()}":$it}""") - } + values.values.map { Jsons.readTree("""{"${columnName}":$it}""") } ) } } + + // pads the json string map values to a fixed length + protected fun Map.withLength(length: Int): Map { + return this.mapValues { + val currentLength = it.value.length - 2 // exclude the quotes + if (currentLength > length) { + throw IllegalArgumentException("$length is out of bounds") + } else { + // make it longer + it.value.replace("\"$".toRegex(), "\"".padStart(length - currentLength + 1)) + } + } + } +} + +object AnsiSql { + + val intValues = + mapOf( + "null" to "null", + "1" to "1", + "0" to "0", + "-1" to "-1", + "2147483647" to "2147483647", + "-2147483648" to "-2147483648", + ) + + val smallIntValues = + mapOf( + "null" to "null", + "1" to "1", + "0" to "0", + "-1" to "-1", + "32767" to "32767", + "-32768" to "-32768", + ) + + val bigIntValues = + mapOf( + "null" to "null", + "1" to "1", + "0" to "0", + "-1" to "-1", + "9223372036854775807" to "9223372036854775807", + "-9223372036854775808" to "-9223372036854775808", + ) + + val decimalValues = + mapOf( + "null" to "null", + "123456789.123456789" to "123456789.123456789", + "-123456789.123456789" to "-123456789.123456789", + "0.000000001" to "0.000000001", + "9999999999.999999999" to "9999999999.999999999", + "-9999999999.999999999" to "-9999999999.999999999", + ) + + val realValues = + mapOf( + "null" to "null", + "3.402E+38" to "3.402E+38", + "-3.402E+38" to "-3.402E+38", + "1.175E-37" to "1.175E-37", + "0.0" to "0.0", + ) + + val doubleValues = + mapOf( + "null" to "null", + "1.7976931348623157E+308" to "1.7976931348623157E+308", + "-1.7976931348623157E+308" to "-1.7976931348623157E+308", + "2.2250738585072014E-308" to "2.2250738585072014E-308", + "0.0" to "0.0", + ) + + val booleanValues = + mapOf( + "null" to "null", + "true" to "true", + "false" to "false", + ) + + val charValues = + mapOf( + "null" to "null", + "'a'" to "\"a\"", + "'Z'" to "\"Z\"", + "'1'" to "\"1\"", + "' '" to "\" \"", + ) + + val varcharValues = + mapOf( + "null" to "null", + "'Hello'" to "\"Hello\"", + "'12345'" to "\"12345\"", + "' '" to "\" \"", + "''" to "\"\"", + ) + + val dateValues = + mapOf( + "null" to "null", + "'1000-01-01'" to "\"1000-01-01\"", + "'9999-12-31'" to "\"9999-12-31\"", + ) + + val timeValues = + mapOf( + "null" to "null", + "'00:00:00'" to "\"00:00:00.000000\"", + "'23:59:59'" to "\"23:59:59.000000\"", + ) + + val timeTzValues = + mapOf( + "null" to "null", + "'00:00:00+00'" to "\"00:00:00.000000Z\"", + "'23:59:59+00'" to "\"23:59:59.000000Z\"", + ) + + val timestampValues = + mapOf( + "null" to "null", + "'1000-01-01 00:00:00'" to "\"1000-01-01T00:00:00.000000\"", + "'9999-12-31 23:59:59'" to "\"9999-12-31T23:59:59.000000\"", + ) + + val timestampWithTzValues = + mapOf( + "null" to "null", + "'1000-01-01 00:00:00'" to "\"1000-01-01T00:00:00.000000Z\"", + "'9999-12-31 23:59:59'" to "\"9999-12-31T23:59:59.000000Z\"", + ) +} + +object ExtendedSql { + val xmlValues = + mapOf( + "null" to "null", + "'value'" to "\"value\"" + ) + + val jsonValues = + mapOf( + "'{}'" to "\"{}\"", + "'{\"k\": null}'" to "\"{\\\"k\\\": null}\"", + "'{\"k\": \"v\"}'" to "\"{\\\"k\\\": \\\"v\\\"}\"" + ) } diff --git a/airbyte-ci/.gitignore b/airbyte-ci/.gitignore deleted file mode 100644 index b33a803302af..000000000000 --- a/airbyte-ci/.gitignore +++ /dev/null @@ -1,172 +0,0 @@ -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -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/ -cover/ - -# 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 -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .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 - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__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/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -### Python Patch ### -# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration -poetry.toml - -# ruff -.ruff_cache/ - -# LSP config files -pyrightconfig.json - -tmp* diff --git a/airbyte-ci/.python-version b/airbyte-ci/.python-version deleted file mode 100644 index 2c0733315e41..000000000000 --- a/airbyte-ci/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11 diff --git a/airbyte-ci/README.md b/airbyte-ci/README.md deleted file mode 100644 index 8e5c231603cb..000000000000 --- a/airbyte-ci/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Airbyte CI - -This folder is a collection of systems, tools and scripts that are used to run Airbyte's CI/CD - -The installation instructions for the `airbyte-ci` CLI tool cal be found here -[airbyte-ci/connectors/pipelines](connectors/pipelines/README.md) - -## Tools - -| Directory | Description | -| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| [`ci_credentials`](connectors/ci_credentials) | A CLI tool to fetch connector secrets from GCP Secrets Manager. | -| [`connector_ops`](connectors/connector_ops) | A python package with utils reused in internal packages. | -| [`connectors_qa`](connectors/connectors_qa/) | A tool to verify connectors have sounds assets and metadata. | -| [`metadata_service`](connectors/metadata_service/) | Tools to generate connector metadata and registry. | -| [`pipelines`](connectors/pipelines/) | Airbyte CI pipelines, including formatting, linting, building, testing connectors, etc. Connector acceptance tests live here. | -| [`auto_merge`](connectors/auto_merge/) | A tool to automatically merge connector pull requests. | diff --git a/airbyte-ci/connectors/README.md b/airbyte-ci/connectors/README.md deleted file mode 100644 index 0af1e828f1b8..000000000000 --- a/airbyte-ci/connectors/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Airbyte Connectors CI - -This folder is a collection of systems, tools and scripts that are used to run CI/CD systems -specific to our connectors. - -For the list of tools and subfolders, please see [README in `airbyte-ci`](../README.md). diff --git a/airbyte-ci/connectors/auto_merge/README.md b/airbyte-ci/connectors/auto_merge/README.md deleted file mode 100644 index 12c280908a80..000000000000 --- a/airbyte-ci/connectors/auto_merge/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# `Auto merge` - -## Purpose - -This Python package is made to merge pull requests automatically on the Airbyte Repo. It is used in -the [following workflow](.github/workflows/auto_merge.yml). - -A pull request is currently considered as auto-mergeable if: - -- It has the `auto-merge` Github label -- It only modifies files in connector-related directories -- All the required checks have passed - -We want to auto-merge a specific set of connector pull requests to simplify the connector updates in -the following use cases: - -- Pull requests updating Python dependencies or the connector base image -- Community contributions when they've been reviewed and approved by our team but CI is still - running: to avoid an extra review iteration just to check CI status. - -## Install and usage - -### Get a Github token - -You need to create a Github token with the following permissions: - -- Read access to the repository to list open pull requests and their statuses -- Write access to the repository to merge pull requests - -### Local install and run - -``` -poetry install -export GITHUB_TOKEN= -# By default no merge will be done, you need to set the AUTO_MERGE_PRODUCTION environment variable to true to actually merge the PRs -poetry run auto-merge -``` - -### In CI - -``` -export GITHUB_TOKEN= -export AUTO_MERGE_PRODUCTION=true -poetry install -poetry run auto-merge -``` - -The execution will set the `GITHUB_STEP_SUMMARY` env var with a markdown summary of the PRs that -have been merged. - -## Changelog - -### 0.1.5 -Update Python version requirement from 3.10 to 3.11. - -### 0.1.3 -Adds `auto-merge/bypass-ci-checks` label which does not require CI checks to pass to auto-merge PR. - -### 0.1.2 -Set merge method to `squash`. - -### 0.1.1 -Consider skipped check runs as successful. - -### 0.1.0 -Initial release. diff --git a/airbyte-ci/connectors/auto_merge/poetry.lock b/airbyte-ci/connectors/auto_merge/poetry.lock deleted file mode 100644 index 0483a94d6505..000000000000 --- a/airbyte-ci/connectors/auto_merge/poetry.lock +++ /dev/null @@ -1,765 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "anyio" -version = "4.3.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, - {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -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"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "cryptography" -version = "42.0.7" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -groups = ["main"] -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"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "mypy" -version = "1.10.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -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"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pygithub" -version = "2.3.0" -description = "Use the full Github API v3" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "PyGithub-2.3.0-py3-none-any.whl", hash = "sha256:65b499728be3ce7b0cd2cd760da3b32f0f4d7bc55e5e0677617f90f6564e793e"}, - {file = "PyGithub-2.3.0.tar.gz", hash = "sha256:0148d7347a1cdeed99af905077010aef81a4dad988b0ba51d4108bf66b443f7e"}, -] - -[package.dependencies] -Deprecated = "*" -pyjwt = {version = ">=2.4.0", extras = ["crypto"]} -pynacl = ">=1.4.0" -requests = ">=2.14.0" -typing-extensions = ">=4.0.0" -urllib3 = ">=1.26.0" - -[[package]] -name = "pyinstrument" -version = "4.6.2" -description = "Call stack profiler for Python. Shows you why your code is slow!" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a1b1cd768ea7ea9ab6f5490f7e74431321bcc463e9441dbc2f769617252d9e2"}, - {file = "pyinstrument-4.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8a386b9d09d167451fb2111eaf86aabf6e094fed42c15f62ec51d6980bce7d96"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23c3e3ca8553b9aac09bd978c73d21b9032c707ac6d803bae6a20ecc048df4a8"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f329f5534ca069420246f5ce57270d975229bcb92a3a3fd6b2ca086527d9764"}, - {file = "pyinstrument-4.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dcdcc7ba224a0c5edfbd00b0f530f5aed2b26da5aaa2f9af5519d4aa8c7e41"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73db0c2c99119c65b075feee76e903b4ed82e59440fe8b5724acf5c7cb24721f"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:da58f265326f3cf3975366ccb8b39014f1e69ff8327958a089858d71c633d654"}, - {file = "pyinstrument-4.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:feebcf860f955401df30d029ec8de7a0c5515d24ea809736430fd1219686fe14"}, - {file = "pyinstrument-4.6.2-cp310-cp310-win32.whl", hash = "sha256:b2b66ff0b16c8ecf1ec22de001cfff46872b2c163c62429055105564eef50b2e"}, - {file = "pyinstrument-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8d104b7a7899d5fa4c5bf1ceb0c1a070615a72c5dc17bc321b612467ad5c5d88"}, - {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:62f6014d2b928b181a52483e7c7b82f2c27e22c577417d1681153e5518f03317"}, - {file = "pyinstrument-4.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5c8d763c5df55131670ba2a01a8aebd0d490a789904a55eb6a8b8d497f110"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed4e8c6c84e0e6429ba7008a66e435ede2d8cb027794c20923c55669d9c5633"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c0f0e1d8f8c70faa90ff57f78ac0dda774b52ea0bfb2d9f0f41ce6f3e7c869e"}, - {file = "pyinstrument-4.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3c44cb037ad0d6e9d9a48c14d856254ada641fbd0ae9de40da045fc2226a2a"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:be9901f17ac2f527c352f2fdca3d717c1d7f2ce8a70bad5a490fc8cc5d2a6007"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8a9791bf8916c1cf439c202fded32de93354b0f57328f303d71950b0027c7811"}, - {file = "pyinstrument-4.6.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d6162615e783c59e36f2d7caf903a7e3ecb6b32d4a4ae8907f2760b2ef395bf6"}, - {file = "pyinstrument-4.6.2-cp311-cp311-win32.whl", hash = "sha256:28af084aa84bbfd3620ebe71d5f9a0deca4451267f363738ca824f733de55056"}, - {file = "pyinstrument-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:dd6007d3c2e318e09e582435dd8d111cccf30d342af66886b783208813caf3d7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3813c8ecfab9d7d855c5f0f71f11793cf1507f40401aa33575c7fd613577c23"}, - {file = "pyinstrument-4.6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c761372945e60fc1396b7a49f30592e8474e70a558f1a87346d27c8c4ce50f7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fba3244e94c117bf4d9b30b8852bbdcd510e7329fdd5c7c8b3799e00a9215a8"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:803ac64e526473d64283f504df3b0d5c2c203ea9603cab428641538ffdc753a7"}, - {file = "pyinstrument-4.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2e554b1bb0df78f5ce8a92df75b664912ca93aa94208386102af454ec31b647"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c671057fad22ee3ded897a6a361204ea2538e44c1233cad0e8e30f6d27f33db"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d02f31fa13a9e8dc702a113878419deba859563a32474c9f68e04619d43d6f01"}, - {file = "pyinstrument-4.6.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b55983a884f083f93f0fc6d12ff8df0acd1e2fb0580d2f4c7bfe6def33a84b58"}, - {file = "pyinstrument-4.6.2-cp312-cp312-win32.whl", hash = "sha256:fdc0a53b27e5d8e47147489c7dab596ddd1756b1e053217ef5bc6718567099ff"}, - {file = "pyinstrument-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:dd5c53a0159126b5ce7cbc4994433c9c671e057c85297ff32645166a06ad2c50"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b082df0bbf71251a7f4880a12ed28421dba84ea7110bb376e0533067a4eaff40"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90350533396071cb2543affe01e40bf534c35cb0d4b8fa9fdb0f052f9ca2cfe3"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67268bb0d579330cff40fd1c90b8510363ca1a0e7204225840614068658dab77"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20e15b4e1d29ba0b7fc81aac50351e0dc0d7e911e93771ebc3f408e864a2c93b"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e625fc6ffcd4fd420493edd8276179c3f784df207bef4c2192725c1b310534c"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:113d2fc534c9ca7b6b5661d6ada05515bf318f6eb34e8d05860fe49eb7cfe17e"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3098cd72b71a322a72dafeb4ba5c566465e193d2030adad4c09566bd2f89bf4f"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:08fdc7f88c989316fa47805234c37a40fafe7b614afd8ae863f0afa9d1707b37"}, - {file = "pyinstrument-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5ebeba952c0056dcc9b9355328c78c4b5c2a33b4b4276a9157a3ab589f3d1bac"}, - {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:34e59e91c88ec9ad5630c0964eca823949005e97736bfa838beb4789e94912a2"}, - {file = "pyinstrument-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd0320c39e99e3c0a3129d1ed010ac41e5a7eb96fb79900d270080a97962e995"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46992e855d630575ec635eeca0068a8ddf423d4fd32ea0875a94e9f8688f0b95"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e474c56da636253dfdca7cd1998b240d6b39f7ed34777362db69224fcf053b1"}, - {file = "pyinstrument-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b559322f30509ad8f082561792352d0805b3edfa508e492a36041fdc009259"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:06a8578b2943eb1dbbf281e1e59e44246acfefd79e1b06d4950f01b693de12af"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7bd3da31c46f1c1cb7ae89031725f6a1d1015c2041d9c753fe23980f5f9fd86c"}, - {file = "pyinstrument-4.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e63f4916001aa9c625976a50779282e0a5b5e9b17c52a50ef4c651e468ed5b88"}, - {file = "pyinstrument-4.6.2-cp38-cp38-win32.whl", hash = "sha256:32ec8db6896b94af790a530e1e0edad4d0f941a0ab8dd9073e5993e7ea46af7d"}, - {file = "pyinstrument-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a59fc4f7db738a094823afe6422509fa5816a7bf74e768ce5a7a2ddd91af40ac"}, - {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3a165e0d2deb212d4cf439383982a831682009e1b08733c568cac88c89784e62"}, - {file = "pyinstrument-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ba858b3d6f6e5597c641edcc0e7e464f85aba86d71bc3b3592cb89897bf43f6"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fd8e547cf3df5f0ec6e4dffbe2e857f6b28eda51b71c3c0b5a2fc0646527835"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de2c1714a37a820033b19cf134ead43299a02662f1379140974a9ab733c5f3a"}, - {file = "pyinstrument-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01fc45dedceec3df81668d702bca6d400d956c8b8494abc206638c167c78dfd9"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b6e161ef268d43ee6bbfae7fd2cdd0a52c099ddd21001c126ca1805dc906539"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6ba8e368d0421f15ba6366dfd60ec131c1b46505d021477e0f865d26cf35a605"}, - {file = "pyinstrument-4.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edca46f04a573ac2fb11a84b937844e6a109f38f80f4b422222fb5be8ecad8cb"}, - {file = "pyinstrument-4.6.2-cp39-cp39-win32.whl", hash = "sha256:baf375953b02fe94d00e716f060e60211ede73f49512b96687335f7071adb153"}, - {file = "pyinstrument-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:af1a953bce9fd530040895d01ff3de485e25e1576dccb014f76ba9131376fcad"}, - {file = "pyinstrument-4.6.2.tar.gz", hash = "sha256:0002ee517ed8502bbda6eb2bb1ba8f95a55492fcdf03811ba13d4806e50dd7f6"}, -] - -[package.extras] -bin = ["click", "nox"] -docs = ["furo (==2021.6.18b36)", "myst-parser (==0.15.1)", "sphinx (==4.2.0)", "sphinxcontrib-programoutput (==0.17)"] -examples = ["django", "numpy"] -test = ["flaky", "greenlet (>=3.0.0a1)", "ipython", "pytest", "pytest-asyncio (==0.12.0)", "sphinx-autobuild (==2021.3.14)", "trio"] -types = ["typing-extensions"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.dependencies] -cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} - -[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"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "pytest" -version = "8.2.0" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2.0" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "ruff" -version = "0.4.3" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.4.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b70800c290f14ae6fcbb41bbe201cf62dfca024d124a1f373e76371a007454ce"}, - {file = "ruff-0.4.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08a0d6a22918ab2552ace96adeaca308833873a4d7d1d587bb1d37bae8728eb3"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba1f14df3c758dd7de5b55fbae7e1c8af238597961e5fb628f3de446c3c40c5"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:819fb06d535cc76dfddbfe8d3068ff602ddeb40e3eacbc90e0d1272bb8d97113"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfc9e955e6dc6359eb6f82ea150c4f4e82b660e5b58d9a20a0e42ec3bb6342b"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:510a67d232d2ebe983fddea324dbf9d69b71c4d2dfeb8a862f4a127536dd4cfb"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9ff11cd9a092ee7680a56d21f302bdda14327772cd870d806610a3503d001f"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29efff25bf9ee685c2c8390563a5b5c006a3fee5230d28ea39f4f75f9d0b6f2f"}, - {file = "ruff-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b00e0bcccf0fc8d7186ed21e311dffd19761cb632241a6e4fe4477cc80ef6e"}, - {file = "ruff-0.4.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262f5635e2c74d80b7507fbc2fac28fe0d4fef26373bbc62039526f7722bca1b"}, - {file = "ruff-0.4.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7363691198719c26459e08cc17c6a3dac6f592e9ea3d2fa772f4e561b5fe82a3"}, - {file = "ruff-0.4.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eeb039f8428fcb6725bb63cbae92ad67b0559e68b5d80f840f11914afd8ddf7f"}, - {file = "ruff-0.4.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:927b11c1e4d0727ce1a729eace61cee88a334623ec424c0b1c8fe3e5f9d3c865"}, - {file = "ruff-0.4.3-py3-none-win32.whl", hash = "sha256:25cacda2155778beb0d064e0ec5a3944dcca9c12715f7c4634fd9d93ac33fd30"}, - {file = "ruff-0.4.3-py3-none-win_amd64.whl", hash = "sha256:7a1c3a450bc6539ef00da6c819fb1b76b6b065dec585f91456e7c0d6a0bbc725"}, - {file = "ruff-0.4.3-py3-none-win_arm64.whl", hash = "sha256:71ca5f8ccf1121b95a59649482470c5601c60a416bf189d553955b0338e34614"}, - {file = "ruff-0.4.3.tar.gz", hash = "sha256:ff0a3ef2e3c4b6d133fbedcf9586abfbe38d076041f2dc18ffb2c7e0485d5a07"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d"}, - {file = "types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.11.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -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 = "urllib3" -version = "2.2.1" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11" -content-hash = "1a6eba17c6fa90cca5ad3d50ad6b16b1794845be537b4b6f4f1e2dd335fec66a" diff --git a/airbyte-ci/connectors/auto_merge/pyproject.toml b/airbyte-ci/connectors/auto_merge/pyproject.toml deleted file mode 100644 index 9651ef70fdbc..000000000000 --- a/airbyte-ci/connectors/auto_merge/pyproject.toml +++ /dev/null @@ -1,47 +0,0 @@ -[tool.poetry] -name = "auto-merge" -version = "0.1.5" -description = "" -authors = ["Airbyte "] -readme = "README.md" -packages = [ - { include = "auto_merge", from = "src" }, -] - -[tool.poetry.dependencies] -python = "^3.11" -pygithub = "^2.3.0" -anyio = "^4.3.0" - - -[tool.poetry.group.dev.dependencies] -mypy = "^1.10.0" -ruff = "^0.4.3" -pytest = "^8.2.0" -pyinstrument = "^4.6.2" -types-requests = "^2.32.4.20260107" - -[tool.ruff] -line-length = 140 - -[tool.ruff.lint] -select = [ - "I" # isort -] - -[tool.poetry.scripts] -auto-merge = "auto_merge.main:auto_merge" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poe.tasks] -test = "pytest tests" -type_check = "mypy src --disallow-untyped-defs" -lint = "ruff check src" - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = ["type_check", "lint",] diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/__init__.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/consts.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/consts.py deleted file mode 100644 index cce847b02427..000000000000 --- a/airbyte-ci/connectors/auto_merge/src/auto_merge/consts.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -AIRBYTE_REPO = "airbytehq/airbyte" -AUTO_MERGE_LABEL = "auto-merge" -AUTO_MERGE_BYPASS_CI_CHECKS_LABEL = "auto-merge/bypass-ci-checks" -BASE_BRANCH = "master" -CONNECTOR_PATH_PREFIXES = { - "airbyte-integrations/connectors", - "docs/integrations/sources", - "docs/integrations/destinations", - "docs/ai-agents/connectors", - "docs/developers/pyairbyte", - "docusaurus/src/data", -} -MERGE_METHOD = "squash" diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/env.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/env.py deleted file mode 100644 index cfb82f108bee..000000000000 --- a/airbyte-ci/connectors/auto_merge/src/auto_merge/env.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import os - -GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] -PRODUCTION = os.environ.get("AUTO_MERGE_PRODUCTION", "false").lower() == "true" diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/helpers.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/helpers.py deleted file mode 100644 index ba56389226bf..000000000000 --- a/airbyte-ci/connectors/auto_merge/src/auto_merge/helpers.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import time -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from github.PullRequest import PullRequest - - -def generate_job_summary_as_markdown(merged_prs: list[PullRequest]) -> str: - """Generate a markdown summary of the merged PRs - - Args: - merged_prs (list[PullRequest]): The PRs that were merged - - Returns: - str: The markdown summary - """ - summary_time = time.strftime("%Y-%m-%d %H:%M:%S") - header = "# Auto-merged PRs" - details = f"Summary generated at {summary_time}" - if not merged_prs: - return f"{header}\n\n{details}\n\n**No PRs were auto-merged**\n" - merged_pr_list = "\n".join([f"- [#{pr.number} - {pr.title}]({pr.html_url})" for pr in merged_prs]) - return f"{header}\n\n{details}\n\n{merged_pr_list}\n" diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/main.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/main.py deleted file mode 100644 index 2ac5dd00cc95..000000000000 --- a/airbyte-ci/connectors/auto_merge/src/auto_merge/main.py +++ /dev/null @@ -1,252 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import logging -import os -import time -from collections.abc import Iterator -from contextlib import contextmanager -from pathlib import Path -from typing import TYPE_CHECKING, Callable, Optional - -import requests -from github import Auth, Github - -from .consts import ( - AIRBYTE_REPO, - AUTO_MERGE_BYPASS_CI_CHECKS_LABEL, - AUTO_MERGE_LABEL, - BASE_BRANCH, - MERGE_METHOD, -) -from .env import GITHUB_TOKEN, PRODUCTION -from .helpers import generate_job_summary_as_markdown -from .pr_validators import VALIDATOR_MAPPING - -if TYPE_CHECKING: - from github.Commit import Commit as GithubCommit - from github.PullRequest import PullRequest - from github.Repository import Repository as GithubRepo - -logging.basicConfig() -logger = logging.getLogger("auto_merge") -logger.setLevel(logging.INFO) - - -@contextmanager -def github_client() -> Iterator[Github]: - client = None - try: - client = Github(auth=Auth.Token(GITHUB_TOKEN), seconds_between_requests=0) - yield client - finally: - if client: - client.close() - - -def check_if_pr_is_auto_mergeable(head_commit: GithubCommit, pr: PullRequest, required_checks: set[str]) -> bool: - """Run all enabled validators and return if they all pass. - - Args: - head_commit (GithubCommit): The head commit of the PR - pr (PullRequest): The PR to check - required_checks (set[str]): The set of required passing checks - - Returns: - bool: True if the PR is auto-mergeable, False otherwise - """ - - validators = get_pr_validators(pr) - for validator in validators: - is_valid, error = validator(head_commit, pr, required_checks) - if not is_valid: - if error: - logger.info(f"PR #{pr.number} - {error}") - return False - return True - - -def get_pr_validators(pr: PullRequest) -> set[Callable]: - """ - Get the validator for a PR based on its labels - - Args: - pr (PullRequest): The PR to get the validator for - - Returns: - list[callable]: The validators - """ - validators: set[Callable] = set() - for label in pr.labels: - if label.name in VALIDATOR_MAPPING: - # Add these to our validators set: - validators |= VALIDATOR_MAPPING[label.name] - - if not validators: - # We shouldn't reach this point, but if we do, we raise an error. - # TODO: We could consider returning a dummy callable which always returns False, - # but for now, we raise an error to ensure we catch any misconfigurations. - raise ValueError( - f"PR #{pr.number} does not have a valid auto-merge label. " - f"Expected one of [{', '.join(VALIDATOR_MAPPING.keys())}], but got: " - f"[{'; '.join(label.name for label in pr.labels)}]", - ) - - return validators - - -def mark_pr_as_ready(node_id: str) -> None: - """Mark a draft PR as ready for review using the GitHub GraphQL API. - - The REST API PATCH endpoint does not support changing draft status. - The GraphQL markPullRequestReadyForReview mutation is required instead. - - Args: - node_id (str): The GraphQL node ID of the pull request - """ - query = """ - mutation($prId: ID!) { - markPullRequestReadyForReview(input: {pullRequestId: $prId}) { - pullRequest { isDraft } - } - } - """ - response = requests.post( - "https://api.github.com/graphql", - json={"query": query, "variables": {"prId": node_id}}, - headers={ - "Authorization": f"token {GITHUB_TOKEN}", - "Accept": "application/vnd.github+json", - }, - ) - response.raise_for_status() - data = response.json() - if "errors" in data: - raise RuntimeError(f"GraphQL error marking PR as ready: {data['errors']}") - - -def merge_with_retries(pr: PullRequest, max_retries: int = 3, wait_time: int = 60) -> Optional[PullRequest]: - """Merge a PR with retries - - Args: - pr (PullRequest): The PR to merge - max_retries (int, optional): The maximum number of retries. Defaults to 3. - wait_time (int, optional): The time to wait between retries in seconds. Defaults to 60. - """ - if pr.draft: - logger.info(f"PR #{pr.number} is a draft, marking as ready for review before merging") - mark_pr_as_ready(pr.node_id) - pr.update() - logger.info(f"PR #{pr.number} draft status after marking ready: {pr.draft}") - for i in range(max_retries): - try: - pr.merge(merge_method=MERGE_METHOD) - logger.info(f"PR #{pr.number} was auto-merged") - return pr - except Exception as e: - logger.error(f"Failed to merge PR #{pr.number} - {e}") - if i < max_retries - 1: - logger.info(f"Retrying to merge PR #{pr.number}") - time.sleep(wait_time) - else: - logger.error(f"Failed to merge PR #{pr.number} after {max_retries} retries") - return None - - -def process_pr(repo: GithubRepo, pr: PullRequest, required_passing_contexts: set[str], dry_run: bool) -> None | PullRequest: - """Process a PR to see if it is auto-mergeable and merge it if it is. - - Args: - repo (GithubRepo): The repository the PR is in - pr (PullRequest): The PR to process - required_passing_contexts (set[str]): The set of required passing checks - dry_run (bool): Whether to actually merge the PR or not - - Returns: - None | PullRequest: The PR if it was merged, None otherwise - """ - logger.info(f"Processing PR #{pr.number}") - head_commit = repo.get_commit(sha=pr.head.sha) - if check_if_pr_is_auto_mergeable(head_commit, pr, required_passing_contexts): - if not dry_run: - merge_with_retries(pr) - return pr - else: - logger.info(f"PR #{pr.number} is auto-mergeable but dry-run is enabled") - return None - - -def get_required_passing_contexts(repo_name: str, branch: str, token: str) -> set[str]: - """Fetch required status check contexts from GitHub rulesets for a branch. - - Uses the GitHub Rules API (GET /repos/{owner}/{repo}/rules/branches/{branch}) - which returns active rules from repository rulesets. - - Args: - repo_name (str): The repository in owner/repo format - branch (str): The branch name - token (str): The GitHub token for authentication - - Returns: - set[str]: The set of required status check context strings - """ - url = f"https://api.github.com/repos/{repo_name}/rules/branches/{branch}" - headers = { - "Authorization": f"token {token}", - "Accept": "application/vnd.github+json", - } - response = requests.get(url, headers=headers) - response.raise_for_status() - contexts: set[str] = set() - for rule in response.json(): - if rule["type"] == "required_status_checks": - for check in rule.get("parameters", {}).get("required_status_checks", []): - contexts.add(check["context"]) - return contexts - - -def back_off_if_rate_limited(github_client: Github) -> None: - """Sleep if the rate limit is reached - - Args: - github_client (Github): The Github client to check the rate limit of - """ - remaining_requests, _ = github_client.rate_limiting - if remaining_requests < 100: - logging.warning(f"Rate limit almost reached. Remaining requests: {remaining_requests}") - if remaining_requests == 0: - logging.warning(f"Rate limited. Sleeping for {github_client.rate_limiting_resettime - time.time()} seconds") - time.sleep(github_client.rate_limiting_resettime - time.time()) - return None - - -def auto_merge() -> None: - """Main function to auto-merge PRs that are candidates for auto-merge. - If the AUTO_MERGE_PRODUCTION environment variable is not set to "true", this will be a dry run. - """ - dry_run = PRODUCTION is False - if PRODUCTION: - logger.info("Running auto-merge in production mode. Mergeable PRs will be merged!") - else: - logger.info("Running auto-merge in dry mode mode. Mergeable PRs won't be merged!") - - with github_client() as gh_client: - repo = gh_client.get_repo(AIRBYTE_REPO) - logger.info(f"Fetching required passing contexts for {BASE_BRANCH}") - required_passing_contexts = get_required_passing_contexts(AIRBYTE_REPO, BASE_BRANCH, GITHUB_TOKEN) - candidate_issues = gh_client.search_issues( - f"repo:{AIRBYTE_REPO} is:pr label:{AUTO_MERGE_LABEL},{AUTO_MERGE_BYPASS_CI_CHECKS_LABEL} base:{BASE_BRANCH} state:open" - ) - prs = [issue.as_pull_request() for issue in candidate_issues] - logger.info( - f"Found {len(prs)} open PRs targeting {BASE_BRANCH} with the '{AUTO_MERGE_LABEL}' or '{AUTO_MERGE_BYPASS_CI_CHECKS_LABEL}' label" - ) - merged_prs = [] - for pr in prs: - back_off_if_rate_limited(gh_client) - if merged_pr := process_pr(repo, pr, required_passing_contexts, dry_run): - merged_prs.append(merged_pr) - if "GITHUB_STEP_SUMMARY" in os.environ: - job_summary_path = Path(os.environ["GITHUB_STEP_SUMMARY"]).write_text(generate_job_summary_as_markdown(merged_prs)) - logger.info(f"Job summary written to {job_summary_path}") diff --git a/airbyte-ci/connectors/auto_merge/src/auto_merge/pr_validators.py b/airbyte-ci/connectors/auto_merge/src/auto_merge/pr_validators.py deleted file mode 100644 index f08c71b33a56..000000000000 --- a/airbyte-ci/connectors/auto_merge/src/auto_merge/pr_validators.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable, Optional, Tuple - -from .consts import ( - AUTO_MERGE_BYPASS_CI_CHECKS_LABEL, - AUTO_MERGE_LABEL, - BASE_BRANCH, - CONNECTOR_PATH_PREFIXES, -) - -if TYPE_CHECKING: - from github.Commit import Commit as GithubCommit - from github.PullRequest import PullRequest - - -def has_auto_merge_label(head_commit: GithubCommit, pr: PullRequest, required_checks: set[str]) -> Tuple[bool, Optional[str]]: - has_auto_merge_label = any(label.name == AUTO_MERGE_LABEL for label in pr.labels) - if not has_auto_merge_label: - return False, f"does not have the {AUTO_MERGE_LABEL} label" - return True, None - - -def has_auto_merge_bypass_ci_checks_label( - head_commit: GithubCommit, pr: PullRequest, required_checks: set[str] -) -> Tuple[bool, Optional[str]]: - has_auto_merge_bypass_ci_checks_label = any(label.name == AUTO_MERGE_BYPASS_CI_CHECKS_LABEL for label in pr.labels) - if not has_auto_merge_bypass_ci_checks_label: - return False, f"does not have the {AUTO_MERGE_BYPASS_CI_CHECKS_LABEL} label" - return True, None - - -def targets_main_branch(head_commit: GithubCommit, pr: PullRequest, required_checks: set[str]) -> Tuple[bool, Optional[str]]: - if not pr.base.ref == BASE_BRANCH: - return False, f"does not target {BASE_BRANCH}" - return True, None - - -def only_modifies_connectors(head_commit: GithubCommit, pr: PullRequest, required_checks: set[str]) -> Tuple[bool, Optional[str]]: - modified_files = pr.get_files() - for file in modified_files: - if not any(file.filename.startswith(prefix) for prefix in CONNECTOR_PATH_PREFIXES): - return False, "is not only modifying connectors" - return True, None - - -def head_commit_passes_all_required_checks( - head_commit: GithubCommit, pr: PullRequest, required_checks: set[str] -) -> Tuple[bool, Optional[str]]: - successful_status_contexts = [commit_status.context for commit_status in head_commit.get_statuses() if commit_status.state == "success"] - successful_check_runs = [ - check_run.name - for check_run in head_commit.get_check_runs() - # Github considers a required check as passing if it has a conclusion of "success" or "skipped" - if check_run.conclusion == "success" or check_run.conclusion == "skipped" - ] - successful_contexts = set(successful_status_contexts + successful_check_runs) - if not required_checks.issubset(successful_contexts): - return False, "not all required checks passed" - return True, None - - -# A PR is considered auto-mergeable if: -# - it has the AUTO_MERGE_LABEL -# - it targets the BASE_BRANCH -# - it touches only files in CONNECTOR_PATH_PREFIXES -# - the head commit passes all required checks - -# PLEASE BE CAREFUL OF THE VALIDATOR ORDERING -# Let's declare faster checks first as the check_if_pr_is_auto_mergeable function fails fast. -COMMON_VALIDATORS = { - targets_main_branch, - only_modifies_connectors, -} -# Let's declare faster checks first as the check_if_pr_is_auto_mergeable function fails fast. -VALIDATOR_MAPPING: dict[str, set[Callable]] = { - # Until we have an auto-approve mechanism, we use this pipeline to force-merge, - # as long as all required checks pass. This doesn't bypass checks but it bypasses the - # approval requirement: - AUTO_MERGE_LABEL: COMMON_VALIDATORS | {has_auto_merge_label, head_commit_passes_all_required_checks}, - # These are pure registry updates, and can be auto-merged without any CI checks: - AUTO_MERGE_BYPASS_CI_CHECKS_LABEL: COMMON_VALIDATORS | {has_auto_merge_bypass_ci_checks_label}, -} diff --git a/airbyte-ci/connectors/ci_credentials/README.md b/airbyte-ci/connectors/ci_credentials/README.md deleted file mode 100644 index 314cdde32d7b..000000000000 --- a/airbyte-ci/connectors/ci_credentials/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# CI Credentials - -CLI tooling to read and manage GSM secrets: - -- `write-to-storage` download a connector's secrets locally in the connector's `secrets` folder -- `update-secrets` uploads new connector secret version that were locally updated. - -## Requirements - -This project requires Python 3.11 and `pipx`. - -## Installation - -The recommended way to install `ci_credentials` is using pipx. This ensures the tool and its dependencies are isolated from your other Python projects. - -First, install `pyenv`. If you don't have it yet, you can install it using Homebrew: - -```bash -brew update -brew install pyenv -``` - -If you haven't installed pipx, you can do it with pip: - -```bash -cd airbyte-ci/connectors/ci_credentials/ -pyenv install # ensure you have the correct python version -python -m pip install --user pipx -python -m pipx ensurepath -``` - -Once pyenv and pipx is installed then run the following (assuming you're in Airbyte repo root): - -```bash -pipx install --editable --force --python=python3.11 airbyte-ci/connectors/ci_credentials/ -``` - -Or install with a link to the default branch of the repo: - -```bash -pipx install git+https://github.com/airbytehq/airbyte.git#subdirectory=airbyte-ci/connectors/ci_credentials -``` - -This command installs `ci_credentials` and makes it globally available in your terminal. - -> [!Note] -> -> - `--force` is required to ensure updates are applied on subsequent installs. -> - `--python=python3.11` is required to ensure the correct python version is used. - -## Get GSM access - -Download a Service account json key that has access to Google Secrets Manager. -`ci_credentials` expects `GCP_GSM_CREDENTIALS` to be set in environment to be able to access secrets. - -### Create Service Account - -- Go to https://console.cloud.google.com/iam-admin/serviceaccounts/create?project=dataline-integration-testing -- In step #1 `Service account details`, set a name and a relevant description -- In step #2 `Grant this service account access to project`, select role `Owner` (there is a role that is more scope but I based this decision on others `-testing` service account) - -### Create Service Account Token - -- Go to https://console.cloud.google.com/iam-admin/serviceaccounts?project=dataline-integration-testing -- Find your service account and click on it -- Go in the tab "KEYS" -- Click on "ADD KEY -> Create new key" and select JSON. This will download a file on your computer - -### Setup ci_credentials - -- In your .zshrc, add: `export GCP_GSM_CREDENTIALS=$(cat )` - -## Development - -During development, you can use the `--editable` option to make changes to the `ci_credentials` package and have them immediately take effect without needing to reinstall the package: - -```bash -pipx install --editable airbyte-ci/connectors/ci_credentials/ -``` - -This is useful when you are making changes to the package and want to test them in real-time. - -> [!Note] -> -> - The package name is `ci_credentials`, not `airbyte-ci`. You will need this when uninstalling or reinstalling. - -## Usage - -After installation, you can use the `ci_credentials` command in your terminal. - -## Run it - -The `VERSION=dev` will make it so it knows to use your local current working directory and not the Github Action one. - -### Write credentials for a specific connector to local storage - -To download GSM secrets to `airbyte-integrations/connectors/source-bings-ads/secrets`: - -```bash -VERSION=dev ci_credentials source-bing-ads write-to-storage -``` - -### Write credentials for all connectors to local storage - -To download GSM secrets to for all available connectors into their respective `secrets` directories: - -```bash -VERSION=dev ci_credentials all write-to-storage -``` - -### Update secrets - -To upload to GSM newly updated configurations from `airbyte-integrations/connectors/source-bings-ads/secrets/updated_configurations`: - -```bash -VERSION=dev ci_credentials source-bing-ads update-secrets -``` - -## FAQ - -### Help - -```bash -VERSION=dev ci_credentials --help -``` - -### What is `VERSION=dev`? - -This is a way to tell the tool to write secrets using your local current working directory and not the Github Action runner one. diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/__init__.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/__init__.py deleted file mode 100644 index c49b12f7a07d..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from .secrets_manager import SecretsManager - -__all__ = ("SecretsManager",) diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/google_api.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/google_api.py deleted file mode 100644 index 69d12894ac90..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/google_api.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import time -from dataclasses import dataclass -from typing import Any, ClassVar, List, Mapping - -import jwt -import requests - -from .logger import Logger - - -TOKEN_TTL = 3600 - - -@dataclass -class GoogleApi: - """ - Simple Google API client - """ - - logger: ClassVar[Logger] = Logger() - - config: Mapping[str, Any] - scopes: List[str] - _access_token: str = None - - def get(self, url: str, params: Mapping = None) -> Mapping[str, Any]: - """Sends a GET request""" - token = self.get_access_token() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json", "X-Goog-User-Project": self.project_id} - # Making a get request - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - def post(self, url: str, json: Mapping = None, params: Mapping = None) -> Mapping[str, Any]: - """Sends a POST request""" - token = self.get_access_token() - - headers = {"Authorization": f"Bearer {token}", "X-Goog-User-Project": self.project_id} - # Making a get request - response = requests.post(url, headers=headers, json=json, params=params) - try: - response.raise_for_status() - except Exception: - self.logger.error(f"error body: {response.text}") - raise - return response.json() - - @property - def token_uri(self): - return self.config["token_uri"] - - @property - def project_id(self): - return self.config["project_id"] - - def __generate_jwt(self) -> str: - """Generates JWT token by a service account json file and scopes""" - now = int(time.time()) - claim = { - "iat": now, - "iss": self.config["client_email"], - "scope": ",".join(self.scopes), - "aud": self.token_uri, - "exp": now + TOKEN_TTL, - } - return jwt.encode(claim, self.config["private_key"].encode(), algorithm="RS256") - - def get_access_token(self) -> str: - """Generates an access token by a service account json file and scopes""" - - if self._access_token is None: - self._access_token = self.__get_access_token() - - return self._access_token - - def __get_access_token(self) -> str: - jwt = self.__generate_jwt() - resp = requests.post( - self.token_uri, - data={ - "assertion": jwt, - "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", - }, - ) - return resp.json()["access_token"] diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/logger.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/logger.py deleted file mode 100644 index 639a38e465b0..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/logger.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import datetime as dt -import inspect -import logging -import logging.handlers -import sys -from typing import Callable - - -class MyFormatter(logging.Formatter): - """Custom formatter for logging""" - - converter = dt.datetime.fromtimestamp - - def formatTime(self, record, datefmt=None): - """! @brief redefinition of format of log""" - ct = self.converter(record.created) - if datefmt: - s = ct.strftime(datefmt) - else: - t = ct.strftime("%Y-%m-%d %H:%M:%S") - s = "%s,%03d" % (t, record.msecs) - return s - - -class Logger: - """Simple logger with a pretty log header - the method error returns the value 1 - the method critical terminates a script work - """ - - def __init__(self): - formatter = MyFormatter(fmt="[%(asctime)s] - %(levelname)s - %(message)s", datefmt="%d/%m/%Y %H:%M:%S.%f") - - logger_name = __name__ - stack_items = inspect.stack() - for i in range(len(stack_items)): - if stack_items[i].filename.endswith("ci_credentials/logger.py"): - logger_name = ".".join(stack_items[i + 1].filename.split("/")[-3:])[:-3] - - self._logger = logging.getLogger(logger_name) - self._logger.setLevel(logging.DEBUG) - self._logger.propagate = False - - handler = logging.StreamHandler() - handler.setLevel(logging.DEBUG) - handler.setFormatter(formatter) - self._logger.addHandler(handler) - - @classmethod - def __prepare_log_line(cls, func_name: str, func: Callable) -> Callable: - def wrapper(*args): - prefix = "" - stack_items = inspect.stack() - for i in range(len(stack_items)): - if stack_items[i].filename.endswith("ci_credentials/logger.py"): - filepath = stack_items[i + 1].filename - line_number = stack_items[i + 1].lineno - - # show last 3 path items only - filepath = "/".join(filepath.split("/")[-3:]) - prefix = f"[{filepath}:{line_number}] # " - break - if prefix: - args = list(args) - args[0] = f"{prefix}{args[0]}" - func(*args) - if func_name == "critical": - sys.exit(1) - elif func_name == "error": - return 1 - return 0 - - return wrapper - - def __getattr__(self, function_name: str): - if not hasattr(self._logger, function_name): - return super().__getattr__(function_name) - return self.__prepare_log_line( - function_name, - getattr(self._logger, function_name), - ) diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/main.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/main.py deleted file mode 100644 index fa1b3ade1df5..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/main.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json -import sys -from json.decoder import JSONDecodeError - -import click - -from .logger import Logger -from .secrets_manager import SecretsManager - - -logger = Logger() - -ENV_GCP_GSM_CREDENTIALS = "GCP_GSM_CREDENTIALS" - - -# credentials of GSM and GitHub secrets should be shared via shell environment -@click.group() -@click.argument("connector_name") -@click.option("--gcp-gsm-credentials", envvar="GCP_GSM_CREDENTIALS") -@click.pass_context -def ci_credentials(ctx, connector_name: str, gcp_gsm_credentials): - ctx.ensure_object(dict) - ctx.obj["connector_name"] = connector_name - # parse unique connector name, because it can have the common prefix "connectors/" - connector_name = connector_name.split("/")[-1] - if connector_name == "all": - # if needed to load all secrets - connector_name = None - - # parse GCP_GSM_CREDENTIALS - try: - gsm_credentials = json.loads(gcp_gsm_credentials) if gcp_gsm_credentials else {} - except JSONDecodeError as e: - return logger.error(f"incorrect GCP_GSM_CREDENTIALS value, error: {e}") - - if not gsm_credentials: - return logger.error("GCP_GSM_CREDENTIALS shouldn't be empty!") - - secret_manager = SecretsManager( - connector_name=connector_name, - gsm_credentials=gsm_credentials, - ) - ctx.obj["secret_manager"] = secret_manager - ctx.obj["connector_secrets"] = secret_manager.read_from_gsm() - - -@ci_credentials.command(help="Download GSM secrets locally to the connector's secrets directory.") -@click.pass_context -def write_to_storage(ctx): - written_files = ctx.obj["secret_manager"].write_to_storage(ctx.obj["connector_secrets"]) - written_files_count = len(written_files) - click.echo(f"{written_files_count} secret files were written: {','.join([str(path) for path in written_files])}") - - -@ci_credentials.command(help="Update GSM secrets according to the content of the secrets/updated_configurations directory.") -@click.pass_context -def update_secrets(ctx): - new_remote_secrets = ctx.obj["secret_manager"].update_secrets(ctx.obj["connector_secrets"]) - updated_secret_names = [secret.name for secret in new_remote_secrets] - updated_secrets_count = len(new_remote_secrets) - click.echo(f"Updated {updated_secrets_count} secrets: {','.join(updated_secret_names)}") - - -if __name__ == "__main__": - sys.exit(ci_credentials(obj={})) diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/models.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/models.py deleted file mode 100644 index 84295b70de45..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/models.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from __future__ import ( # Used to evaluate type hints at runtime, a NameError: name 'RemoteSecret' is not defined is thrown otherwise - annotations, -) - -from dataclasses import dataclass - - -DEFAULT_SECRET_FILE = "config" - - -@dataclass -class Secret: - connector_name: str - configuration_file_name: str - value: str - - @property - def name(self) -> str: - return self.generate_secret_name(self.connector_name, self.configuration_file_name) - - @staticmethod - def generate_secret_name(connector_name: str, configuration_file_name: str) -> str: - """ - Generates an unique GSM secret name. - Format of secret name: SECRET____CREDS - Examples: - 1. connector_name: source-linnworks, filename: dsdssds_a-b---_---_config.json - => SECRET_SOURCE-LINNWORKS_DSDSSDS_A-B__CREDS - 2. connector_name: source-s3, filename: config.json - => SECRET_SOURCE-LINNWORKS__CREDS - """ - name_parts = ["secret", connector_name] - filename_wo_ext = configuration_file_name.replace(".json", "") - if filename_wo_ext != DEFAULT_SECRET_FILE: - name_parts.append(filename_wo_ext.replace(DEFAULT_SECRET_FILE, "").strip("_-")) - name_parts.append("_creds") - return "_".join(name_parts).upper() - - @property - def directory(self) -> str: - if self.connector_name == "base-normalization": - return f"airbyte-integrations/bases/{self.connector_name}/secrets" - else: - return f"airbyte-integrations/connectors/{self.connector_name}/secrets" - - -@dataclass -class RemoteSecret(Secret): - enabled_version: str - - @classmethod - def from_secret(cls, secret: Secret, enabled_version: str) -> RemoteSecret: - return RemoteSecret(secret.connector_name, secret.configuration_file_name, secret.value, enabled_version) diff --git a/airbyte-ci/connectors/ci_credentials/ci_credentials/secrets_manager.py b/airbyte-ci/connectors/ci_credentials/ci_credentials/secrets_manager.py deleted file mode 100644 index d64cb201f621..000000000000 --- a/airbyte-ci/connectors/ci_credentials/ci_credentials/secrets_manager.py +++ /dev/null @@ -1,302 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import base64 -import json -import os -import re -from glob import glob -from json.decoder import JSONDecodeError -from pathlib import Path -from typing import Any, ClassVar, List, Mapping - -import requests -import yaml - -from .google_api import GoogleApi -from .logger import Logger -from .models import DEFAULT_SECRET_FILE, RemoteSecret, Secret - - -DEFAULT_SECRET_FILE_WITH_EXT = DEFAULT_SECRET_FILE + ".json" - -GSM_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",) - -DEFAULT_MASK_KEY_PATTERNS = [ - "password", - "host", - "user", - "_key", - "_id", - "token", - "secret", - "bucket", - "role_arn", - "service_account_info", - "account_id", - "api", - "domain_url", - "client_id", - "access", - "jwt", - "base_url", - "key", - "credentials", - "_sid", - "survey_", - "appid", - "apikey", - "api_key", -] - - -class SecretsManager: - """Loading, saving and updating all requested secrets into connector folders""" - - SPEC_MASK_URL = "https://connectors.airbyte.com/files/registries/v0/specs_secrets_mask.yaml" - - logger: ClassVar[Logger] = Logger() - if os.getenv("VERSION") in ["dev", "dagger_ci"]: - base_folder = Path(os.getcwd()) - else: - base_folder = Path("/actions-runner/_work/airbyte/airbyte") - - def __init__(self, connector_name: str, gsm_credentials: Mapping[str, Any]): - self.gsm_credentials = gsm_credentials - self.connector_name = connector_name - self._api = None - - @property - def api(self) -> GoogleApi: - if self._api is None: - self._api = GoogleApi(self.gsm_credentials, GSM_SCOPES) - return self._api - - @property - def mask_key_patterns(self) -> List[str]: - return self._get_spec_mask() + DEFAULT_MASK_KEY_PATTERNS - - def __load_gsm_secrets(self) -> List[RemoteSecret]: - """Loads needed GSM secrets""" - secrets = [] - # docs: https://cloud.google.com/secret-manager/docs/filtering#api - filter = "name:SECRET_" - if self.connector_name: - filter += f" AND labels.connector={self.connector_name}" - url = f"https://secretmanager.googleapis.com/v1/projects/{self.api.project_id}/secrets" - next_token = None - while True: - params = { - "filter": filter, - } - if next_token: - params["pageToken"] = next_token - - all_secrets_data = self.api.get(url, params=params) - for secret_info in all_secrets_data.get("secrets") or []: - secret_name = secret_info["name"] - connector_name = secret_info.get("labels", {}).get("connector") - if not connector_name: - self.logger.warning(f"secret {secret_name} doesn't have the label 'connector'") - continue - elif self.connector_name and connector_name != self.connector_name: - self.logger.warning(f"incorrect the label connector '{connector_name}' of secret {secret_name}") - continue - filename = secret_info.get("labels", {}).get("filename") - if filename: - # all secret file names should be finished with ".json" - # but '.' cant be used in google, so we append it - filename = f"{filename}.json" - else: - # the "filename" label is optional. - filename = DEFAULT_SECRET_FILE_WITH_EXT - log_name = f'{secret_name.split("/")[-1]}({connector_name})' - self.logger.info(f"found GSM secret: {log_name} = > {filename}") - - versions_url = f"https://secretmanager.googleapis.com/v1/{secret_name}/versions" - versions_data = self.api.get(versions_url) - enabled_versions = [version["name"] for version in versions_data["versions"] if version["state"] == "ENABLED"] - if len(enabled_versions) > 1: - self.logger.critical(f"{log_name} should have one enabled version at the same time!!!") - if not enabled_versions: - self.logger.warning(f"{log_name} doesn't have enabled versions for {secret_name}") - continue - enabled_version = enabled_versions[0] - secret_url = f"https://secretmanager.googleapis.com/v1/{enabled_version}:access" - secret_data = self.api.get(secret_url) - secret_value = secret_data.get("payload", {}).get("data") - if not secret_value: - self.logger.warning(f"{log_name} has empty value") - continue - secret_value = base64.b64decode(secret_value.encode()).decode("utf-8") - try: - # minimize and validate its JSON value - json_value = json.loads(secret_value) - secret_value = json.dumps(json_value, separators=(",", ":")) - self.mask_secrets_from_action_log(None, json_value) - except JSONDecodeError as err: - self.logger.error(f"{log_name} has non-JSON value!!! Error: {err}") - continue - remote_secret = RemoteSecret(connector_name, filename, secret_value, enabled_version) - secrets.append(remote_secret) - - next_token = all_secrets_data.get("nextPageToken") - if not next_token: - break - - return secrets - - def mask_secrets_from_action_log(self, key, value): - # recursive masking of json based on leaf key - if not value: - return - elif isinstance(value, dict): - for child, item in value.items(): - self.mask_secrets_from_action_log(child, item) - elif isinstance(value, list): - for item in value: - self.mask_secrets_from_action_log(key, item) - else: - if key: - # regular value, check for what to mask - for pattern in self.mask_key_patterns: - if re.search(pattern, key): - self.logger.info(f"Add mask for key: {key}") - for line in str(value).splitlines(): - line = str(line).strip() - # don't output } and such - if len(line) > 1: - if not os.getenv("VERSION") in ["dev", "dagger_ci"]: - # has to be at the beginning of line for Github to notice it - print(f"::add-mask::{line}") - if os.getenv("VERSION") == "dagger_ci": - with open("/tmp/secrets_to_mask.txt", "a") as f: - f.write(f"{line}\n") - break - # see if it's really embedded json and get those values too - try: - json_value = json.loads(value) - self.mask_secrets_from_action_log(None, json_value) - except Exception: - # carry on - pass - - def read_from_gsm(self) -> List[RemoteSecret]: - """Reads all necessary secrets from different sources""" - secrets = self.__load_gsm_secrets() - if not len(secrets): - self.logger.warning(f"not found any secrets of the connector '{self.connector_name}'") - return [] - return secrets - - def write_to_storage(self, secrets: List[RemoteSecret]) -> List[Path]: - """Save target secrets to the airbyte-integrations/connectors|bases/{connector_name}/secrets folder - - Args: - secrets (List[RemoteSecret]): List of remote secret to write locally - - Returns: - List[Path]: List of paths were the secrets were written - """ - written_files = [] - for secret in secrets: - secrets_dir = self.base_folder / secret.directory - secrets_dir.mkdir(parents=True, exist_ok=True) - filepath = secrets_dir / secret.configuration_file_name - with open(filepath, "w") as file: - file.write(secret.value) - written_files.append(filepath) - return written_files - - def _create_new_secret_version(self, new_secret: Secret, old_secret: RemoteSecret) -> RemoteSecret: - """Create a new secret version from a new secret instance. Disable the previous secret version. - - Args: - new_secret (Secret): The new secret instance - old_secret (RemoteSecret): The old secret instance - - Returns: - RemoteSecret: The newly created remote secret instance - """ - secret_url = f"https://secretmanager.googleapis.com/v1/projects/{self.api.project_id}/secrets/{new_secret.name}:addVersion" - body = {"payload": {"data": base64.b64encode(new_secret.value.encode()).decode("utf-8")}} - new_version_response = self.api.post(secret_url, json=body) - self._disable_version(old_secret.enabled_version) - return RemoteSecret.from_secret(new_secret, enabled_version=new_version_response["name"]) - - def _disable_version(self, version_name: str) -> dict: - """Disable a GSM secret version - - Args: - version_name (str): Full name of the version (containing project id and secret name) - - Returns: - dict: API response - """ - disable_version_url = f"https://secretmanager.googleapis.com/v1/{version_name}:disable" - return self.api.post(disable_version_url) - - def _get_updated_secrets(self) -> List[Secret]: - """Find locally updated configurations files and return the most recent instance for each configuration file name. - - Returns: - List[Secret]: List of Secret instances parsed from local updated configuration files - """ - updated_configurations_glob = ( - f"{str(self.base_folder)}/airbyte-integrations/connectors/{self.connector_name}/secrets/updated_configurations/*.json" - ) - updated_configuration_files_versions = {} - for updated_configuration_path in glob(updated_configurations_glob): - updated_configuration_path = Path(updated_configuration_path) - with open(updated_configuration_path, "r") as updated_configuration: - updated_configuration_value = json.load(updated_configuration) - configuration_original_file_name = f"{updated_configuration_path.stem.split('|')[0]}{updated_configuration_path.suffix}" - updated_configuration_files_versions.setdefault(configuration_original_file_name, []) - updated_configuration_files_versions[configuration_original_file_name].append( - (updated_configuration_value, os.path.getctime(str(updated_configuration_path))) - ) - - for updated_configurations in updated_configuration_files_versions.values(): - updated_configurations.sort(key=lambda x: x[1]) - return [ - Secret( - connector_name=self.connector_name, - configuration_file_name=configuration_file_name, - value=json.dumps(versions_by_creation_time[-1][0]), - ) - for configuration_file_name, versions_by_creation_time in updated_configuration_files_versions.items() - ] - - def update_secrets(self, existing_secrets: List[RemoteSecret]) -> List[RemoteSecret]: - """Update existing secrets if an updated version was found locally. - - Args: - existing_secrets (List[RemoteSecret]): List of existing secrets for the current connector on GSM. - - Returns: - List[RemoteSecret]: List of updated secrets as RemoteSecret instances - """ - existing_secrets = {secret.name: secret for secret in existing_secrets} - updated_secrets = {secret.name: secret for secret in self._get_updated_secrets()} - new_remote_secrets = [] - for existing_secret_name in existing_secrets: - if existing_secret_name in updated_secrets and json.loads(updated_secrets[existing_secret_name].value) != json.loads( - existing_secrets[existing_secret_name].value - ): - new_secret = updated_secrets[existing_secret_name] - old_secret = existing_secrets[existing_secret_name] - new_remote_secret = self._create_new_secret_version(new_secret, old_secret) - new_remote_secrets.append(new_remote_secret) - self.logger.info(f"Updated {new_remote_secret.name} with new value") - return new_remote_secrets - - def _get_spec_mask(self) -> List[str]: - response = requests.get(self.SPEC_MASK_URL, allow_redirects=True) - if not response.ok: - self.logger.error(f"Failed to fetch spec mask: {response.content}") - try: - return yaml.safe_load(response.content)["properties"] - except Exception as e: - self.logger.error(f"Failed to parse spec mask: {e}") - return [] diff --git a/airbyte-ci/connectors/ci_credentials/poetry.lock b/airbyte-ci/connectors/ci_credentials/poetry.lock deleted file mode 100644 index 06f2fac41604..000000000000 --- a/airbyte-ci/connectors/ci_credentials/poetry.lock +++ /dev/null @@ -1,507 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "certifi" -version = "2025.1.31" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, -] - -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} - -[[package]] -name = "cryptography" -version = "44.0.0" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -files = [ - {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, - {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, - {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, - {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, - {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, - {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, - {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[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"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pytest" -version = "8.3.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-mock" -version = "1.12.1" -description = "Mock out responses from the requests package" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, - {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, -] - -[package.dependencies] -requests = ">=2.22,<3" - -[package.extras] -fixture = ["fixtures"] - -[[package]] -name = "urllib3" -version = "2.3.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11" -content-hash = "b9b62c5c060b4e57a4bebfee356c8c2c9f1bef3b9cd75d78fd1dccdf9e2e2212" diff --git a/airbyte-ci/connectors/ci_credentials/pyproject.toml b/airbyte-ci/connectors/ci_credentials/pyproject.toml deleted file mode 100644 index d7d85b6974cf..000000000000 --- a/airbyte-ci/connectors/ci_credentials/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -[tool.poetry] -name = "ci_credentials" -version = "1.2.1" -description = "CLI tooling to read and manage GSM secrets" -authors = ["Airbyte "] -readme = "README.md" -packages = [{ include = "ci_credentials" }] - -[tool.poetry.dependencies] -python = "^3.11" -requests = "^2.31" -cryptography = ">=42.0" -click = "^8.1.3" -pyyaml = "^6.0" -pyjwt = "2.8.0" - -[tool.poetry.group.dev.dependencies] -requests-mock = "^1.10.0" -pytest = "^8" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.scripts] -ci_credentials = "ci_credentials.main:ci_credentials" - -[tool.poe.tasks] -test = "pytest tests --config-file=./pyproject.toml" - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = ["test"] diff --git a/airbyte-ci/connectors/ci_credentials/tests/__init__.py b/airbyte-ci/connectors/ci_credentials/tests/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/ci_credentials/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/ci_credentials/tests/test_models.py b/airbyte-ci/connectors/ci_credentials/tests/test_models.py deleted file mode 100644 index dd3b749b53d2..000000000000 --- a/airbyte-ci/connectors/ci_credentials/tests/test_models.py +++ /dev/null @@ -1,48 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import pytest -from ci_credentials.models import Secret - - -@pytest.mark.parametrize( - "connector_name,filename,expected_name, expected_directory", - ( - ("source-default", "config.json", "SECRET_SOURCE-DEFAULT__CREDS", "airbyte-integrations/connectors/source-default/secrets"), - ( - "source-custom-filename-1", - "config_custom.json", - "SECRET_SOURCE-CUSTOM-FILENAME-1_CUSTOM__CREDS", - "airbyte-integrations/connectors/source-custom-filename-1/secrets", - ), - ( - "source-custom-filename-2", - "auth.json", - "SECRET_SOURCE-CUSTOM-FILENAME-2_AUTH__CREDS", - "airbyte-integrations/connectors/source-custom-filename-2/secrets", - ), - ( - "source-custom-filename-3", - "config_auth-test---___---config.json", - "SECRET_SOURCE-CUSTOM-FILENAME-3_AUTH-TEST__CREDS", - "airbyte-integrations/connectors/source-custom-filename-3/secrets", - ), - ( - "source-custom-filename-4", - "_____config_test---config.json", - "SECRET_SOURCE-CUSTOM-FILENAME-4_TEST__CREDS", - "airbyte-integrations/connectors/source-custom-filename-4/secrets", - ), - ( - "base-normalization", - "_____config_test---config.json", - "SECRET_BASE-NORMALIZATION_TEST__CREDS", - "airbyte-integrations/bases/base-normalization/secrets", - ), - ), -) -def test_secret_instantiation(connector_name, filename, expected_name, expected_directory): - secret = Secret(connector_name, filename, "secret_value") - assert secret.name == expected_name - assert secret.directory == expected_directory diff --git a/airbyte-ci/connectors/ci_credentials/tests/test_secrets_manager.py b/airbyte-ci/connectors/ci_credentials/tests/test_secrets_manager.py deleted file mode 100644 index 87c698e5ee6b..000000000000 --- a/airbyte-ci/connectors/ci_credentials/tests/test_secrets_manager.py +++ /dev/null @@ -1,209 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import base64 -import json -import re -from unittest.mock import patch - -import pytest -import requests_mock -from ci_credentials import SecretsManager -from ci_credentials.models import RemoteSecret, Secret - - -@pytest.fixture -def matchers(): - return { - "secrets": re.compile("https://secretmanager.googleapis.com/v1/projects/.+/secrets"), - "versions": re.compile("https://secretmanager.googleapis.com/v1/.+/versions"), - "addVersion": re.compile("https://secretmanager.googleapis.com/v1/.+:addVersion"), - "access": re.compile("https://secretmanager.googleapis.com/v1/.+/1:access"), - "disable": re.compile("https://secretmanager.googleapis.com/v1/.+:disable"), - "spec_secret_mask": re.compile("https://connectors.airbyte.com/files/registries/v0/specs_secrets_mask.yaml"), - } - - -@pytest.mark.parametrize( - "connector_name,gsm_secrets,expected_secrets", - ( - ( - "source-gsm-only", - { - "config": {"test_key": "test_value"}, - "config_oauth": {"test_key_1": "test_key_2"}, - }, - [ - RemoteSecret( - "source-gsm-only", - "config.json", - '{"test_key":"test_value"}', - "projects//secrets/SECRET_SOURCE-GSM-ONLY_0_CREDS/versions/1", - ), - RemoteSecret( - "source-gsm-only", - "config_oauth.json", - '{"test_key_1":"test_key_2"}', - "projects//secrets/SECRET_SOURCE-GSM-ONLY_1_CREDS/versions/1", - ), - ], - ), - ), - ids=[ - "gsm_only", - ], -) -@patch("ci_credentials.google_api.GoogleApi.get_access_token", lambda *args: ("fake_token", None)) -@patch("ci_credentials.google_api.GoogleApi.project_id", "fake_id") -def test_read(matchers, connector_name, gsm_secrets, expected_secrets): - secrets_list = { - "secrets": [ - { - "name": f"projects//secrets/SECRET_{connector_name.upper()}_{i}_CREDS", - "labels": { - "filename": k, - "connector": connector_name, - }, - } - for i, k in enumerate(gsm_secrets) - ] - } - - versions_response_list = [ - { - "json": { - "versions": [ - { - "name": f"projects//secrets/SECRET_{connector_name.upper()}_{i}_CREDS/versions/1", - "state": "ENABLED", - } - ] - } - } - for i in range(len(gsm_secrets)) - ] - - secrets_response_list = [ - {"json": {"payload": {"data": base64.b64encode(json.dumps(v).encode()).decode("utf-8")}}} for v in gsm_secrets.values() - ] - - manager = SecretsManager(connector_name=connector_name, gsm_credentials={}) - with requests_mock.Mocker() as m: - m.get(matchers["secrets"], json=secrets_list) - m.post(matchers["secrets"], json={"name": ""}) - m.get(matchers["versions"], versions_response_list) - m.get(matchers["access"], secrets_response_list) - m.get(matchers["spec_secret_mask"], json={"spec_secret_mask": "test"}) - - secrets = manager.read_from_gsm() - assert secrets == expected_secrets - - -@pytest.mark.parametrize( - "connector_name,secrets,expected_files", - ( - ( - "source-test", - [Secret("source-test", "test_config.json", "test_value")], - ["airbyte-integrations/connectors/source-test/secrets/test_config.json"], - ), - ( - "source-test2", - [Secret("source-test2", "test.json", "test_value"), Secret("source-test2", "auth.json", "test_auth")], - [ - "airbyte-integrations/connectors/source-test2/secrets/test.json", - "airbyte-integrations/connectors/source-test2/secrets/auth.json", - ], - ), - ( - "base-normalization", - [Secret("base-normalization", "test.json", "test_value"), Secret("base-normalization", "auth.json", "test_auth")], - [ - "airbyte-integrations/bases/base-normalization/secrets/test.json", - "airbyte-integrations/bases/base-normalization/secrets/auth.json", - ], - ), - ( - "source-no-secret", - [], - [], - ), - ), -) -def test_write(tmp_path, connector_name, secrets, expected_files): - manager = SecretsManager(connector_name=connector_name, gsm_credentials={}) - manager.base_folder = tmp_path - written_files = manager.write_to_storage(secrets) - for expected_file in expected_files: - target_file = tmp_path / expected_file - assert target_file.exists() - assert target_file in written_files - has = False - for secret in secrets: - if target_file.name == secret.configuration_file_name: - with open(target_file, "r") as f: - assert f.read() == secret.value - has = True - break - assert has, f"incorrect file data: {target_file}" - - -@pytest.mark.parametrize( - "connector_name,dict_json_value,expected_secret", - ( - ("source-default", '{"org_id": 111}', "::add-mask::111"), - ("source-default", '{"org": 111}', ""), - ), -) -def test_validate_mask_values(connector_name, dict_json_value, expected_secret, capsys): - manager = SecretsManager(connector_name=connector_name, gsm_credentials={}) - json_value = json.loads(dict_json_value) - manager.mask_secrets_from_action_log(None, json_value) - assert expected_secret in capsys.readouterr().out - - -@patch("ci_credentials.google_api.GoogleApi.get_access_token", lambda *args: ("fake_token", None)) -@patch("ci_credentials.google_api.GoogleApi.project_id", "fake_id") -@pytest.mark.parametrize( - "old_secret_value, updated_configurations", - [ - (json.dumps({"key": "value"}), [json.dumps({"key": "new_value_1"}), json.dumps({"key": "new_value_2"})]), - (json.dumps({"key": "value"}), [json.dumps({"key": "value"})]), - ], -) -def test_update_secrets(tmp_path, matchers, old_secret_value, updated_configurations): - existing_secret = RemoteSecret("source-test", "config.json", old_secret_value, "previous_version") - existing_secrets = [existing_secret] - - manager = SecretsManager(connector_name="source-test", gsm_credentials={}) - manager.base_folder = tmp_path - updated_configuration_directory = tmp_path / "airbyte-integrations/connectors/source-test/secrets/updated_configurations" - updated_configuration_directory.mkdir(parents=True) - - for i, updated_configuration in enumerate(updated_configurations): - stem, ext = existing_secret.configuration_file_name.split(".") - updated_configuration_file_name = f"{stem}|{i}.{ext}" - updated_configuration_path = updated_configuration_directory / updated_configuration_file_name - with open(updated_configuration_path, "w") as f: - f.write(updated_configuration) - - with requests_mock.Mocker() as m: - add_version_adapter = m.post(matchers["addVersion"], json={"name": "new_version"}) - disable_version_adapter = m.post(matchers["disable"], json={}) - updated_secrets = manager.update_secrets(existing_secrets) - - if old_secret_value != updated_configurations[-1]: - # We confirm the new version was created from the latest updated_configuration value - for secret in updated_secrets: - assert secret.connector_name == "source-test" - assert secret.configuration_file_name == "config.json" - assert secret.value == updated_configurations[-1] - assert secret.enabled_version == "new_version" - expected_add_version_payload = {"payload": {"data": base64.b64encode(updated_configurations[-1].encode()).decode("utf-8")}} - assert add_version_adapter.last_request.json() == expected_add_version_payload - assert disable_version_adapter.called_once - else: - assert not updated_secrets - assert not add_version_adapter.called - assert not disable_version_adapter.called diff --git a/airbyte-ci/connectors/connector_ops/README.md b/airbyte-ci/connectors/connector_ops/README.md deleted file mode 100644 index b8d2e0fd20de..000000000000 --- a/airbyte-ci/connectors/connector_ops/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# connector_ops - -A collection of utilities for working with Airbyte connectors. - -# Setup - -## Installation - -`connector_ops` tools use [Poetry](https://github.com/python-poetry/poetry) to manage dependencies, -and targets Python 3.11 and higher. - -Assuming you're in Airbyte repo root: - -```bash -cd airbyte-ci/connectors/connector_ops -poetry install -``` - -## Usage - -Connector OPS package provides useful `Connector` class and helper methods. It's used in several Airbyte CI packages. - -## Contributing to `connector_ops` - -### Running tests - -To run tests locally: - -```bash -poetry run pytest -``` - -## Changelog -- 0.10.2: Update Python version requirement from 3.10 to 3.11. -- 0.10.1: Update to `ci_credentials` 1.2.0, which drops `common_utils`. -- 0.10.0: Add `documentation_file_name` property to `Connector` class. -- 0.9.0: Add components path attribute for manifest-only connectors. -- 0.8.1: Gradle dependency discovery logic supports the Bulk CDK. -- 0.8.0: Add a `sbom_url` property to `Connector` -- 0.7.0: Added required reviewers for manifest-only connector changes/additions. -- 0.6.1: Simplified gradle dependency discovery logic. -- 0.6.0: Added manifest-only build. -- 0.5.0: Added `cloud_usage` property to `Connector` class. -- 0.4.0: Removed acceptance test configuration and allowed hosts checks as they're not used. diff --git a/airbyte-ci/connectors/connector_ops/connector_ops/__init__.py b/airbyte-ci/connectors/connector_ops/connector_ops/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/connector_ops/connector_ops/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/connector_ops/connector_ops/utils.py b/airbyte-ci/connectors/connector_ops/connector_ops/utils.py deleted file mode 100644 index 32e03ebdbcf0..000000000000 --- a/airbyte-ci/connectors/connector_ops/connector_ops/utils.py +++ /dev/null @@ -1,793 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import functools -import json -import logging -import os -import re -from dataclasses import dataclass -from enum import Enum -from glob import glob -from pathlib import Path -from typing import List, Optional, Set, Tuple, Union - -import git -import requests -import yaml -from ci_credentials import SecretsManager -from pydash.collections import find -from pydash.objects import get -from rich.console import Console -from simpleeval import simple_eval - - -console = Console() - -DIFFED_BRANCH = os.environ.get("DIFFED_BRANCH", "origin/master") -OSS_CATALOG_URL = "https://connectors.airbyte.com/files/registries/v0/oss_registry.json" -CLOUD_CATALOG_URL = "https://connectors.airbyte.com/files/registries/v0/cloud_registry.json" -BASE_AIRBYTE_DOCS_URL = "https://docs.airbyte.com" -CONNECTOR_PATH_PREFIX = "airbyte-integrations/connectors" -SOURCE_CONNECTOR_PATH_PREFIX = CONNECTOR_PATH_PREFIX + "/source-" -DESTINATION_CONNECTOR_PATH_PREFIX = CONNECTOR_PATH_PREFIX + "/destination-" - -THIRD_PARTY_GLOB = "third-party" -THIRD_PARTY_CONNECTOR_PATH_PREFIX = CONNECTOR_PATH_PREFIX + f"/{THIRD_PARTY_GLOB}/" -SCAFFOLD_CONNECTOR_GLOB = "-scaffold-" - - -ACCEPTANCE_TEST_CONFIG_FILE_NAME = "acceptance-test-config.yml" -METADATA_FILE_NAME = "metadata.yaml" -AIRBYTE_DOCKER_REPO = "airbyte" -AIRBYTE_REPO_DIRECTORY_NAME = "airbyte" -GRADLE_PROJECT_RE_PATTERN = r"project\((['\"])(.+?)\1\)" -TEST_GRADLE_DEPENDENCIES = [ - "testImplementation", - "testCompileOnly", - "integrationTestJavaImplementation", - "performanceTestJavaImplementation", - "testFixturesCompileOnly", - "testFixturesImplementation", -] - - -def download_catalog(catalog_url): - response = requests.get(catalog_url) - response.raise_for_status() - return response.json() - - -OSS_CATALOG = download_catalog(OSS_CATALOG_URL) -MANIFEST_FILE_NAME = "manifest.yaml" -COMPONENTS_FILE_NAME = "components.py" -DOCKERFILE_FILE_NAME = "Dockerfile" -PYPROJECT_FILE_NAME = "pyproject.toml" -ICON_FILE_NAME = "icon.svg" -POETRY_LOCK_FILE_NAME = "poetry.lock" - -STRATEGIC_CONNECTOR_THRESHOLDS = { - "sl": 200, - "ql": 400, -} - -ALLOWED_HOST_THRESHOLD = { - "ql": 300, -} - - -class ConnectorInvalidNameError(Exception): - pass - - -class ConnectorVersionNotFound(Exception): - pass - - -def get_connector_name_from_path(path): - return path.split("/")[2] - - -def get_changed_metadata(diff_regex: Optional[str] = None) -> Set[str]: - """Retrieve the set of connectors for which the metadata file was changed in the current branch (compared to master). - - Args: - diff_regex (str): Find the edited files that contain the following regex in their change. - - Returns: - Set[Connector]: Set of connectors that were changed - """ - return get_changed_file(METADATA_FILE_NAME, diff_regex) - - -def get_changed_file(file_name: str, diff_regex: Optional[str] = None) -> Set[str]: - """Retrieve the set of connectors for which the given file was changed in the current branch (compared to master). - - Args: - diff_regex (str): Find the edited files that contain the following regex in their change. - - Returns: - Set[Connector]: Set of connectors that were changed - """ - airbyte_repo = git.Repo(search_parent_directories=True) - - if diff_regex is None: - diff_command_args = ("--name-only", DIFFED_BRANCH) - else: - diff_command_args = ("--name-only", f"-G{diff_regex}", DIFFED_BRANCH) - - changed_acceptance_test_config_paths = { - file_path - for file_path in airbyte_repo.git.diff(*diff_command_args).split("\n") - if file_path.startswith(SOURCE_CONNECTOR_PATH_PREFIX) and file_path.endswith(file_name) - } - return {Connector(get_connector_name_from_path(changed_file)) for changed_file in changed_acceptance_test_config_paths} - - -def has_local_cdk_ref(build_file: Path) -> bool: - """Return true if the build file uses the local CDK. - - Args: - build_file (Path): Path to the build.gradle/build.gradle.kts file of the project. - - Returns: - bool: True if using local CDK. - """ - contents = "\n".join( - [ - # Return contents without inline code comments - line.split("//")[0] - for line in build_file.read_text().split("\n") - ] - ) - contents = contents.replace(" ", "") - return "useLocalCdk=true" in contents - - -def get_gradle_dependencies_block(build_file: Path) -> str: - """Get the dependencies block of a Gradle file. - - Args: - build_file (Path): Path to the build.gradle/build.gradle.kts file of the project. - - Returns: - str: The dependencies block of the Gradle file. - """ - contents = build_file.read_text().split("\n") - dependency_block = [] - in_dependencies_block = False - for line in contents: - if line.strip().startswith("dependencies"): - in_dependencies_block = True - continue - if in_dependencies_block: - if line.startswith("}"): - in_dependencies_block = False - break - else: - dependency_block.append(line) - dependencies_block = "\n".join(dependency_block) - return dependencies_block - - -def parse_gradle_dependencies(build_file: Path) -> Tuple[List[Path], List[Path]]: - """Parse the dependencies block of a Gradle file and return the list of project dependencies and test dependencies. - - Args: - build_file (Path): _description_ - - Returns: - Tuple[List[Tuple[str, Path]], List[Tuple[str, Path]]]: _description_ - """ - - dependencies_block = get_gradle_dependencies_block(build_file) - - project_dependencies: List[Path] = [] - test_dependencies: List[Path] = [] - - # Find all matches for test dependencies and regular dependencies - matches = re.findall( - r"(compileOnly|testCompileOnly|testFixturesCompileOnly|testFixturesImplementation|testImplementation|integrationTestJavaImplementation|performanceTestJavaImplementation|implementation|api).*?project\(['\"](.*?)['\"]\)", - dependencies_block, - ) - if matches: - # Iterate through each match - for match in matches: - dependency_type, project_path = match - path_parts = project_path.split(":") - path = Path(*path_parts) - - if dependency_type in TEST_GRADLE_DEPENDENCIES: - test_dependencies.append(path) - else: - project_dependencies.append(path) - - # Dedupe dependencies: - project_dependencies = list(set(project_dependencies)) - test_dependencies = list(set(test_dependencies)) - - return project_dependencies, test_dependencies - - -def get_all_gradle_dependencies( - build_file: Path, with_test_dependencies: bool = True, found_dependencies: Optional[List[Path]] = None -) -> List[Path]: - """Recursively retrieve all transitive dependencies of a Gradle project. - - Args: - build_file (Path): Path to the build.gradle/build.gradle.kts file of the project. - found_dependencies (List[Path]): List of dependencies that have already been found. Defaults to None. - - Returns: - List[Path]: All dependencies of the project. - """ - if found_dependencies is None: - found_dependencies = [] - project_dependencies, test_dependencies = parse_gradle_dependencies(build_file) - all_dependencies = project_dependencies + test_dependencies if with_test_dependencies else project_dependencies - valid_build_files = ["build.gradle", "build.gradle.kts"] - for dependency_path in all_dependencies: - for build_file in valid_build_files: - if dependency_path not in found_dependencies and Path(dependency_path / build_file).exists(): - found_dependencies.append(dependency_path) - get_all_gradle_dependencies(dependency_path / build_file, with_test_dependencies, found_dependencies) - - return found_dependencies - - -class ConnectorLanguage(str, Enum): - PYTHON = "python" - JAVA = "java" - LOW_CODE = "low-code" - MANIFEST_ONLY = "manifest-only" - - -class ConnectorLanguageError(Exception): - pass - - -@dataclass(frozen=True) -class Connector: - """Utility class to gather metadata about a connector.""" - - # Path to the connector directory relative to the CONNECTOR_PATH_PREFIX - # e.g source-google-sheets or third-party/farosai/airbyte-pagerduty-source - relative_connector_path: str - - def _get_type_and_name_from_technical_name(self) -> Tuple[str, str]: - if "-" not in self.technical_name: - raise ConnectorInvalidNameError(f"Connector type and name could not be inferred from {self.technical_name}") - _type = self.technical_name.split("-")[0] - name = self.technical_name[len(_type) + 1 :] - return _type, name - - @property - def technical_name(self) -> str: - """ - Return the technical name of the connector from the given relative_connector_path - e.g. source-google-sheets -> source-google-sheets or third-party/farosai/airbyte-pagerduty-source -> airbyte-pagerduty-source - """ - return self.relative_connector_path.split("/")[-1] - - @property - def name(self): - return self._get_type_and_name_from_technical_name()[1] - - @property - def connector_type(self) -> str: - return self.metadata["connectorType"] if self.metadata else None - - @property - def is_third_party(self) -> bool: - return THIRD_PARTY_GLOB in self.relative_connector_path - - @property - def has_airbyte_docs(self) -> bool: - return ( - self.metadata - and self.metadata.get("documentationUrl") is not None - and BASE_AIRBYTE_DOCS_URL in str(self.metadata.get("documentationUrl")) - ) - - @property - def local_connector_documentation_directory(self) -> Path: - return Path(f"./docs/integrations/{self.connector_type}s") - - @property - def relative_documentation_path_str(self) -> str: - documentation_url = self.metadata["documentationUrl"] - relative_documentation_path = documentation_url.replace(BASE_AIRBYTE_DOCS_URL, "") - - # strip leading and trailing slashes - relative_documentation_path = relative_documentation_path.strip("/") - - return f"./docs/{relative_documentation_path}" - - @property - def documentation_file_name(self) -> str: - return self.metadata.get("documentationUrl").split("/")[-1] + ".md" - - @property - def documentation_file_path(self) -> Optional[Path]: - return Path(f"{self.relative_documentation_path_str}.md") if self.has_airbyte_docs else None - - @property - def inapp_documentation_file_path(self) -> Path: - if not self.has_airbyte_docs: - return None - - return Path(f"{self.relative_documentation_path_str}.inapp.md") - - @property - def migration_guide_file_name(self) -> str: - return f"{self.name}-migrations.md" - - @property - def migration_guide_file_path(self) -> Path: - return self.local_connector_documentation_directory / self.migration_guide_file_name - - @property - def icon_path(self) -> Path: - file_path = self.code_directory / ICON_FILE_NAME - return file_path - - @property - def code_directory(self) -> Path: - return Path(f"./{CONNECTOR_PATH_PREFIX}/{self.relative_connector_path}") - - @property - def python_source_dir_path(self) -> Path: - return self.code_directory / self.technical_name.replace("-", "_") - - @property - def _manifest_only_path(self) -> Path: - return self.code_directory / MANIFEST_FILE_NAME - - @property - def _manifest_low_code_path(self) -> Path: - return self.python_source_dir_path / MANIFEST_FILE_NAME - - @property - def manifest_path(self) -> Path: - if self._manifest_only_path.is_file(): - return self._manifest_only_path - - return self._manifest_low_code_path - - @property - def manifest_only_components_path(self) -> Path: - """Return the path to the components.py file of a manifest-only connector.""" - return self.code_directory / COMPONENTS_FILE_NAME - - @property - def has_dockerfile(self) -> bool: - return self.dockerfile_file_path.is_file() - - @property - def dockerfile_file_path(self) -> Path: - return self.code_directory / DOCKERFILE_FILE_NAME - - @property - def pyproject_file_path(self) -> Path: - return self.code_directory / PYPROJECT_FILE_NAME - - @property - def metadata_file_path(self) -> Path: - return self.code_directory / METADATA_FILE_NAME - - @property - def metadata(self) -> Optional[dict]: - file_path = self.metadata_file_path - if not file_path.is_file(): - return None - return yaml.safe_load((self.code_directory / METADATA_FILE_NAME).read_text())["data"] - - @property - def connector_spec_file_content(self) -> Optional[dict]: - """ - The spec source of truth is the actual output of the spec command, as connector can mutate their spec. - But this is the best effort approach at statically fetching a spec without running the command on the connector. - Which is "good enough" in some cases. - """ - yaml_spec = Path(self.python_source_dir_path / "spec.yaml") - json_spec = Path(self.python_source_dir_path / "spec.json") - - if yaml_spec.exists(): - return yaml.safe_load(yaml_spec.read_text()) - elif json_spec.exists(): - with open(json_spec) as f: - return json.load(f) - elif self.manifest_path.exists(): - return yaml.safe_load(self.manifest_path.read_text())["spec"] - - return None - - @property - def language(self) -> ConnectorLanguage: - if Path(self.code_directory / "manifest.yaml").is_file(): - return ConnectorLanguage.MANIFEST_ONLY - if Path(self.code_directory / self.technical_name.replace("-", "_") / "manifest.yaml").is_file(): - return ConnectorLanguage.LOW_CODE - if Path(self.code_directory / "setup.py").is_file() or Path(self.code_directory / "pyproject.toml").is_file(): - return ConnectorLanguage.PYTHON - if Path(self.code_directory / "src" / "main" / "java").exists() or Path(self.code_directory / "src" / "main" / "kotlin").exists(): - return ConnectorLanguage.JAVA - return None - - @property - def version(self) -> Optional[str]: - if self.metadata is None: - return self.version_in_dockerfile_label - return self.metadata["dockerImageTag"] - - @property - def version_in_dockerfile_label(self) -> Optional[str]: - if not self.has_dockerfile: - return None - with open(self.code_directory / "Dockerfile") as f: - for line in f: - if "io.airbyte.version" in line: - return line.split("=")[1].strip() - raise ConnectorVersionNotFound( - """ - Could not find the connector version from its Dockerfile. - The io.airbyte.version tag is missing. - """ - ) - - @property - def name_from_metadata(self) -> Optional[str]: - return self.metadata.get("name") if self.metadata else None - - @property - def support_level(self) -> Optional[str]: - return self.metadata.get("supportLevel") if self.metadata else None - - def metadata_query_match(self, query_string: str) -> bool: - """Evaluate a query string against the connector metadata. - - Based on the simpleeval library: - https://github.com/danthedeckie/simpleeval - - Examples - -------- - >>> connector.metadata_query_match("'s3' in data.name") - True - - >>> connector.metadata_query_match("data.supportLevel == 'certified'") - False - - >>> connector.metadata_query_match("data.ab_internal.ql >= 100") - True - - Args: - query_string (str): The query string to evaluate. - - Returns: - bool: True if the query string matches the connector metadata, False otherwise. - """ - try: - matches = simple_eval(query_string, names={"data": self.metadata}) - return bool(matches) - except Exception as e: - # Skip on error as we not all fields are present in all connectors. - logging.debug(f"Failed to evaluate query string {query_string} for connector {self.technical_name}, error: {e}") - return False - - @property - def ab_internal_sl(self) -> int: - """Airbyte Internal Field. - - More info can be found here: https://www.notion.so/Internal-Metadata-Fields-32b02037e7b244b7934214019d0b7cc9 - - Returns: - int: The value - """ - default_value = 100 - sl_value = get(self.metadata, "ab_internal.sl") - - if sl_value is None: - logging.warning( - f"Connector {self.technical_name} does not have a `ab_internal.sl` defined in metadata.yaml. Defaulting to {default_value}" - ) - return default_value - - return sl_value - - @property - def ab_internal_ql(self) -> int: - """Airbyte Internal Field. - - More info can be found here: https://www.notion.so/Internal-Metadata-Fields-32b02037e7b244b7934214019d0b7cc9 - - Returns: - int: The value - """ - default_value = 100 - ql_value = get(self.metadata, "ab_internal.ql") - - if ql_value is None: - logging.warning( - f"Connector {self.technical_name} does not have a `ab_internal.ql` defined in metadata.yaml. Defaulting to {default_value}" - ) - return default_value - - return ql_value - - @property - def is_strategic_connector(self) -> bool: - """Check if a connector qualifies as a strategic connector. - - Returns: - bool: True if the connector is a high value connector, False otherwise. - """ - if self.ab_internal_sl >= STRATEGIC_CONNECTOR_THRESHOLDS["sl"]: - return True - - if self.ab_internal_ql >= STRATEGIC_CONNECTOR_THRESHOLDS["ql"]: - return True - - return False - - @property - def requires_high_test_strictness_level(self) -> bool: - """Check if a connector requires high strictness CAT tests. - - Returns: - bool: True if the connector requires high test strictness level, False otherwise. - """ - return self.ab_internal_ql >= STRATEGIC_CONNECTOR_THRESHOLDS["ql"] - - @property - def requires_allowed_hosts_check(self) -> bool: - """Check if a connector requires allowed hosts. - - Returns: - bool: True if the connector requires allowed hosts, False otherwise. - """ - return self.ab_internal_ql >= ALLOWED_HOST_THRESHOLD["ql"] - - @property - def allowed_hosts(self) -> Optional[List[str]]: - return self.metadata.get("allowedHosts") if self.metadata else None - - @property - def suggested_streams(self) -> Optional[List[str]]: - return self.metadata.get("suggestedStreams") if self.metadata else None - - @property - def acceptance_test_config_path(self) -> Path: - return self.code_directory / ACCEPTANCE_TEST_CONFIG_FILE_NAME - - @property - def acceptance_test_config(self) -> Optional[dict]: - try: - with open(self.acceptance_test_config_path) as acceptance_test_config_file: - return yaml.safe_load(acceptance_test_config_file) - except FileNotFoundError: - logging.warning(f"No {ACCEPTANCE_TEST_CONFIG_FILE_NAME} file found for {self.technical_name}") - return None - - @property - def supports_normalization(self) -> bool: - return self.metadata and self.metadata.get("normalizationConfig") is not None - - @property - def normalization_repository(self) -> Optional[str]: - if self.supports_normalization: - return f"{self.metadata['normalizationConfig']['normalizationRepository']}" - - @property - def normalization_tag(self) -> Optional[str]: - if self.supports_normalization: - return f"{self.metadata['normalizationConfig']['normalizationTag']}" - - @property - def is_using_poetry(self) -> bool: - return Path(self.code_directory / "pyproject.toml").exists() - - @property - def registry_primary_key_field(self) -> str: - """ - The primary key field of the connector in the registry. - - example: - - source -> sourceDefinitionId - - destination -> destinationDefinitionId - """ - return f"{self.connector_type}DefinitionId" - - @property - def is_enabled_in_any_registry(self) -> bool: - """Check if the connector is enabled in the registry. - - Example: - - {registries: null} -> false - - {registries: {oss: {enabled: false }}} -> false - - {registries: {oss: {enabled: true }}} -> true - - {registries: {cloud: {enabled: true }}} -> true - - Returns: - bool: True if the connector is enabled, False otherwise. - """ - registries = self.metadata.get("registryOverrides") - if not registries: - return False - - for registry in registries.values(): - if registry.get("enabled"): - return True - - return False - - @property - def is_released(self) -> bool: - """Pull the the OSS registry and check if it the current definition ID and docker image tag are in the registry. - If there is a match it means the connector is released. - We use the OSS registry as the source of truth for released connectors as the cloud registry can be a subset of the OSS registry. - - Returns: - bool: True if the connector is released, False otherwise. - """ - metadata = self.metadata - registry = download_catalog(OSS_CATALOG_URL) - for connector in registry[f"{self.connector_type}s"]: - if ( - connector[self.registry_primary_key_field] == metadata["definitionId"] - and connector["dockerImageTag"] == metadata["dockerImageTag"] - ): - return True - return False - - @property - def cloud_usage(self) -> Optional[str]: - """Pull the cloud registry, check if the connector is in the registry and return the usage metrics. - - Returns: - Optional[str]: The usage metrics of the connector, could be one of ["low", "medium", "high"] or None if the connector is not in the registry. - """ - metadata = self.metadata - definition_id = metadata.get("definitionId") - cloud_registry = download_catalog(CLOUD_CATALOG_URL) - - all_connectors_of_type = cloud_registry[f"{self.connector_type}s"] - connector_entry = find(all_connectors_of_type, {self.registry_primary_key_field: definition_id}) - if not connector_entry: - return None - - return get(connector_entry, "generated.metrics.cloud.usage") - - @property - def sbom_url(self) -> Optional[str]: - """ - Fetches SBOM URL from the connector definition in the OSS registry, if it exists, None otherwise. - """ - metadata = self.metadata - definition_id = metadata.get("definitionId") - # We use the OSS registry as the source of truth for released connectors as the cloud registry can be a subset of the OSS registry. - oss_registry = download_catalog(OSS_CATALOG_URL) - - all_connectors_of_type = oss_registry[f"{self.connector_type}s"] - connector_entry = find(all_connectors_of_type, {self.registry_primary_key_field: definition_id}) - if not connector_entry: - return None - - return get(connector_entry, "generated.sbomUrl") - - @property - def image_address(self) -> str: - return f'{self.metadata["dockerRepository"]}:{self.metadata["dockerImageTag"]}' - - @property - def cdk_name(self) -> str | None: - try: - return [tag.split(":")[-1] for tag in self.metadata["tags"] if tag.startswith("cdk:")][0] - except IndexError: - return None - - @property - def base_image_address(self) -> str | None: - return self.metadata.get("connectorBuildOptions", {}).get("baseImage") - - @property - def uses_base_image(self) -> bool: - return self.base_image_address is not None - - @property - def base_image_version(self) -> str | None: - if not self.uses_base_image: - return None - return self.base_image_address.split(":")[1].split("@")[0] - - def get_secret_manager(self, gsm_credentials: str): - return SecretsManager(connector_name=self.technical_name, gsm_credentials=gsm_credentials) - - def __repr__(self) -> str: - return self.technical_name - - @functools.lru_cache(maxsize=2) - def get_local_dependency_paths(self, with_test_dependencies: bool = True) -> Set[Path]: - dependencies_paths = [] - build_script = "build.gradle" - if Path(self.code_directory / "build.gradle.kts").exists(): - build_script = "build.gradle.kts" - - if self.language == ConnectorLanguage.JAVA: - dependencies_paths += [Path("./airbyte-cdk/java/airbyte-cdk"), Path("./airbyte-cdk/bulk")] - dependencies_paths += get_all_gradle_dependencies( - self.code_directory / build_script, with_test_dependencies=with_test_dependencies - ) - return sorted(list(set(dependencies_paths))) - - -def get_changed_connectors( - modified_files: Optional[Set[Union[str, Path]]] = None, source: bool = True, destination: bool = True, third_party: bool = True -) -> Set[Connector]: - """Retrieve a set of Connectors that were changed in the current branch (compared to master).""" - if modified_files is None: - airbyte_repo = git.Repo(search_parent_directories=True) - modified_files = airbyte_repo.git.diff("--name-only", DIFFED_BRANCH).split("\n") - - prefix_to_check = [] - if source: - prefix_to_check.append(SOURCE_CONNECTOR_PATH_PREFIX) - if destination: - prefix_to_check.append(DESTINATION_CONNECTOR_PATH_PREFIX) - if third_party: - prefix_to_check.append(THIRD_PARTY_CONNECTOR_PATH_PREFIX) - - changed_source_connector_files = { - file_path - for file_path in modified_files - if any(file_path.startswith(prefix) for prefix in prefix_to_check) and SCAFFOLD_CONNECTOR_GLOB not in file_path - } - return {Connector(get_connector_name_from_path(changed_file)) for changed_file in changed_source_connector_files} - - -def _get_relative_connector_folder_name_from_metadata_path(metadata_file_path: str) -> str: - """Get the relative connector folder name from the metadata file path. - - Args: - metadata_file_path (Path): Path to the metadata file. - - Returns: - str: The relative connector folder name. - """ - # remove CONNECTOR_PATH_PREFIX and anything before - metadata_file_path = metadata_file_path.split(CONNECTOR_PATH_PREFIX)[-1] - - # remove metadata.yaml - metadata_file_path = metadata_file_path.replace(METADATA_FILE_NAME, "") - - # remove leading and trailing slashes - metadata_file_path = metadata_file_path.strip("/") - return metadata_file_path - - -def get_all_connectors_in_repo() -> Set[Connector]: - """Retrieve a set of all Connectors in the repo. - We globe the connectors folder for metadata.yaml files and construct Connectors from the directory name. - - Returns: - A set of Connectors. - """ - repo = git.Repo(search_parent_directories=True) - repo_path = repo.working_tree_dir - - return { - Connector(_get_relative_connector_folder_name_from_metadata_path(metadata_file)) - for metadata_file in glob(f"{repo_path}/{CONNECTOR_PATH_PREFIX}/**/metadata.yaml", recursive=True) - if SCAFFOLD_CONNECTOR_GLOB not in metadata_file - } - - -class ConnectorTypeEnum(str, Enum): - source = "source" - destination = "destination" - - -class SupportLevelEnum(str, Enum): - certified = "certified" - community = "community" - archived = "archived" diff --git a/airbyte-ci/connectors/connector_ops/poetry.lock b/airbyte-ci/connectors/connector_ops/poetry.lock deleted file mode 100644 index 3cd49bb7a480..000000000000 --- a/airbyte-ci/connectors/connector_ops/poetry.lock +++ /dev/null @@ -1,1454 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "cachetools" -version = "5.5.1" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, - {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, -] - -[[package]] -name = "certifi" -version = "2025.1.31" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, -] - -[[package]] -name = "ci-credentials" -version = "1.2.0" -description = "CLI tooling to read and manage GSM secrets" -optional = false -python-versions = "^3.11" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [] -develop = false - -[package.dependencies] -click = "^8.1.3" -cryptography = ">=42.0" -pyjwt = "2.8.0" -pyyaml = "^6.0" -requests = "^2.31" - -[package.source] -type = "directory" -url = "../ci_credentials" - -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "(python_version >= \"3.12\" or python_version == \"3.11\") and platform_system == \"Windows\"", dev = "(python_version >= \"3.12\" or python_version == \"3.11\") and sys_platform == \"win32\""} - -[[package]] -name = "cryptography" -version = "44.0.0" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, - {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, - {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, - {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, - {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, - {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, - {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "deprecated" -version = "1.2.18" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] - -[[package]] -name = "freezegun" -version = "1.5.1" -description = "Let your Python tests travel through time" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, - {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, -] - -[package.dependencies] -python-dateutil = ">=2.7" - -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.44" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, - {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] - -[[package]] -name = "google-api-core" -version = "2.24.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google_api_core-2.24.1-py3-none-any.whl", hash = "sha256:bc78d608f5a5bf853b80bd70a795f703294de656c096c0968320830a4bc280f1"}, - {file = "google_api_core-2.24.1.tar.gz", hash = "sha256:f8b36f5456ab0dd99a1b693a40a31d1e7757beea380ad1b38faaf8941eae9d8a"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -proto-plus = [ - {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-auth" -version = "2.38.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, - {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-storage" -version = "2.19.0" -description = "Google Cloud Storage API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba"}, - {file = "google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2"}, -] - -[package.dependencies] -google-api-core = ">=2.15.0,<3.0.0dev" -google-auth = ">=2.26.1,<3.0dev" -google-cloud-core = ">=2.3.0,<3.0dev" -google-crc32c = ">=1.0,<2.0dev" -google-resumable-media = ">=2.7.2" -requests = ">=2.18.0,<3.0.0dev" - -[package.extras] -protobuf = ["protobuf (<6.0.0dev)"] -tracing = ["opentelemetry-api (>=1.1.0)"] - -[[package]] -name = "google-crc32c" -version = "1.6.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa"}, - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc"}, - {file = "google_crc32c-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f"}, - {file = "google_crc32c-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57"}, - {file = "google_crc32c-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d"}, - {file = "google_crc32c-1.6.0.tar.gz", hash = "sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, - {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.66.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, -] - -[package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "numpy" -version = "2.2.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"}, - {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"}, - {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"}, - {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"}, - {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"}, - {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"}, - {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"}, - {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"}, - {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"}, - {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"}, - {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"}, - {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"}, - {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"}, - {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"}, - {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"}, - {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"}, - {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"}, - {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"}, - {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"}, - {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"}, - {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"}, - {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"}, - {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"}, - {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"}, - {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"}, - {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"}, - {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"}, - {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"}, - {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"}, - {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"}, - {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"}, - {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "proto-plus" -version = "1.26.0" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"}, - {file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "5.29.3" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, - {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, - {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, - {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, - {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, - {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, - {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, - {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, - {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.1" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "1.10.21" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"}, - {file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"}, - {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"}, - {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"}, - {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"}, - {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"}, - {file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"}, - {file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"}, - {file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"}, - {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"}, - {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"}, - {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"}, - {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"}, - {file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"}, - {file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"}, - {file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"}, - {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"}, - {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"}, - {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"}, - {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"}, - {file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"}, - {file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"}, - {file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"}, - {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"}, - {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"}, - {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"}, - {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"}, - {file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"}, - {file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"}, - {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"}, - {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"}, - {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"}, - {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"}, - {file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"}, - {file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"}, - {file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"}, - {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"}, - {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"}, - {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"}, - {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"}, - {file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"}, - {file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"}, - {file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"}, - {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"}, - {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"}, - {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"}, - {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"}, - {file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"}, - {file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"}, - {file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pydash" -version = "6.0.2" -description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pydash-6.0.2-py3-none-any.whl", hash = "sha256:6d3ce5cbbc8ca3533c12782ac201c2ec756d1e1703ec3efc88f2b95d1ed2bb31"}, - {file = "pydash-6.0.2.tar.gz", hash = "sha256:35caa588e01d293713655e0870544d25128cd414c5e19477a0d63adc2b2ca03e"}, -] - -[package.extras] -dev = ["Sphinx", "black", "build", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "importlib-metadata (<5)", "invoke", "isort", "pylint", "pytest", "pytest-cov", "sphinx-rtd-theme", "tox", "twine", "wheel"] - -[[package]] -name = "pygithub" -version = "2.5.0" -description = "Use the full Github API v3" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "PyGithub-2.5.0-py3-none-any.whl", hash = "sha256:b0b635999a658ab8e08720bdd3318893ff20e2275f6446fcf35bf3f44f2c0fd2"}, - {file = "pygithub-2.5.0.tar.gz", hash = "sha256:e1613ac508a9be710920d26eb18b1905ebd9926aa49398e88151c1b526aad3cf"}, -] - -[package.dependencies] -Deprecated = "*" -pyjwt = {version = ">=2.4.0", extras = ["crypto"]} -pynacl = ">=1.4.0" -requests = ">=2.14.0" -typing-extensions = ">=4.0.0" -urllib3 = ">=1.26.0" - -[[package]] -name = "pygments" -version = "2.19.1" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.dependencies] -cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} - -[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"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "pytest" -version = "8.3.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, - {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2025.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, - {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.9.4" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "semver" -version = "3.0.4" -description = "Python helper for Semantic Versioning (https://semver.org)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, - {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, -] - -[[package]] -name = "simpleeval" -version = "0.9.13" -description = "A simple, safe single expression evaluator library." -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "simpleeval-0.9.13-py2.py3-none-any.whl", hash = "sha256:22a2701a5006e4188d125d34accf2405c2c37c93f6b346f2484b6422415ae54a"}, - {file = "simpleeval-0.9.13.tar.gz", hash = "sha256:4a30f9cc01825fe4c719c785e3762623e350c4840d5e6855c2a8496baaa65fac"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "smmap" -version = "5.0.2" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2025.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wrapt" -version = "1.17.2" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version >= \"3.12\" or python_version == \"3.11\"" -files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, -] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11" -content-hash = "2e5cf16ae5611f4e60137746863e018c5670a42829f024c9d7384e9d5e144581" diff --git a/airbyte-ci/connectors/connector_ops/pyproject.toml b/airbyte-ci/connectors/connector_ops/pyproject.toml deleted file mode 100644 index 108cbed0cbb4..000000000000 --- a/airbyte-ci/connectors/connector_ops/pyproject.toml +++ /dev/null @@ -1,38 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.1.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "connector_ops" -version = "0.10.1" -description = "Packaged maintained by the connector operations team to perform CI for connectors" -authors = ["Airbyte "] - -[tool.poetry.dependencies] -python = "^3.11" -click = "^8.1.3" -requests = "^2.31" -PyYAML = "^6.0" -GitPython = "^3.1.29" -pydantic = "^1.9" -PyGithub = "^2" -rich = "^13.0.0" -pydash = "^6.0.2" -google-cloud-storage = "^2.8.0" -ci-credentials = {path = "../ci_credentials"} -pandas = "^2.0.3" -simpleeval = "^0.9.13" -semver = "^3.0.2" - -[tool.poetry.group.dev.dependencies] -pytest = "^8" -pytest-mock = "^3.10.0" -freezegun = "^1.1.0" - -[tool.poe.tasks] -test = "pytest tests" - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = ["test"] diff --git a/airbyte-ci/connectors/connector_ops/pytest.ini b/airbyte-ci/connectors/connector_ops/pytest.ini deleted file mode 100644 index 6df308df74d5..000000000000 --- a/airbyte-ci/connectors/connector_ops/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -markers = - slow: marks tests as slow (deselect with '-m "not slow"') - serial diff --git a/airbyte-ci/connectors/connector_ops/tests/conftest.py b/airbyte-ci/connectors/connector_ops/tests/conftest.py deleted file mode 100644 index 78aad3d1c104..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/conftest.py +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -import os -from datetime import datetime - -import pandas as pd -import pytest - - -@pytest.fixture(scope="module") -def adoption_metrics_per_connector_version(): - return pd.DataFrame( - [ - { - "connector_definition_id": "dfd88b22-b603-4c3d-aad7-3701784586b1", - "connector_version": "2.0.0", - "number_of_connections": 0, - "number_of_users": 0, - "succeeded_syncs_count": 0, - "failed_syncs_count": 0, - "total_syncs_count": 0, - "sync_success_rate": 0.0, - } - ] - ) - - -@pytest.fixture -def dummy_qa_report() -> pd.DataFrame: - return pd.DataFrame( - [ - { - "connector_type": "source", - "connector_name": "test", - "connector_technical_name": "source-test", - "connector_definition_id": "foobar", - "connector_version": "0.0.0", - "support_level": "community", - "is_on_cloud": False, - "is_appropriate_for_cloud_use": True, - "latest_build_is_successful": True, - "documentation_is_available": False, - "number_of_connections": 0, - "number_of_users": 0, - "sync_success_rate": 0.99, - "total_syncs_count": 0, - "failed_syncs_count": 0, - "succeeded_syncs_count": 0, - "is_eligible_for_promotion_to_cloud": True, - "report_generation_datetime": datetime.utcnow(), - } - ] - ) - - -@pytest.fixture(autouse=True) -def set_working_dir_to_repo_root(monkeypatch): - """Set working directory to the root of the repository. - - HACK: This is a workaround for the fact that these tests are not run from the root of the repository. - """ - monkeypatch.chdir(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) diff --git a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-header.md b/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-header.md deleted file mode 100644 index 2d430959e1c6..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-header.md +++ /dev/null @@ -1,9 +0,0 @@ -# Foobar Migration Guide - -## 2.0.0 - -This is something else - -## 1.0.0 - -This is something diff --git a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-title.md b/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-title.md deleted file mode 100644 index 10fd8674485a..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/bad-title.md +++ /dev/null @@ -1,9 +0,0 @@ -# source-foobar Migration Guide - -## Upgrading to 2.0.0 - -This is something else - -## Upgrading to 1.0.0 - -This is something diff --git a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/extra-header.md b/airbyte-ci/connectors/connector_ops/tests/test_migration_files/extra-header.md deleted file mode 100644 index 02a23ff5bd19..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/extra-header.md +++ /dev/null @@ -1,13 +0,0 @@ -# Foobar Migration Guide - -## Upgrading to 2.0.0 - -This is something else - -## Upgrading to 1.0.0 - -This is something - -## Upgrading to 1.0.0 - -This is extra diff --git a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/missing-entry.md b/airbyte-ci/connectors/connector_ops/tests/test_migration_files/missing-entry.md deleted file mode 100644 index cf642efdc263..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/missing-entry.md +++ /dev/null @@ -1,5 +0,0 @@ -# Foobar Migration Guide - -## Upgrading to 1.0.0 - -This is something diff --git a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/out-of-order.md b/airbyte-ci/connectors/connector_ops/tests/test_migration_files/out-of-order.md deleted file mode 100644 index dc2caf839c41..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_migration_files/out-of-order.md +++ /dev/null @@ -1,9 +0,0 @@ -# Foobar Migration Guide - -## Upgrading to 1.0.0 - -This is something - -## Upgrading to 2.0.0 - -This is something else diff --git a/airbyte-ci/connectors/connector_ops/tests/test_utils.py b/airbyte-ci/connectors/connector_ops/tests/test_utils.py deleted file mode 100644 index a58acdb17f14..000000000000 --- a/airbyte-ci/connectors/connector_ops/tests/test_utils.py +++ /dev/null @@ -1,184 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from contextlib import nullcontext as does_not_raise -from pathlib import Path - -import pytest -import semver -from connector_ops import utils - - -class TestConnector: - @pytest.mark.parametrize( - "technical_name, expected_type, expected_name, expected_error", - [ - ("source-faker", "source", "faker", does_not_raise()), - ("source-facebook-marketing", "source", "facebook-marketing", does_not_raise()), - ("destination-postgres", "destination", "postgres", does_not_raise()), - ("foo", None, None, pytest.raises(utils.ConnectorInvalidNameError)), - ], - ) - def test__get_type_and_name_from_technical_name(self, technical_name, expected_type, expected_name, expected_error): - connector = utils.Connector(technical_name) - with expected_error: - assert connector._get_type_and_name_from_technical_name() == (expected_type, expected_name) - assert connector.name == expected_name - assert connector.connector_type == expected_type - - @pytest.mark.parametrize( - "connector, exists", - [ - (utils.Connector("source-faker"), True), - (utils.Connector("source-doesnotexist"), False), - ], - ) - def test_init(self, connector, exists, mocker, tmp_path): - assert str(connector) == connector.technical_name - assert connector.code_directory == Path(f"./airbyte-integrations/connectors/{connector.technical_name}") - assert connector.acceptance_test_config_path == connector.code_directory / utils.ACCEPTANCE_TEST_CONFIG_FILE_NAME - - if exists: - assert connector.connector_type, connector.name == connector._get_type_and_name_from_technical_name() - assert connector.documentation_file_path == Path(f"./docs/integrations/{connector.connector_type}s/{connector.name}.md") - assert isinstance(connector.metadata, dict) - assert isinstance(connector.support_level, str) - assert isinstance(connector.acceptance_test_config, dict) - assert connector.icon_path == Path(f"./airbyte-integrations/connectors/{connector.technical_name}/icon.svg") - assert semver.Version.parse(connector.version) - else: - assert connector.metadata is None - assert connector.support_level is None - assert connector.acceptance_test_config is None - assert connector.icon_path == Path(f"./airbyte-integrations/connectors/{connector.technical_name}/icon.svg") - assert connector.version is None - with pytest.raises(utils.ConnectorVersionNotFound): - Path(tmp_path / "Dockerfile").touch() - mocker.patch.object(utils.Connector, "code_directory", tmp_path) - utils.Connector(connector.technical_name).version - - def test_metadata_query_match(self, mocker): - connector = utils.Connector("source-faker") - mocker.patch.object(utils.Connector, "metadata", {"dockerRepository": "airbyte/source-faker", "ab_internal": {"ql": 100}}) - assert connector.metadata_query_match("data.dockerRepository == 'airbyte/source-faker'") - assert connector.metadata_query_match("'source' in data.dockerRepository") - assert not connector.metadata_query_match("data.dockerRepository == 'airbyte/source-faker2'") - assert not connector.metadata_query_match("'destination' in data.dockerRepository") - assert connector.metadata_query_match("data.ab_internal.ql == 100") - assert connector.metadata_query_match("data.ab_internal.ql >= 100") - assert connector.metadata_query_match("data.ab_internal.ql > 1") - assert not connector.metadata_query_match("data.ab_internal.ql == 101") - assert not connector.metadata_query_match("data.ab_internal.ql >= 101") - assert not connector.metadata_query_match("data.ab_internal.ql > 101") - assert not connector.metadata_query_match("data.ab_internal == whatever") - - @pytest.fixture - def connector_without_dockerfile(self, mocker, tmp_path): - mocker.patch.object(utils.Connector, "code_directory", tmp_path) - connector = utils.Connector("source-faker") - return connector - - def test_has_dockerfile_without_dockerfile(self, connector_without_dockerfile): - assert not connector_without_dockerfile.has_dockerfile - - @pytest.fixture - def connector_with_dockerfile(self, mocker, tmp_path): - mocker.patch.object(utils.Connector, "code_directory", tmp_path) - connector = utils.Connector("source-faker") - tmp_path.joinpath("Dockerfile").touch() - return connector - - def test_has_dockerfile_with_dockerfile(self, connector_with_dockerfile): - assert connector_with_dockerfile.has_dockerfile - - -@pytest.fixture() -def gradle_file_with_dependencies(tmpdir) -> tuple[Path, list[Path], list[Path]]: - test_gradle_file = Path(tmpdir) / "build.gradle" - test_gradle_file.write_text( - """ - plugins { - id 'java' - } - - dependencies { - implementation project(':path:to:dependency1') - implementation project(':path:to:dependency2') - testImplementation project(':path:to:test:dependency') - integrationTestJavaImplementation project(':path:to:test:dependency1') - performanceTestJavaImplementation project(':path:to:test:dependency2') - } - """ - ) - expected_dependencies = [Path("path/to/dependency1"), Path("path/to/dependency2")] - expected_test_dependencies = [Path("path/to/test/dependency"), Path("path/to/test/dependency1"), Path("path/to/test/dependency2")] - - return test_gradle_file, expected_dependencies, expected_test_dependencies - - -@pytest.fixture() -def gradle_file_with_local_cdk_dependencies(tmpdir) -> tuple[Path, list[Path], list[Path]]: - test_gradle_file = Path(tmpdir) / "build.gradle" - test_gradle_file.write_text( - """ - plugins { - id 'java' - id 'airbyte-java-connector' - } - - airbyteJavaConnector { - cdkVersionRequired = '0.1.0' - features = ['db-destinations'] - useLocalCdk = true - } - - airbyteJavaConnector.addCdkDependencies() - - dependencies { - implementation project(':path:to:dependency1') - implementation project(':path:to:dependency2') - testImplementation project(':path:to:test:dependency') - integrationTestJavaImplementation project(':path:to:test:dependency1') - performanceTestJavaImplementation project(':path:to:test:dependency2') - } - """ - ) - expected_dependencies = [ - Path("path/to/dependency1"), - Path("path/to/dependency2"), - ] - expected_test_dependencies = [ - Path("path/to/test/dependency"), - Path("path/to/test/dependency1"), - Path("path/to/test/dependency2"), - ] - return test_gradle_file, expected_dependencies, expected_test_dependencies - - -def test_parse_dependencies(gradle_file_with_dependencies): - gradle_file, expected_regular_dependencies, expected_test_dependencies = gradle_file_with_dependencies - regular_dependencies, test_dependencies = utils.parse_gradle_dependencies(gradle_file) - assert len(regular_dependencies) == len(expected_regular_dependencies) - assert all([regular_dependency in expected_regular_dependencies for regular_dependency in regular_dependencies]) - assert len(test_dependencies) == len(expected_test_dependencies) - assert all([test_dependency in expected_test_dependencies for test_dependency in test_dependencies]) - - -def test_parse_dependencies_with_cdk(gradle_file_with_local_cdk_dependencies): - gradle_file, expected_regular_dependencies, expected_test_dependencies = gradle_file_with_local_cdk_dependencies - regular_dependencies, test_dependencies = utils.parse_gradle_dependencies(gradle_file) - assert len(regular_dependencies) == len(expected_regular_dependencies) - assert all([regular_dependency in expected_regular_dependencies for regular_dependency in regular_dependencies]) - assert len(test_dependencies) == len(expected_test_dependencies) - assert all([test_dependency in expected_test_dependencies for test_dependency in test_dependencies]) - - -def test_get_all_connectors_in_repo(): - all_connectors = utils.get_all_connectors_in_repo() - assert len(all_connectors) > 0 - for connector in all_connectors: - assert isinstance(connector, utils.Connector) - assert connector.metadata is not None - if connector.has_airbyte_docs and connector.is_enabled_in_any_registry: - assert connector.documentation_file_path.exists() diff --git a/airbyte-ci/connectors/connectors_insights/README.md b/airbyte-ci/connectors/connectors_insights/README.md deleted file mode 100644 index 6d38228949f8..000000000000 --- a/airbyte-ci/connectors/connectors_insights/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Connectors Insights - -Connectors Insights is a Python project designed to generate various insights from analysis of our connectors code. This project utilizes Poetry for dependency management and packaging. - -## Artifacts Produced -The project generates the following artifacts: - -- `insights.json`: Contains general insights and metadata about the connectors. -- `sbom.json`: Contains the Software Bill Of Material. Produced by [Syft](https://github.com/anchore/syft). - -## Installation -To install the project and its dependencies, ensure you have Poetry installed, then run: -```sh -poetry install -``` - -## Usage -The Connectors Insights project provides a command-line interface (CLI) to generate the artifacts. Below is the command to run the CLI: - -```sh -# From airbyte root directory -connectors-insights generate --output-directory --gcs-uri=gs:/// --connector-directory airbyte-integrations/connectors/ --concurrency 2 --rewrite -``` - -### CLI Options - -- `generate`: The command to generate the artifacts. - -- `-o, --output-dir`: Specifies the local directory where the generated artifacts will be saved. In this example, artifacts are saved to `/Users/augustin/Desktop/insights`. - -- `-g, --gcs-uri`: The Google Cloud Storage (GCS) URI prefix where the artifacts will be uploaded. In the form: `gs:///`. - -- `-d, --connector-directory`: The directory containing the connectors. This option points to the location of the connectors to be analyzed, here it is `airbyte-integrations/connectors/`. - -- `-c, --concurrency`: Sets the level of concurrency for the generation process. In this example, it is set to `2`. - -- `--rewrite`: If provided, this flag indicates that existing artifacts should be rewritten if they already exist. - -## Example -To generate the artifacts and save them both locally and to GCS, you can use the following command: - -```sh -connectors-insights generate --output-directory --gcs-uri=gs:/// --connector-directory airbyte-integrations/connectors/ --concurrency 2 --rewrite -``` - -This command will generate `insights.json` and `sbom.json` files, saving them to the specified local directory and uploading them to the specified GCS URI if `--gcs-uri` is passed. - -### Examples of generated artifacts -* [`insights.json`](https://storage.googleapis.com/prod-airbyte-cloud-connector-metadata-service/connector_insights/source-faker/latest/insights.json) -* [`sbom.json`](https://storage.googleapis.com/prod-airbyte-cloud-connector-metadata-service/connector_insights/source-faker/latest/sbom.json) - - -## Orchestration - -This CLI is currently running nightly in GitHub Actions. The workflow can be found in `.github/workflows/connector_insights.yml`. - -## Changelog - -### 0.3.7 -Update Python version requirement from 3.10 to 3.11. - -### 0.3.5 -Fix permissions issue when installing `pylint` in connector container. - -### 0.3.4 -Update `dagger` to `0.13.3`. - -### 0.3.3 -Use SBOM from the connector registry (SPDX format) instead of generating SBOM in the connector insights. - -### 0.3.2 -Bugfix: Ignore CI on master report if it's not accessible. - -### 0.3.1 -Skip manifest inferred insights when the connector does not have a `manifest.yaml` file. - -### 0.3.0 -Adding `manifest_uses_parameters`, `manifest_uses_custom_components`, and `manifest_custom_components_classes` insights. - -### 0.2.4 -Do not generate insights for `*-scaffold-*` and `*-strict-encrypt` connectors. - -### 0.2.3 -Share `.docker/config.json` with `syft` to benefit from increased DockerHub rate limit. - -### 0.2.2 -- Write the sbom output to a file and not to stdout to avoid issues with large outputs. - -### 0.2.1 -- Implement a high-level error handling to not fail the entire process if a connector fails to generate insights. - -### 0.2.0 -- Detect deprecated class and module use in connectors. -- Fix missing CDK version for connectors not declaring a CDK name in their metadata. - -### 0.1.0 -- Initial release diff --git a/airbyte-ci/connectors/connectors_insights/poetry.lock b/airbyte-ci/connectors/connectors_insights/poetry.lock deleted file mode 100644 index 0587981e3fa3..000000000000 --- a/airbyte-ci/connectors/connectors_insights/poetry.lock +++ /dev/null @@ -1,2379 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "anyio" -version = "4.8.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "asyncclick" -version = "8.1.8" -description = "Composable command line interface toolkit," -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "asyncclick-8.1.8-py3-none-any.whl", hash = "sha256:eb1ccb44bc767f8f0695d592c7806fdf5bd575605b4ee246ffd5fadbcfdbd7c6"}, - {file = "asyncclick-8.1.8.0-py3-none-any.whl", hash = "sha256:be146a2d8075d4fe372ff4e877f23c8b5af269d16705c1948123b9415f6fd678"}, - {file = "asyncclick-8.1.8.tar.gz", hash = "sha256:0f0eb0f280e04919d67cf71b9fcdfb4db2d9ff7203669c40284485c149578e4c"}, -] - -[package.dependencies] -anyio = ">=4.0,<5.0" -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "asyncer" -version = "0.0.7" -description = "Asyncer, async and await, focused on developer experience." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "asyncer-0.0.7-py3-none-any.whl", hash = "sha256:f0d579d4f67c4ead52ede3a45c854f462cae569058a8a6a68a4ebccac1c335d8"}, - {file = "asyncer-0.0.7.tar.gz", hash = "sha256:d5e563fb0f56eb87b97257984703658a4f5bbdb52ff851b3e8ed864cc200b1d2"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5.0" - -[[package]] -name = "attrs" -version = "25.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, -] - -[package.extras] -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-uv", "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 = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "beartype" -version = "0.19.0" -description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699"}, - {file = "beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573"}, -] - -[package.extras] -dev = ["autoapi (>=0.9.0)", "coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] -test = ["coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -test-tox = ["equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "typing-extensions (>=3.10.0.0)"] -test-tox-coverage = ["coverage (>=5.5)"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "cachetools" -version = "5.5.1" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, - {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, -] - -[[package]] -name = "cattrs" -version = "24.1.2" -description = "Composable complex class support for attrs and dataclasses." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, - {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, -] - -[package.dependencies] -attrs = ">=23.1.0" - -[package.extras] -bson = ["pymongo (>=4.4.0)"] -cbor2 = ["cbor2 (>=5.4.6)"] -msgpack = ["msgpack (>=1.0.5)"] -msgspec = ["msgspec (>=0.18.5)"] -orjson = ["orjson (>=3.9.2)"] -pyyaml = ["pyyaml (>=6.0)"] -tomlkit = ["tomlkit (>=0.11.8)"] -ujson = ["ujson (>=5.7.0)"] - -[[package]] -name = "certifi" -version = "2025.1.31" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, -] - -[[package]] -name = "ci-credentials" -version = "1.2.0" -description = "CLI tooling to read and manage GSM secrets" -optional = false -python-versions = "^3.11" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [] -develop = false - -[package.dependencies] -click = "^8.1.3" -cryptography = ">=42.0" -pyjwt = "2.8.0" -pyyaml = "^6.0" -requests = "^2.31" - -[package.source] -type = "directory" -url = "../ci_credentials" - -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "(python_version == \"3.11\" or python_version >= \"3.12\") and platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "connector-ops" -version = "0.10.0" -description = "Packaged maintained by the connector operations team to perform CI for connectors" -optional = false -python-versions = "^3.11" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [] -develop = false - -[package.dependencies] -ci-credentials = {path = "../ci_credentials"} -click = "^8.1.3" -GitPython = "^3.1.29" -google-cloud-storage = "^2.8.0" -pandas = "^2.0.3" -pydantic = "^1.9" -pydash = "^6.0.2" -PyGithub = "^2" -PyYAML = "^6.0" -requests = "^2.31" -rich = "^13.0.0" -semver = "^3.0.2" -simpleeval = "^0.9.13" - -[package.source] -type = "directory" -url = "../connector_ops" - -[[package]] -name = "cryptography" -version = "44.0.0" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, - {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, - {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, - {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, - {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, - {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, - {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, - {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, - {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, - {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, - {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, - {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "dagger-io" -version = "0.13.3" -description = "A client package for running Dagger pipelines in Python." -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "dagger_io-0.13.3-py3-none-any.whl", hash = "sha256:c3be14bd2c77ad265f944612123ef6f7653cf0365ffee0c70bf2a2662dc9783d"}, - {file = "dagger_io-0.13.3.tar.gz", hash = "sha256:fb9f602b8493f6e5f66afba4c6f51485dccb7c4795fbde7d92e188c69e8961b7"}, -] - -[package.dependencies] -anyio = ">=3.6.2" -beartype = ">=0.18.2" -cattrs = ">=22.2.0" -gql = {version = ">=3.5.0", extras = ["httpx"]} -opentelemetry-exporter-otlp-proto-grpc = ">=1.23.0" -opentelemetry-sdk = ">=1.23.0" -platformdirs = ">=2.6.2" -rich = ">=10.11.0" -typing-extensions = ">=4.8.0" - -[[package]] -name = "deprecated" -version = "1.2.18" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] - -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.44" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, - {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] - -[[package]] -name = "google-api-core" -version = "2.24.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_api_core-2.24.1-py3-none-any.whl", hash = "sha256:bc78d608f5a5bf853b80bd70a795f703294de656c096c0968320830a4bc280f1"}, - {file = "google_api_core-2.24.1.tar.gz", hash = "sha256:f8b36f5456ab0dd99a1b693a40a31d1e7757beea380ad1b38faaf8941eae9d8a"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -proto-plus = [ - {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, - {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-auth" -version = "2.38.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, - {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-storage" -version = "2.19.0" -description = "Google Cloud Storage API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba"}, - {file = "google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2"}, -] - -[package.dependencies] -google-api-core = ">=2.15.0,<3.0.0dev" -google-auth = ">=2.26.1,<3.0dev" -google-cloud-core = ">=2.3.0,<3.0dev" -google-crc32c = ">=1.0,<2.0dev" -google-resumable-media = ">=2.7.2" -requests = ">=2.18.0,<3.0.0dev" - -[package.extras] -protobuf = ["protobuf (<6.0.0dev)"] -tracing = ["opentelemetry-api (>=1.1.0)"] - -[[package]] -name = "google-crc32c" -version = "1.6.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa"}, - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc"}, - {file = "google_crc32c-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f"}, - {file = "google_crc32c-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57"}, - {file = "google_crc32c-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d"}, - {file = "google_crc32c-1.6.0.tar.gz", hash = "sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, - {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.66.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, -] - -[package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "gql" -version = "3.5.0" -description = "GraphQL client for Python" -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "gql-3.5.0-py2.py3-none-any.whl", hash = "sha256:70dda5694a5b194a8441f077aa5fb70cc94e4ec08016117523f013680901ecb7"}, - {file = "gql-3.5.0.tar.gz", hash = "sha256:ccb9c5db543682b28f577069950488218ed65d4ac70bb03b6929aaadaf636de9"}, -] - -[package.dependencies] -anyio = ">=3.0,<5" -backoff = ">=1.11.1,<3.0" -graphql-core = ">=3.2,<3.3" -httpx = {version = ">=0.23.1,<1", optional = true, markers = "extra == \"httpx\""} -yarl = ">=1.6,<2.0" - -[package.extras] -aiohttp = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)"] -all = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "websockets (>=10,<12)"] -botocore = ["botocore (>=1.21,<2)"] -dev = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "httpx (>=0.23.1,<1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "sphinx (>=5.3.0,<6)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "vcrpy (==4.4.0)", "websockets (>=10,<12)"] -httpx = ["httpx (>=0.23.1,<1)"] -requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)"] -test = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "vcrpy (==4.4.0)", "websockets (>=10,<12)"] -test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.4.0)"] -websockets = ["websockets (>=10,<12)"] - -[[package]] -name = "graphql-core" -version = "3.2.6" -description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." -optional = false -python-versions = "<4,>=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f"}, - {file = "graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab"}, -] - -[[package]] -name = "grpcio" -version = "1.70.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, - {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:374d014f29f9dfdb40510b041792e0e2828a1389281eb590df066e1cc2b404e5"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2af68a6f5c8f78d56c145161544ad0febbd7479524a59c16b3e25053f39c87f"}, - {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7df14b2dcd1102a2ec32f621cc9fab6695effef516efbc6b063ad749867295"}, - {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c78b339869f4dbf89881e0b6fbf376313e4f845a42840a7bdf42ee6caed4b11f"}, - {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58ad9ba575b39edef71f4798fdb5c7b6d02ad36d47949cd381d4392a5c9cbcd3"}, - {file = "grpcio-1.70.0-cp310-cp310-win32.whl", hash = "sha256:2b0d02e4b25a5c1f9b6c7745d4fa06efc9fd6a611af0fb38d3ba956786b95199"}, - {file = "grpcio-1.70.0-cp310-cp310-win_amd64.whl", hash = "sha256:0de706c0a5bb9d841e353f6343a9defc9fc35ec61d6eb6111802f3aa9fef29e1"}, - {file = "grpcio-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:17325b0be0c068f35770f944124e8839ea3185d6d54862800fc28cc2ffad205a"}, - {file = "grpcio-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:dbe41ad140df911e796d4463168e33ef80a24f5d21ef4d1e310553fcd2c4a386"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5ea67c72101d687d44d9c56068328da39c9ccba634cabb336075fae2eab0d04b"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb5277db254ab7586769e490b7b22f4ddab3876c490da0a1a9d7c695ccf0bf77"}, - {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7831a0fc1beeeb7759f737f5acd9fdcda520e955049512d68fda03d91186eea"}, - {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:27cc75e22c5dba1fbaf5a66c778e36ca9b8ce850bf58a9db887754593080d839"}, - {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d63764963412e22f0491d0d32833d71087288f4e24cbcddbae82476bfa1d81fd"}, - {file = "grpcio-1.70.0-cp311-cp311-win32.whl", hash = "sha256:bb491125103c800ec209d84c9b51f1c60ea456038e4734688004f377cfacc113"}, - {file = "grpcio-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:d24035d49e026353eb042bf7b058fb831db3e06d52bee75c5f2f3ab453e71aca"}, - {file = "grpcio-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ef4c14508299b1406c32bdbb9fb7b47612ab979b04cf2b27686ea31882387cff"}, - {file = "grpcio-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:aa47688a65643afd8b166928a1da6247d3f46a2784d301e48ca1cc394d2ffb40"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:880bfb43b1bb8905701b926274eafce5c70a105bc6b99e25f62e98ad59cb278e"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e654c4b17d07eab259d392e12b149c3a134ec52b11ecdc6a515b39aceeec898"}, - {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2394e3381071045a706ee2eeb6e08962dd87e8999b90ac15c55f56fa5a8c9597"}, - {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b3c76701428d2df01964bc6479422f20e62fcbc0a37d82ebd58050b86926ef8c"}, - {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac073fe1c4cd856ebcf49e9ed6240f4f84d7a4e6ee95baa5d66ea05d3dd0df7f"}, - {file = "grpcio-1.70.0-cp312-cp312-win32.whl", hash = "sha256:cd24d2d9d380fbbee7a5ac86afe9787813f285e684b0271599f95a51bce33528"}, - {file = "grpcio-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:0495c86a55a04a874c7627fd33e5beaee771917d92c0e6d9d797628ac40e7655"}, - {file = "grpcio-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa573896aeb7d7ce10b1fa425ba263e8dddd83d71530d1322fd3a16f31257b4a"}, - {file = "grpcio-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:d405b005018fd516c9ac529f4b4122342f60ec1cee181788249372524e6db429"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f32090238b720eb585248654db8e3afc87b48d26ac423c8dde8334a232ff53c9"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa089a734f24ee5f6880c83d043e4f46bf812fcea5181dcb3a572db1e79e01c"}, - {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19375f0300b96c0117aca118d400e76fede6db6e91f3c34b7b035822e06c35f"}, - {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7c73c42102e4a5ec76608d9b60227d917cea46dff4d11d372f64cbeb56d259d0"}, - {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0a5c78d5198a1f0aa60006cd6eb1c912b4a1520b6a3968e677dbcba215fabb40"}, - {file = "grpcio-1.70.0-cp313-cp313-win32.whl", hash = "sha256:fe9dbd916df3b60e865258a8c72ac98f3ac9e2a9542dcb72b7a34d236242a5ce"}, - {file = "grpcio-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:4119fed8abb7ff6c32e3d2255301e59c316c22d31ab812b3fbcbaf3d0d87cc68"}, - {file = "grpcio-1.70.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:8058667a755f97407fca257c844018b80004ae8035565ebc2812cc550110718d"}, - {file = "grpcio-1.70.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:879a61bf52ff8ccacbedf534665bb5478ec8e86ad483e76fe4f729aaef867cab"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ba0a173f4feacf90ee618fbc1a27956bfd21260cd31ced9bc707ef551ff7dc7"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558c386ecb0148f4f99b1a65160f9d4b790ed3163e8610d11db47838d452512d"}, - {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:412faabcc787bbc826f51be261ae5fa996b21263de5368a55dc2cf824dc5090e"}, - {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3b0f01f6ed9994d7a0b27eeddea43ceac1b7e6f3f9d86aeec0f0064b8cf50fdb"}, - {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7385b1cb064734005204bc8994eed7dcb801ed6c2eda283f613ad8c6c75cf873"}, - {file = "grpcio-1.70.0-cp38-cp38-win32.whl", hash = "sha256:07269ff4940f6fb6710951116a04cd70284da86d0a4368fd5a3b552744511f5a"}, - {file = "grpcio-1.70.0-cp38-cp38-win_amd64.whl", hash = "sha256:aba19419aef9b254e15011b230a180e26e0f6864c90406fdbc255f01d83bc83c"}, - {file = "grpcio-1.70.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4f1937f47c77392ccd555728f564a49128b6a197a05a5cd527b796d36f3387d0"}, - {file = "grpcio-1.70.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:0cd430b9215a15c10b0e7d78f51e8a39d6cf2ea819fd635a7214fae600b1da27"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:e27585831aa6b57b9250abaf147003e126cd3a6c6ca0c531a01996f31709bed1"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1af8e15b0f0fe0eac75195992a63df17579553b0c4af9f8362cc7cc99ccddf4"}, - {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbce24409beaee911c574a3d75d12ffb8c3e3dd1b813321b1d7a96bbcac46bf4"}, - {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff4a8112a79464919bb21c18e956c54add43ec9a4850e3949da54f61c241a4a6"}, - {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5413549fdf0b14046c545e19cfc4eb1e37e9e1ebba0ca390a8d4e9963cab44d2"}, - {file = "grpcio-1.70.0-cp39-cp39-win32.whl", hash = "sha256:b745d2c41b27650095e81dea7091668c040457483c9bdb5d0d9de8f8eb25e59f"}, - {file = "grpcio-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:a31d7e3b529c94e930a117b2175b2efd179d96eb3c7a21ccb0289a8ab05b645c"}, - {file = "grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.70.0)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.7" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.28.1" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "importlib-metadata" -version = "8.5.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, - {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "multidict" -version = "6.1.0" -description = "multidict implementation" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, -] - -[[package]] -name = "mypy" -version = "1.14.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, - {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, - {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, - {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, - {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, - {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, - {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, - {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, - {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, - {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, - {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, - {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, - {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, - {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, - {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, - {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, -] - -[package.dependencies] -mypy_extensions = ">=1.0.0" -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "numpy" -version = "2.2.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"}, - {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"}, - {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"}, - {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"}, - {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"}, - {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"}, - {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"}, - {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"}, - {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"}, - {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"}, - {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"}, - {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"}, - {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"}, - {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"}, - {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"}, - {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"}, - {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"}, - {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"}, - {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"}, - {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"}, - {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"}, - {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"}, - {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"}, - {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"}, - {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"}, - {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"}, - {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"}, - {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"}, - {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"}, - {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"}, - {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"}, - {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"}, - {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"}, - {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"}, - {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"}, - {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"}, - {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"}, - {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"}, -] - -[[package]] -name = "opentelemetry-api" -version = "1.29.0" -description = "OpenTelemetry Python API" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"}, - {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.5.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.29.0" -description = "OpenTelemetry Protobuf encoding" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.29.0-py3-none-any.whl", hash = "sha256:a9d7376c06b4da9cf350677bcddb9618ed4b8255c3f6476975f5e38274ecd3aa"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.29.0.tar.gz", hash = "sha256:e7c39b5dbd1b78fe199e40ddfe477e6983cb61aa74ba836df09c3869a3e3e163"}, -] - -[package.dependencies] -opentelemetry-proto = "1.29.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.29.0" -description = "OpenTelemetry Collector Protobuf over gRPC Exporter" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0-py3-none-any.whl", hash = "sha256:5a2a3a741a2543ed162676cf3eefc2b4150e6f4f0a193187afb0d0e65039c69c"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0.tar.gz", hash = "sha256:3d324d07d64574d72ed178698de3d717f62a059a93b6b7685ee3e303384e73ea"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" -grpcio = ">=1.63.2,<2.0.0" -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.29.0" -opentelemetry-proto = "1.29.0" -opentelemetry-sdk = ">=1.29.0,<1.30.0" - -[[package]] -name = "opentelemetry-proto" -version = "1.29.0" -description = "OpenTelemetry Python Proto" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_proto-1.29.0-py3-none-any.whl", hash = "sha256:495069c6f5495cbf732501cdcd3b7f60fda2b9d3d4255706ca99b7ca8dec53ff"}, - {file = "opentelemetry_proto-1.29.0.tar.gz", hash = "sha256:3c136aa293782e9b44978c738fff72877a4b78b5d21a64e879898db7b2d93e5d"}, -] - -[package.dependencies] -protobuf = ">=5.0,<6.0" - -[[package]] -name = "opentelemetry-sdk" -version = "1.29.0" -description = "OpenTelemetry Python SDK" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"}, - {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"}, -] - -[package.dependencies] -opentelemetry-api = "1.29.0" -opentelemetry-semantic-conventions = "0.50b0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.50b0" -description = "OpenTelemetry Semantic Conventions" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"}, - {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.29.0" - -[[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "propcache" -version = "0.2.1" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, - {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, - {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, - {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, - {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, - {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, - {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, - {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, - {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, - {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, - {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, - {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, - {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, - {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, - {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, - {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, - {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, - {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, - {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, - {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, - {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, - {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, - {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, - {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, - {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, - {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, - {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, - {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, -] - -[[package]] -name = "proto-plus" -version = "1.26.0" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"}, - {file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "5.29.3" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, - {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, - {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, - {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, - {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, - {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, - {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, - {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, - {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, - {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.1" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "1.10.21" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"}, - {file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"}, - {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"}, - {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"}, - {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"}, - {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"}, - {file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"}, - {file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"}, - {file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"}, - {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"}, - {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"}, - {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"}, - {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"}, - {file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"}, - {file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"}, - {file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"}, - {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"}, - {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"}, - {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"}, - {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"}, - {file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"}, - {file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"}, - {file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"}, - {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"}, - {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"}, - {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"}, - {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"}, - {file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"}, - {file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"}, - {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"}, - {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"}, - {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"}, - {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"}, - {file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"}, - {file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"}, - {file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"}, - {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"}, - {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"}, - {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"}, - {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"}, - {file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"}, - {file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"}, - {file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"}, - {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"}, - {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"}, - {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"}, - {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"}, - {file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"}, - {file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"}, - {file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pydash" -version = "6.0.2" -description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pydash-6.0.2-py3-none-any.whl", hash = "sha256:6d3ce5cbbc8ca3533c12782ac201c2ec756d1e1703ec3efc88f2b95d1ed2bb31"}, - {file = "pydash-6.0.2.tar.gz", hash = "sha256:35caa588e01d293713655e0870544d25128cd414c5e19477a0d63adc2b2ca03e"}, -] - -[package.extras] -dev = ["Sphinx", "black", "build", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "importlib-metadata (<5)", "invoke", "isort", "pylint", "pytest", "pytest-cov", "sphinx-rtd-theme", "tox", "twine", "wheel"] - -[[package]] -name = "pygithub" -version = "2.5.0" -description = "Use the full Github API v3" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "PyGithub-2.5.0-py3-none-any.whl", hash = "sha256:b0b635999a658ab8e08720bdd3318893ff20e2275f6446fcf35bf3f44f2c0fd2"}, - {file = "pygithub-2.5.0.tar.gz", hash = "sha256:e1613ac508a9be710920d26eb18b1905ebd9926aa49398e88151c1b526aad3cf"}, -] - -[package.dependencies] -Deprecated = "*" -pyjwt = {version = ">=2.4.0", extras = ["crypto"]} -pynacl = ">=1.4.0" -requests = ">=2.14.0" -typing-extensions = ">=4.0.0" -urllib3 = ">=1.26.0" - -[[package]] -name = "pygments" -version = "2.19.1" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.dependencies] -cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} - -[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"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2025.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, - {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.9.4" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruff" -version = "0.4.10" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, -] - -[[package]] -name = "semver" -version = "3.0.4" -description = "Python helper for Semantic Versioning (https://semver.org)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, - {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, -] - -[[package]] -name = "simpleeval" -version = "0.9.13" -description = "A simple, safe single expression evaluator library." -optional = false -python-versions = "*" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "simpleeval-0.9.13-py2.py3-none-any.whl", hash = "sha256:22a2701a5006e4188d125d34accf2405c2c37c93f6b346f2484b6422415ae54a"}, - {file = "simpleeval-0.9.13.tar.gz", hash = "sha256:4a30f9cc01825fe4c719c785e3762623e350c4840d5e6855c2a8496baaa65fac"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "smmap" -version = "5.0.2" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "soupsieve" -version = "2.6" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, -] - -[[package]] -name = "types-beautifulsoup4" -version = "4.12.0.20241020" -description = "Typing stubs for beautifulsoup4" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "types-beautifulsoup4-4.12.0.20241020.tar.gz", hash = "sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059"}, - {file = "types_beautifulsoup4-4.12.0.20241020-py3-none-any.whl", hash = "sha256:c95e66ce15a4f5f0835f7fbc5cd886321ae8294f977c495424eaf4225307fd30"}, -] - -[package.dependencies] -types-html5lib = "*" - -[[package]] -name = "types-html5lib" -version = "1.1.11.20241018" -description = "Typing stubs for html5lib" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "types-html5lib-1.1.11.20241018.tar.gz", hash = "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa"}, - {file = "types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403"}, -] - -[[package]] -name = "types-requests" -version = "2.32.0.20241016" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, - {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2025.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wrapt" -version = "1.17.2" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, -] - -[[package]] -name = "yarl" -version = "1.18.3" -description = "Yet another URL library" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" - -[[package]] -name = "zipp" -version = "3.21.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version == \"3.11\" or python_version >= \"3.12\"" -files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11" -content-hash = "0ff88ea1d411afca3b23762cc84070b3c1ca422225fad5041bfbff0191a39b78" diff --git a/airbyte-ci/connectors/connectors_insights/pyproject.toml b/airbyte-ci/connectors/connectors_insights/pyproject.toml deleted file mode 100644 index be58b4b9d54e..000000000000 --- a/airbyte-ci/connectors/connectors_insights/pyproject.toml +++ /dev/null @@ -1,48 +0,0 @@ -[tool.poetry] -name = "connectors-insights" -version = "0.3.7" -description = "" -authors = ["Airbyte "] -readme = "README.md" -packages = [{ include = "connectors_insights", from = "src" }] - -[tool.poetry.dependencies] -python = "^3.11" -dagger-io = "0.13.3" -click = "^8.1.7" -pydantic = "^1.9" -asyncer = "^0.0.7" -asyncclick = "^8.1.7.2" -connector-ops = { path = "../connector_ops", develop = false } -beautifulsoup4 = "^4.12.3" -requests = "^2.32.3" -google-cloud-storage = "^2.17.0" -ruff = "^0.4.8" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.scripts] -connectors-insights = "connectors_insights.cli:connectors_insights" - -[tool.poetry.group.dev.dependencies] -mypy = "^1.8.0" -types-requests = "^2.32.0.20240602" -types-beautifulsoup4 = "^4.12.0.20240511" - -[tool.ruff] -line-length = 140 - -[tool.ruff.lint] -select = ["F"] - -[tool.poe.tasks] -type_check = "mypy src --disallow-untyped-defs" -lint = "ruff check src --fix" -ci = ["type_check", "lint"] - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = ["ci"] diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/__init__.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/cli.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/cli.py deleted file mode 100644 index 1587db395bd9..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/cli.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import logging -import sys -from pathlib import Path -from typing import TYPE_CHECKING - -import asyncclick as click -import asyncer -import dagger -from anyio import Semaphore -from connector_ops.utils import Connector # type: ignore - -from connectors_insights.insights import generate_insights_for_connector -from connectors_insights.result_backends import GCSBucket, LocalDir -from connectors_insights.utils import gcs_uri_to_bucket_key, get_all_connectors_in_directory, remove_strict_encrypt_suffix - -if TYPE_CHECKING: - from typing import List - - from connectors_insights.result_backends import ResultBackend - -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") -logging.getLogger("urllib3").setLevel(logging.WARNING) -logging.getLogger("httpx").setLevel(logging.WARNING) - - -@click.group -async def connectors_insights() -> None: - pass - - -@connectors_insights.command("generate", help="Generate connector insights the given connectors.") -@click.option( - "-n", - "--name", - "selected_connectors", - multiple=True, - help="The technical name of the connector. e.g. 'source-google-sheets'.", -) -@click.option( - "-d", - "--connector-directory", - "connector_directory", - type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path), - help="The directory containing the connectors, to generate insights for all connectors in this directory.", -) -@click.option( - "-o", - "--output-directory", - "output_directory", - type=click.Path(file_okay=False, path_type=Path, writable=True, dir_okay=True), - help="Path to the directory where the insights will be saved as JSON files.", -) -@click.option( - "-g", - "--gcs-uri", - "gcs_uri", - help="GCS URI to the directory where the insights will be saved as JSON files.", -) -@click.option( - "-c", - "--concurrency", - "concurrency", - type=int, - default=5, - help="The number of connectors to generate insights for concurrently.", -) -@click.option( - "--rewrite", - "rewrite", - type=bool, - is_flag=True, - default=False, - help="Whether to rewrite the report file if it already exists.", -) -async def generate( - selected_connectors: List[str], - connector_directory: Path | None, - output_directory: Path | None, - gcs_uri: str | None, - concurrency: int, - rewrite: bool, -) -> None: - logger = logging.getLogger(__name__) - result_backends: List[ResultBackend] = [] - if output_directory: - result_backends.append(LocalDir(output_directory)) - if gcs_uri: - result_backends.append(GCSBucket(*gcs_uri_to_bucket_key(gcs_uri))) - connectors: List[Connector] = [] - if selected_connectors: - connectors += [Connector(remove_strict_encrypt_suffix(connector)) for connector in selected_connectors] - if connector_directory: - connectors += get_all_connectors_in_directory(connector_directory) - - connectors = sorted(list(connectors), key=lambda connector: connector.technical_name, reverse=True) - - if not connectors: - raise click.UsageError( - "No connectors passed. Please pass at least one connector with --name or a directory containing connectors with --connector-directory." - ) - else: - logger.info(f"Generating insights for {len(connectors)} connectors.") - semaphore = Semaphore(concurrency) - soon_results = [] - async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as dagger_client: - async with asyncer.create_task_group() as connector_task_group: - for connector in connectors: - soon_results.append( - connector_task_group.soonify(generate_insights_for_connector)( - dagger_client, - connector, - semaphore, - rewrite, - result_backends=result_backends, - ) - ) - failing_connector_names = [soon_result.value[1].technical_name for soon_result in soon_results if not soon_result.value[0]] - if failing_connector_names: - raise click.ClickException("Failed to generate insights for the following connectors: " + ", ".join(failing_connector_names)) diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/hacks.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/hacks.py deleted file mode 100644 index d521a1d8b8db..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/hacks.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import json -from typing import TYPE_CHECKING - -import requests -from bs4 import BeautifulSoup -from google.cloud import storage # type: ignore - -if TYPE_CHECKING: - from typing import Dict - - from connector_ops.utils import Connector # type: ignore - - -TEST_SUMMARY_ROOT_URL = "https://connectors.airbyte.com/files/generated_reports/test_summary" - - -def get_ci_json_report(json_report_url: str) -> Dict: - storage_client = storage.Client(project="dataline-integration-testing") - bucket_name = json_report_url.split("/")[3] - blob_name = "/".join(json_report_url.split("/")[4:]) - bucket = storage_client.bucket(bucket_name) - blob = bucket.blob(blob_name) - - return json.loads(blob.download_as_string()) - - -def get_ci_on_master_report(connector: Connector) -> Dict | None: - """This is a hack because we use the HTML report to get the latest CI insights. - We should ideally fetch the json report directly but the URL has timestamp in it so it's not deterministic. - - Args: - connector (Connector): The connector to get the CI insights for. - - Returns: - Dict | None: The CI insights if found, None otherwise. - """ - url = f"{TEST_SUMMARY_ROOT_URL}/{connector.technical_name}/index.html" - - response = requests.get(url) - if response.status_code != 200: - return None - - soup = BeautifulSoup(response.content, "html.parser") - - rows = soup.find_all("tr") - - for row in rows[1:]: # Skipping the header row - columns = row.find_all("td") - if columns: - try: - report_url = columns[3].find("a")["href"] - except (IndexError, KeyError, TypeError): - continue - json_report_url = report_url.replace(".html", ".json") - # The first table entry is the latest report, but sometimes it's not there questionmark - try: - json_report = get_ci_json_report(json_report_url) - if json_report["connector_version"] == connector.version: - return json_report - except Exception as e: - continue - return None - - return None diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/insights.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/insights.py deleted file mode 100644 index 5267c88df8ce..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/insights.py +++ /dev/null @@ -1,263 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import datetime -import itertools -import json -import logging -import re -from typing import TYPE_CHECKING - -import requests - -from connectors_insights.hacks import get_ci_on_master_report -from connectors_insights.models import ConnectorInsights -from connectors_insights.pylint import get_pylint_output -from connectors_insights.result_backends import FileToPersist, ResultBackend - -if TYPE_CHECKING: - from typing import Dict, List, Tuple - - import dagger - from anyio import Semaphore - from connector_ops.utils import Connector # type: ignore - - -def get_manifest_inferred_insights(connector: Connector) -> dict: - if connector.manifest_path is None or not connector.manifest_path.exists(): - return {} - - manifest = connector.manifest_path.read_text() - - schemas_directory = connector.code_directory / connector.technical_name.replace("-", "_") / "schemas" - - return { - "manifest_uses_parameters": manifest.find("$parameters") != -1, - "manifest_uses_custom_components": manifest.find("class_name:") != -1, - "manifest_custom_component_classes": re.findall(r"class_name: (.+)", manifest), - "has_json_schemas": schemas_directory.is_dir() and any(schemas_directory.iterdir()), - } - - -def get_metadata_inferred_insights(connector: Connector) -> Dict: - return { - "connector_technical_name": connector.technical_name, - "connector_version": connector.version, - "connector_image_address": connector.image_address, - "uses_base_image": connector.uses_base_image, - "base_image_address": connector.base_image_address, - "base_image_version": connector.base_image_version, - "connector_language": connector.language, - "cdk_name": connector.cdk_name, - "is_using_poetry": connector.is_using_poetry, - "connector_definition_id": connector.metadata.get("definitionId"), - "connector_type": connector.metadata.get("connectorType"), - "connector_subtype": connector.metadata.get("connectorSubtype"), - "connector_support_level": connector.metadata.get("supportLevel"), - "ab_internal_sl": connector.metadata.get("ab_internal", {}).get("sl"), - "ab_internal_ql": connector.metadata.get("ab_internal", {}).get("ql"), - "is_cloud_enabled": connector.metadata.get("registryOverrides", {}).get("cloud", {}).get("enabled", False), - "is_oss_enabled": connector.metadata.get("registryOverrides", {}).get("oss", {}).get("enabled", False), - } - - -def get_sbom_inferred_insights(raw_sbom: str | None, connector: Connector) -> Dict: - """Parse the SBOM and get dependencies and CDK version from it. - - Args: - raw_sbom (str | None): the SBOM in JSON format. - connector (Connector): the connector to get insights for. - - Returns: - Dict: the inferred insights from the SBOM. - """ - sbom_inferred_insights: Dict[str, List[Dict[str, str]] | None] = { - "cdk_version": None, - "dependencies": [], - } - if not raw_sbom: - return sbom_inferred_insights - sbom = json.loads(raw_sbom) - python_artifacts = {package["name"]: package for package in sbom["packages"] if package["SPDXID"].startswith("SPDXRef-Package-python-")} - sbom_inferred_insights["cdk_version"] = python_artifacts.get("airbyte-cdk", {}).get("versionInfo") - - for package in sbom["packages"]: - package_type = package["SPDXID"].split("-")[2] - try: - dependency = {"type": package_type, "version": package["versionInfo"], "package_name": package["name"]} - except KeyError: - continue - if isinstance(sbom_inferred_insights["dependencies"], list) and dependency not in sbom_inferred_insights["dependencies"]: - sbom_inferred_insights["dependencies"].append(dependency) - return sbom_inferred_insights - - -def get_pylint_inferred_insights(pylint_output: str | None) -> Dict: - """Make insights from the pylint output. - It currently parses the deprecated classes and modules from the pylint output. - """ - if not pylint_output: - return {} - - deprecated_classes_in_use = [] - deprecated_modules_in_use = [] - forbidden_method_names_in_use = [] - deprecated_class_pattern = r"Using deprecated class (\w+) of module ([\w\.]+)" - deprecated_module_pattern = r"Deprecated module '([^']+)'" - forbidden_method_name_pattern = r'Method name "([^"]+)"' - for message in json.loads(pylint_output): - if message["symbol"] == "deprecated-class": - if match := re.search(deprecated_class_pattern, message["message"]): - deprecated_classes_in_use.append(f"{match.group(2)}.{match.group(1)}") - if message["symbol"] == "deprecated-module": - if match := re.search(deprecated_module_pattern, message["message"]): - deprecated_modules_in_use.append(match.group(1)) - if message["symbol"] == "forbidden-method-name": - if match := re.search(forbidden_method_name_pattern, message["message"]): - forbidden_method_names_in_use.append(match.group(1)) - return { - "deprecated_classes_in_use": deprecated_classes_in_use, - "deprecated_modules_in_use": deprecated_modules_in_use, - "forbidden_method_names_in_use": forbidden_method_names_in_use, - } - - -def should_skip_generation( - result_backends: List[ResultBackend] | None, connector: Connector, files_to_persist: List[FileToPersist], rewrite: bool -) -> bool: - """Check if the insights generation should be skipped because they already exist. - Always run if rewrite is True or no result backends are provided. - - Args: - result_backends (List[ResultBackend] | None): The result backends to check if the insights already exist. - connector (Connector): The connector to check if the insights already exist. - files_to_persist (List[FileToPersist]): The files to persist for the connector. - rewrite (bool): Whether to rewrite the insights if they already exist. - - Returns: - bool: True if the insights generation should be skipped, False otherwise. - """ - if rewrite or not result_backends: - return False - - for result_backend, file_to_persist in itertools.product(result_backends, files_to_persist): - if not result_backend.artifact_already_exists(connector, file_to_persist): - return False - return True - - -def fetch_sbom(connector: Connector) -> str | None: - """Fetch the SBOM for the connector if it is released. - SBOM are generated from published Docker images. If the connector is not released it does not have a published Docker image. - - Args: - dagger_client (dagger.Client): The Dagger client to use. - connector (Connector): The connector to fetch the SBOM for. - - Returns: - str | None: The SBOM in JSON format if the connector is released, None otherwise. - """ - if connector.sbom_url: - r = requests.get(connector.sbom_url) - r.raise_for_status() - return r.text - return None - - -def generate_insights(connector: Connector, sbom: str | None, pylint_output: str | None) -> ConnectorInsights: - """Generate insights for the connector. - - Args: - connector (Connector): The connector to generate insights for. - sbom (str | None): The SBOM in JSON format. - - Returns: - ConnectorInsights: The insights for the connector. - """ - ci_on_master_report = get_ci_on_master_report(connector) - return ConnectorInsights( - **{ - **get_metadata_inferred_insights(connector), - **get_manifest_inferred_insights(connector), - **get_pylint_inferred_insights(pylint_output), - **get_sbom_inferred_insights(sbom, connector), - "ci_on_master_report": ci_on_master_report, - "ci_on_master_passes": ci_on_master_report.get("success") if ci_on_master_report else None, - "insight_generation_timestamp": datetime.datetime.utcnow(), - } - ) - - -# TODO: make it async for concurrent uploads -def persist_files( - connector: Connector, - files_to_persist: List[FileToPersist], - result_backends: List[ResultBackend] | None, - rewrite: bool, - logger: logging.Logger, -) -> None: - """Persist the files to the result backends. - - Args: - connector (Connector): The connector to persist the files for. - files_to_persist (List[FileToPersist]): The files to persist for the connector. - result_backends (List[ResultBackend] | None): The result backends to persist the files to. - rewrite (bool): Whether to rewrite the files if they already exist. - logger (logging.Logger): The logger to use. - - Returns: - None - """ - if not result_backends: - logger.warning(f"No result backends provided to persist files for {connector.technical_name}") - return None - for backend, file in itertools.product(result_backends, files_to_persist): - if file.file_content: - if not rewrite and backend.artifact_already_exists(connector, file): - logger.info(f"Skipping writing {file.file_name} for {connector.technical_name} because it already exists.") - continue - backend.write(connector, file) - else: - logger.warning(f"No content provided for {file.file_name} for {connector.technical_name}") - - -async def generate_insights_for_connector( - dagger_client: dagger.Client, - connector: Connector, - semaphore: Semaphore, - rewrite: bool = False, - result_backends: List[ResultBackend] | None = None, -) -> Tuple[bool, Connector]: - """Aggregate insights for a connector and write them to the result backends. - - Args: - dagger_client (dagger.Client): the dagger client. - connector (Connector): the connector to generate insights for. - semaphore (Semaphore): the semaphore to limit the number of concurrent insights generation. - rewrite (bool): whether to rewrite the insights if they already exist. - result_backend (List[ResultBackend] | None): the result backends to write the insights to. - Returns: - Tuple[bool, Connector]: a tuple of whether the insights were generated and the connector. - """ - logger = logging.getLogger(__name__) - insights_file = FileToPersist("insights.json") - files_to_persist = [insights_file] - - async with semaphore: - if should_skip_generation(result_backends, connector, files_to_persist, rewrite): - logger.info(f"Skipping insights generation for {connector.technical_name} because it is already generated.") - return True, connector - - logger.info(f"Generating insights for {connector.technical_name}") - result_backends = result_backends or [] - try: - pylint_output = await get_pylint_output(dagger_client, connector) - raw_sbom = fetch_sbom(connector) - insights = generate_insights(connector, raw_sbom, pylint_output) - insights_file.set_file_content(insights.json()) - persist_files(connector, files_to_persist, result_backends, rewrite, logger) - logger.info(f"Finished generating insights for {connector.technical_name}") - return True, connector - except Exception as e: - logger.error(f"Failed to generate insights for {connector.technical_name}: {e}") - return False, connector diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/models.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/models.py deleted file mode 100644 index 570982251856..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/models.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import json -from datetime import datetime - -from connector_ops.utils import ConnectorLanguage # type: ignore -from pydantic import BaseModel, StrictBool -from pydantic.json import pydantic_encoder - - -class ConnectorInsights(BaseModel): - insight_generation_timestamp: datetime - connector_definition_id: str - connector_type: str - connector_subtype: str | None - connector_technical_name: str - connector_version: str - connector_image_address: str - connector_support_level: str | None - ab_internal_sl: int | None - ab_internal_ql: int | None - cdk_name: str | None - cdk_version: str | None - connector_language: ConnectorLanguage | None - ci_on_master_report: dict | None - ci_on_master_passes: StrictBool | None - uses_base_image: StrictBool - base_image_address: str | None - base_image_version: str | None - is_using_poetry: StrictBool - dependencies: list[dict] - is_cloud_enabled: StrictBool - is_oss_enabled: StrictBool - deprecated_classes_in_use: list[str] | None - deprecated_modules_in_use: list[str] | None - forbidden_method_names_in_use: list[str] | None - manifest_uses_parameters: StrictBool | None - manifest_uses_custom_components: StrictBool | None - manifest_custom_component_classes: list[str] | None - has_json_schemas: StrictBool | None - - class Config: - json_encoders = {dict: lambda v: json.dumps(v, default=pydantic_encoder)} diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint.py deleted file mode 100644 index eef3fe1cd086..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import os -from pathlib import Path -from typing import TYPE_CHECKING - -from connector_ops.utils import ConnectorLanguage # type: ignore - -from connectors_insights.utils import never_fail_exec - -if TYPE_CHECKING: - import dagger - from connector_ops.utils import Connector # type: ignore - -PYLINT_COMMAND = [ - "pylint", - "--load-plugins=custom_plugin", - "--disable=all", - "--output-format=json", - "--enable=deprecated-class", - "--enable=deprecated-module", - "--enable=forbidden-method-name", - ".", -] - - -async def get_pylint_output(dagger_client: dagger.Client, connector: Connector) -> str | None: - """Invoke pylint to check for deprecated classes and modules in the connector code. - We use the custom plugin cdk_deprecation_checkers.py to check for deprecated classes and modules. - The plugin is located in the `pylint_plugins` directory. - - Args: - dagger_client (dagger.Client): Current dagger client. - connector (Connector): Connector object. - - Returns: - str | None: Pylint output. - """ - if connector.language not in [ConnectorLanguage.PYTHON, ConnectorLanguage.LOW_CODE, ConnectorLanguage.MANIFEST_ONLY]: - return None - cdk_deprecation_checker_path = Path(os.path.abspath(__file__)).parent / "pylint_plugins/cdk_deprecation_checkers.py" - pip_cache_volume: dagger.CacheVolume = dagger_client.cache_volume("pip_cache") - - return await ( - dagger_client.container() - .from_(connector.image_address) - .with_user("root") - .with_mounted_cache("/root/.cache/pip", pip_cache_volume) - .with_new_file("__init__.py", contents="") - .with_exec(["pip", "install", "pylint"]) - .with_workdir(connector.technical_name.replace("-", "_")) - .with_env_variable("PYTHONPATH", ".") - .with_new_file("custom_plugin.py", contents=cdk_deprecation_checker_path.read_text()) - .with_(never_fail_exec(PYLINT_COMMAND)) - .stdout() - ) diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint_plugins/cdk_deprecation_checkers.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint_plugins/cdk_deprecation_checkers.py deleted file mode 100644 index a961e1a3e223..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/pylint_plugins/cdk_deprecation_checkers.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -from collections import namedtuple -from typing import TYPE_CHECKING - -from pylint.checkers import BaseChecker, DeprecatedMixin # type: ignore - -if TYPE_CHECKING: - from typing import Set - - import astroid # type: ignore - from pylint.lint import PyLinter # type: ignore - -DeprecatedClass = namedtuple("DeprecatedClass", ["module", "name"]) - -DEPRECATED_CLASSES: Set[DeprecatedClass] = { - DeprecatedClass("airbyte_cdk.logger", "AirbyteLogger"), - DeprecatedClass(None, "GoogleAnalyticsDataApiBaseStream"), -} - -DEPRECATED_MODULES = {"airbyte_cdk.sources.streams.http.auth"} - -FORBIDDEN_METHOD_NAMES = {"get_updated_state"} - - -class ForbiddenMethodNameChecker(BaseChecker): - name = "forbidden-method-name-checker" - msgs = { - "C9001": ('Method name "%s" is forbidden', "forbidden-method-name", "Used when a forbidden method name is detected."), - } - - def visit_functiondef(self, node: astroid.node) -> None: - if node.name in FORBIDDEN_METHOD_NAMES: - self.add_message("forbidden-method-name", node=node, args=(node.name,)) - - -class DeprecationChecker(DeprecatedMixin, BaseChecker): - """Check for deprecated classes and modules. - The DeprecatedMixin class is here: - https://github.com/pylint-dev/pylint/blob/a5a77f6e891f6e143439d19b5e7f0a29eb5ea1cd/pylint/checkers/deprecated.py#L31 - """ - - name = "deprecated" - - msgs = { - **DeprecatedMixin.DEPRECATED_METHOD_MESSAGE, - **DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE, - **DeprecatedMixin.DEPRECATED_CLASS_MESSAGE, - **DeprecatedMixin.DEPRECATED_MODULE_MESSAGE, - } - - def deprecated_modules(self) -> set[str]: - """Callback method called by DeprecatedMixin for every module found in the code. - - Returns: - collections.abc.Container of deprecated module names. - """ - return DEPRECATED_MODULES - - def deprecated_classes(self, module: str) -> set[str]: - """Callback method called by DeprecatedMixin for every class found in the code. - - Returns: - collections.abc.Container of deprecated class names. - """ - _deprecated_classes = set() - for deprecated_class in DEPRECATED_CLASSES: - if deprecated_class.module is None or deprecated_class.module == module: - _deprecated_classes.add(deprecated_class.name) - return _deprecated_classes - - -def register(linter: PyLinter) -> None: - linter.register_checker(DeprecationChecker(linter)) - linter.register_checker(ForbiddenMethodNameChecker(linter)) diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/result_backends.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/result_backends.py deleted file mode 100644 index 28c32d622417..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/result_backends.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging -from abc import ABC, abstractmethod -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING - -from connector_ops.utils import Connector # type: ignore -from google.cloud import storage # type: ignore - -if TYPE_CHECKING: - from typing import Any - - -@dataclass -class FileToPersist: - file_name: str - file_content: str | None = None - - def set_file_content(self, file_content: str) -> None: - self.file_content = file_content - - -class ResultBackend(ABC): - def __init__(self, *args: Any, **kwargs: Any): - self.logger = logging.getLogger(self.__class__.__name__) - - def write(self, connector: Connector, file_to_persist: FileToPersist) -> None: - if not file_to_persist.file_content: - raise ValueError("File content must be set before writing to local directory") - self._write(connector, file_to_persist) - - @abstractmethod - def _write(self, connector: Connector, file_to_persist: FileToPersist) -> None: - raise NotImplementedError("write method must be implemented by subclass") - - @abstractmethod - def artifact_already_exists(self, connector: Connector, file_to_persist: FileToPersist) -> bool: - raise NotImplementedError("insights_already_exist method must be implemented by subclass") - - -class LocalDir(ResultBackend): - def __init__(self, local_directory: Path): - super().__init__() - if not local_directory.exists(): - local_directory.mkdir(parents=True) - - self.local_directory = local_directory - - def _write(self, connector: Connector, file_to_persist: FileToPersist) -> None: - assert file_to_persist.file_content is not None - connector_result_directory = self.local_directory / connector.technical_name / connector.version - connector_result_directory.mkdir(parents=True, exist_ok=True) - file_path = connector_result_directory / file_to_persist.file_name - with open(file_path, "w") as f: - f.write(file_to_persist.file_content) - self.logger.info(f"{file_to_persist.file_name} written to {file_path}") - - def artifact_already_exists(self, connector: Connector, file_to_persist: FileToPersist) -> bool: - connector_result_directory = self.local_directory / connector.technical_name / connector.version - return (connector_result_directory / file_to_persist.file_name).exists() - - -class GCSBucket(ResultBackend): - DEFAULT_GCP_PROJECT = "prod-ab-cloud-proj" - - def __init__(self, bucket_name: str, key_prefix: str, gcp_project: str = DEFAULT_GCP_PROJECT): - super().__init__() - self.bucket_name = bucket_name - self.key_prefix = key_prefix - self.storage_client = storage.Client(project=gcp_project) - self.bucket = self.storage_client.bucket(self.bucket_name) - - def _write(self, connector: Connector, file_to_persist: FileToPersist) -> None: - assert file_to_persist.file_content is not None - version_blob_prefix = f"{self.key_prefix}/{connector.technical_name}/{connector.version}" - latest_blob_prefix = f"{self.key_prefix}/{connector.technical_name}/latest" - for blob_prefix in [version_blob_prefix, latest_blob_prefix]: - blob = self.bucket.blob(f"{blob_prefix}/{file_to_persist.file_name}") - blob.upload_from_string(file_to_persist.file_content) - self.logger.info(f"{file_to_persist.file_name} written to {blob.public_url}") - - def artifact_already_exists(self, connector: Connector, file_to_persist: FileToPersist) -> bool: - blob_prefix = f"{self.key_prefix}/{connector.technical_name}/{connector.version}" - return self.bucket.blob(f"{blob_prefix}/{file_to_persist.file_name}").exists() diff --git a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/utils.py b/airbyte-ci/connectors/connectors_insights/src/connectors_insights/utils.py deleted file mode 100644 index b68e62b61670..000000000000 --- a/airbyte-ci/connectors/connectors_insights/src/connectors_insights/utils.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import re -from pathlib import Path -from typing import TYPE_CHECKING - -import requests - -if TYPE_CHECKING: - from typing import Callable, List, Set, Tuple - - from dagger import Container - -from connector_ops.utils import METADATA_FILE_NAME, Connector # type: ignore - - -def remove_strict_encrypt_suffix(connector_technical_name: str) -> str: - """Remove the strict encrypt suffix from a connector name. - - Args: - connector_technical_name (str): the connector name. - - Returns: - str: the connector name without the strict encrypt suffix. - """ - strict_encrypt_suffixes = [ - "-strict-encrypt", - "-secure", - ] - - for suffix in strict_encrypt_suffixes: - if connector_technical_name.endswith(suffix): - new_connector_technical_name = connector_technical_name.replace(suffix, "") - return new_connector_technical_name - return connector_technical_name - - -def get_all_connectors_in_directory(directory: Path) -> Set[Connector]: - """Get all connectors in a directory. - - Args: - directory (Path): the directory to search for connectors. - - Returns: - List[Connector]: the list of connectors in the directory. - """ - connectors = [] - for connector_directory in directory.iterdir(): - if ( - connector_directory.is_dir() - and (connector_directory / METADATA_FILE_NAME).exists() - and "scaffold" not in str(connector_directory) - ): - connectors.append(Connector(remove_strict_encrypt_suffix(connector_directory.name))) - return set(connectors) - - -def gcs_uri_to_bucket_key(gcs_uri: str) -> Tuple[str, str]: - # Ensure the GCS URI follows the expected pattern - match = re.match(r"^gs://([^/]+)/(.+)$", gcs_uri) - if not match: - raise ValueError(f"Invalid GCS URI: {gcs_uri}") - - bucket, key = match.groups() - return bucket, key - - -def never_fail_exec(command: List[str]) -> Callable[[Container], Container]: - """ - Wrap a command execution with some bash sugar to always exit with a 0 exit code but write the actual exit code to a file. - - Underlying issue: - When a classic dagger with_exec is returning a >0 exit code an ExecError is raised. - It's OK for the majority of our container interaction. - But some execution, like running CAT, are expected to often fail. - In CAT we don't want ExecError to be raised on container interaction because CAT might write updated secrets that we need to pull from the container after the test run. - The bash trick below is a hack to always return a 0 exit code but write the actual exit code to a file. - The file is then read by the pipeline to determine the exit code of the container. - - Args: - command (List[str]): The command to run in the container. - - Returns: - Callable: _description_ - """ - - def never_fail_exec_inner(container: Container) -> Container: - return container.with_exec(["sh", "-c", f"{' '.join(command)}; echo $? > /exit_code"]) - - return never_fail_exec_inner diff --git a/airbyte-ci/connectors/erd/README.md b/airbyte-ci/connectors/erd/README.md deleted file mode 100644 index 7623b907d5ff..000000000000 --- a/airbyte-ci/connectors/erd/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# erd - -A collection of utilities for generating ERDs. - -# Setup - -## Installation - -`erd` tools use [Poetry](https://github.com/python-poetry/poetry) to manage dependencies, -and targets Python 3.11 and higher. - -Assuming you're in Airbyte repo root: - -```bash -cd airbyte-ci/connectors/erd -poetry install -``` - -## Usage - -Pre-requisites: -* Env variable `GENAI_API_KEY`. Can be found at URL https://aistudio.google.com/app/apikey - -`poetry run erd --source-path --source-technical-name ` - -The script supports the option to ignore the LLM generation by passing parameter `--skip-llm-relationships` - -## Contributing to `erd` - -### Running tests - -To run tests locally: - -```bash -poetry run pytest -``` - -## Changelog -- 0.1.1: Update Python version requirement from 3.10 to 3.11. -- 0.1.0: Initial commit diff --git a/airbyte-ci/connectors/erd/poetry.lock b/airbyte-ci/connectors/erd/poetry.lock deleted file mode 100644 index f2fe4611891b..000000000000 --- a/airbyte-ci/connectors/erd/poetry.lock +++ /dev/null @@ -1,2369 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "airbyte-cdk" -version = "6.36.4" -description = "A framework for writing Airbyte Connectors." -optional = false -python-versions = "<3.13,>=3.10" -groups = ["main"] -files = [ - {file = "airbyte_cdk-6.36.4-py3-none-any.whl", hash = "sha256:a8c30929188737c80fccc660fdaef7d36893c6a7491b338761bcfe7e6a62fefa"}, - {file = "airbyte_cdk-6.36.4.tar.gz", hash = "sha256:3e0530c9209c2ab778e8414a24b3c08b90768e9e3707e00b95dd1f351b4945e0"}, -] - -[package.dependencies] -airbyte-protocol-models-dataclasses = ">=0.14,<0.15" -backoff = "*" -cachetools = "*" -cryptography = ">=42.0.5,<44.0.0" -dpath = ">=2.1.6,<3.0.0" -dunamai = ">=1.22.0,<2.0.0" -genson = "1.3.0" -isodate = ">=0.6.1,<0.7.0" -Jinja2 = ">=3.1.2,<3.2.0" -jsonref = ">=0.2,<0.3" -jsonschema = ">=4.17.3,<4.18.0" -langchain_core = "0.1.42" -nltk = "3.9.1" -numpy = "<2" -orjson = ">=3.10.7,<4.0.0" -pandas = "2.2.2" -psutil = "6.1.0" -pydantic = ">=2.7,<3.0" -pyjwt = ">=2.8.0,<3.0.0" -pyrate-limiter = ">=3.1.0,<3.2.0" -python-dateutil = ">=2.9.0,<3.0.0" -python-ulid = ">=3.0.0,<4.0.0" -pytz = "2024.2" -PyYAML = ">=6.0.1,<7.0.0" -rapidfuzz = ">=3.10.1,<4.0.0" -requests = "*" -requests_cache = "*" -serpyco-rs = ">=1.10.2,<2.0.0" -Unidecode = ">=1.3,<2.0" -wcmatch = "10.0" -whenever = ">=0.6.16,<0.7.0" -xmltodict = ">=0.13,<0.15" - -[package.extras] -file-based = ["avro (>=1.11.2,<1.13.0)", "fastavro (>=1.8.0,<1.9.0)", "markdown", "pdf2image (==1.16.3)", "pdfminer.six (==20221105)", "pyarrow (>=15.0.0,<15.1.0)", "pytesseract (==0.3.10)", "python-calamine (==0.2.3)", "python-snappy (==0.7.3)", "unstructured.pytesseract (>=0.3.12)", "unstructured[docx,pptx] (==0.10.27)"] -sql = ["sqlalchemy (>=2.0,!=2.0.36,<3.0)"] -vector-db-based = ["cohere (==4.21)", "langchain (==0.1.16)", "openai[embeddings] (==0.27.9)", "tiktoken (==0.8.0)"] - -[[package]] -name = "airbyte-protocol-models-dataclasses" -version = "0.14.2" -description = "Declares the Airbyte Protocol using Python Dataclasses. Dataclasses in Python have less performance overhead compared to Pydantic models, making them a more efficient choice for scenarios where speed and memory usage are critical" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "airbyte_protocol_models_dataclasses-0.14.2-py3-none-any.whl", hash = "sha256:ae06a406df031afa42f1156bacc587958197e5c7d9bbaf11893480903d4ded8b"}, - {file = "airbyte_protocol_models_dataclasses-0.14.2.tar.gz", hash = "sha256:9279237156b722cdd54e7b9ec8f97d264bd96e3f3008bc5fc47c215288a2212a"}, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "attributes-doc" -version = "0.4.0" -description = "PEP 224 implementation" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "attributes-doc-0.4.0.tar.gz", hash = "sha256:b1576c94a714e9fc2c65c47cf10d0c8e1a5f7c4f5ae7f69006be108d95cbfbfb"}, - {file = "attributes_doc-0.4.0-py2.py3-none-any.whl", hash = "sha256:4c3007d9e58f3a6cb4b9c614c4d4ce2d92161581f28e594ddd8241cc3a113bdd"}, -] - -[[package]] -name = "attrs" -version = "24.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, -] - -[package.extras] -benchmark = ["cloudpickle", "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 = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "bracex" -version = "2.5" -description = "Bash style brace expander." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "bracex-2.5-py3-none-any.whl", hash = "sha256:d2fcf4b606a82ac325471affe1706dd9bbaa3536c91ef86a31f6b766f3dad1d0"}, - {file = "bracex-2.5.tar.gz", hash = "sha256:0725da5045e8d37ea9592ab3614d8b561e22c3c5fde3964699be672e072ab611"}, -] - -[[package]] -name = "cachetools" -version = "5.4.0" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, -] - -[[package]] -name = "cattrs" -version = "23.2.3" -description = "Composable complex class support for attrs and dataclasses." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cattrs-23.2.3-py3-none-any.whl", hash = "sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108"}, - {file = "cattrs-23.2.3.tar.gz", hash = "sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f"}, -] - -[package.dependencies] -attrs = ">=23.1.0" - -[package.extras] -bson = ["pymongo (>=4.4.0)"] -cbor2 = ["cbor2 (>=5.4.6)"] -msgpack = ["msgpack (>=1.0.5)"] -orjson = ["orjson (>=3.9.2)"] -pyyaml = ["pyyaml (>=6.0)"] -tomlkit = ["tomlkit (>=0.11.8)"] -ujson = ["ujson (>=5.7.0)"] - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {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.17.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {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] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "cryptography" -version = "42.0.8" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, - {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, - {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, - {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, - {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, - {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, - {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "dpath" -version = "2.2.0" -description = "Filesystem-like pathing and searching for dictionaries" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576"}, - {file = "dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e"}, -] - -[[package]] -name = "dunamai" -version = "1.23.0" -description = "Dynamic version generation" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041"}, - {file = "dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4"}, -] - -[package.dependencies] -packaging = ">=20.9" - -[[package]] -name = "genson" -version = "1.3.0" -description = "GenSON is a powerful, user-friendly JSON Schema generator." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, - {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, -] - -[[package]] -name = "google-ai-generativelanguage" -version = "0.6.6" -description = "Google Ai Generativelanguage API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-ai-generativelanguage-0.6.6.tar.gz", hash = "sha256:1739f035caeeeca5c28f887405eec8690f3372daf79fecf26454a97a4f1733a8"}, - {file = "google_ai_generativelanguage-0.6.6-py3-none-any.whl", hash = "sha256:59297737931f073d55ce1268dcc6d95111ee62850349d2b6cde942b16a4fca5c"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[[package]] -name = "google-api-core" -version = "2.19.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, - {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-api-python-client" -version = "2.141.0" -description = "Google API Client Library for Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_api_python_client-2.141.0-py2.py3-none-any.whl", hash = "sha256:43c05322b91791204465291b3852718fae38d4f84b411d8be847c4f86882652a"}, - {file = "google_api_python_client-2.141.0.tar.gz", hash = "sha256:0f225b1f45d5a6f8c2a400f48729f5d6da9a81138e81e0478d61fdd8edf6563a"}, -] - -[package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" -google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" -google-auth-httplib2 = ">=0.2.0,<1.0.0" -httplib2 = ">=0.19.0,<1.dev0" -uritemplate = ">=3.0.1,<5" - -[[package]] -name = "google-auth" -version = "2.33.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_auth-2.33.0-py2.py3-none-any.whl", hash = "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df"}, - {file = "google_auth-2.33.0.tar.gz", hash = "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-auth-httplib2" -version = "0.2.0" -description = "Google Authentication Library: httplib2 transport" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, - {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, -] - -[package.dependencies] -google-auth = "*" -httplib2 = ">=0.19.0" - -[[package]] -name = "google-generativeai" -version = "0.7.2" -description = "Google Generative AI High level API client library and tools." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "google_generativeai-0.7.2-py3-none-any.whl", hash = "sha256:3117d1ebc92ee77710d4bc25ab4763492fddce9b6332eb25d124cf5d8b78b339"}, -] - -[package.dependencies] -google-ai-generativelanguage = "0.6.6" -google-api-core = "*" -google-api-python-client = "*" -google-auth = ">=2.15.0" -protobuf = "*" -pydantic = "*" -tqdm = "*" -typing-extensions = "*" - -[package.extras] -dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"] - -[[package]] -name = "googleapis-common-protos" -version = "1.63.2" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, - {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, -] - -[package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "grpcio" -version = "1.65.4" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, - {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, - {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, - {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, - {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, - {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, - {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, - {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, - {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, - {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, - {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, - {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, - {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, - {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, - {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, - {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, - {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, - {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, - {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, - {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, - {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.65.4)"] - -[[package]] -name = "grpcio-status" -version = "1.62.3" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, - {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.3" -protobuf = ">=4.21.6" - -[[package]] -name = "httplib2" -version = "0.22.0" -description = "A comprehensive HTTP client library." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, - {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, -] - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isodate" -version = "0.6.1" -description = "An ISO 8601 date/time/duration parser and formatter" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "joblib" -version = "1.4.2" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, - {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, -] - -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main"] -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "3.0.0" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, - {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, -] - -[[package]] -name = "jsonref" -version = "0.2" -description = "An implementation of JSON Reference for Python" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "jsonref-0.2-py3-none-any.whl", hash = "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f"}, - {file = "jsonref-0.2.tar.gz", hash = "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"}, -] - -[[package]] -name = "jsonschema" -version = "4.17.3" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, - {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, -] - -[package.dependencies] -attrs = ">=17.4.0" -pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" - -[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)"] - -[[package]] -name = "langchain-core" -version = "0.1.42" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.8.1" -groups = ["main"] -files = [ - {file = "langchain_core-0.1.42-py3-none-any.whl", hash = "sha256:c5653ffa08a44f740295c157a24c0def4a753333f6a2c41f76bf431cd00be8b5"}, - {file = "langchain_core-0.1.42.tar.gz", hash = "sha256:40751bf60ea5d8e2b2efe65290db434717ee3834870c002e40e2811f09d814e6"}, -] - -[package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.0,<0.2.0" -packaging = ">=23.2,<24.0" -pydantic = ">=1,<3" -PyYAML = ">=5.3" -tenacity = ">=8.1.0,<9.0.0" - -[package.extras] -extended-testing = ["jinja2 (>=3,<4)"] - -[[package]] -name = "langsmith" -version = "0.1.99" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -groups = ["main"] -files = [ - {file = "langsmith-0.1.99-py3-none-any.whl", hash = "sha256:ef8d1d74a2674c514aa429b0171a9fbb661207dc3835142cca0e8f1bf97b26b0"}, - {file = "langsmith-0.1.99.tar.gz", hash = "sha256:b5c6a1f158abda61600a4a445081ee848b4a28b758d91f2793dc02aeffafcaf1"}, -] - -[package.dependencies] -orjson = ">=3.9.14,<4.0.0" -pydantic = {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""} -requests = ">=2,<3" - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mypy" -version = "1.11.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {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" -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nltk" -version = "3.9.1" -description = "Natural Language Toolkit" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, - {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, -] - -[package.dependencies] -click = "*" -joblib = "*" -regex = ">=2021.8.3" -tqdm = "*" - -[package.extras] -all = ["matplotlib", "numpy", "pyparsing", "python-crfsuite", "requests", "scikit-learn", "scipy", "twython"] -corenlp = ["requests"] -machine-learning = ["numpy", "python-crfsuite", "scikit-learn", "scipy"] -plot = ["matplotlib"] -tgrep = ["pyparsing"] -twitter = ["twython"] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -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"}, -] - -[[package]] -name = "orjson" -version = "3.10.7" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, - {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, - {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, - {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, - {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, - {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, - {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, - {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, - {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, - {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, - {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, - {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, - {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, - {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, - {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, - {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, - {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, - {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, - {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, - {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pandas" -version = "2.2.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, -] - -[package.dependencies] -numpy = {version = ">=1.23.2", markers = "python_version == \"3.11\""} -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "proto-plus" -version = "1.24.0" -description = "Beautiful, Pythonic protocol buffers." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "4.25.4" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, - {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, - {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, - {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, - {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, - {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, - {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, - {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, - {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, -] - -[[package]] -name = "psutil" -version = "6.1.0" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -groups = ["main"] -files = [ - {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, - {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, - {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, - {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, - {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, - {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, - {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, - {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, -] - -[package.extras] -dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] -test = ["pytest", "pytest-xdist", "setuptools"] - -[[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.0" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "2.8.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.20.1" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydbml" -version = "1.1.1" -description = "Python parser and builder for DBML" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydbml-1.1.1-py3-none-any.whl", hash = "sha256:dbc0832fc9d24527ed004f4c8d8306cf7a06db4928ec7d5b73d864ab609225f5"}, - {file = "pydbml-1.1.1.tar.gz", hash = "sha256:a9833f93d20a5b48c032bdc8d4f151ca3bd6d8a97ce83301d211810d0b2a16cc"}, -] - -[package.dependencies] -pyparsing = ">=3.0.0" - -[[package]] -name = "pyjwt" -version = "2.9.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {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", "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 = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -groups = ["main"] -files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyrate-limiter" -version = "3.1.1" -description = "Python Rate-Limiter using Leaky-Bucket Algorithm" -optional = false -python-versions = ">=3.8,<4.0" -groups = ["main"] -files = [ - {file = "pyrate_limiter-3.1.1-py3-none-any.whl", hash = "sha256:c51906f1d51d56dc992ff6c26e8300e32151bc6cfa3e6559792e31971dfd4e2b"}, - {file = "pyrate_limiter-3.1.1.tar.gz", hash = "sha256:2f57eda712687e6eccddf6afe8f8a15b409b97ed675fe64a626058f12863b7b7"}, -] - -[package.extras] -all = ["filelock (>=3.0)", "redis (>=5.0.0,<6.0.0)"] -docs = ["furo (>=2022.3.4,<2023.0.0)", "myst-parser (>=0.17)", "sphinx (>=4.3.0,<5.0.0)", "sphinx-autodoc-typehints (>=1.17,<2.0)", "sphinx-copybutton (>=0.5)", "sphinxcontrib-apidoc (>=0.3,<0.4)"] - -[[package]] -name = "pyrsistent" -version = "0.20.0" -description = "Persistent/Functional/Immutable data structures" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, - {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, - {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, - {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, - {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, - {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, - {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, -] - -[[package]] -name = "pytest" -version = "8.3.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-ulid" -version = "3.0.0" -description = "Universally unique lexicographically sortable identifier" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, - {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, -] - -[package.extras] -pydantic = ["pydantic (>=2.0)"] - -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "rapidfuzz" -version = "3.12.1" -description = "rapid fuzzy string matching" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbb7ea2fd786e6d66f225ef6eef1728832314f47e82fee877cb2a793ebda9579"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ae41361de05762c1eaa3955e5355de7c4c6f30d1ef1ea23d29bf738a35809ab"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc3c39e0317e7f68ba01bac056e210dd13c7a0abf823e7b6a5fe7e451ddfc496"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69f2520296f1ae1165b724a3aad28c56fd0ac7dd2e4cff101a5d986e840f02d4"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34dcbf5a7daecebc242f72e2500665f0bde9dd11b779246c6d64d106a7d57c99"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:773ab37fccf6e0513891f8eb4393961ddd1053c6eb7e62eaa876e94668fc6d31"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ecf0e6de84c0bc2c0f48bc03ba23cef2c5f1245db7b26bc860c11c6fd7a097c"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4dc2ebad4adb29d84a661f6a42494df48ad2b72993ff43fad2b9794804f91e45"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8389d98b9f54cb4f8a95f1fa34bf0ceee639e919807bb931ca479c7a5f2930bf"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:165bcdecbfed9978962da1d3ec9c191b2ff9f1ccc2668fbaf0613a975b9aa326"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:129d536740ab0048c1a06ccff73c683f282a2347c68069affae8dbc423a37c50"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b67e390261ffe98ec86c771b89425a78b60ccb610c3b5874660216fcdbded4b"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win32.whl", hash = "sha256:a66520180d3426b9dc2f8d312f38e19bc1fc5601f374bae5c916f53fa3534a7d"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:82260b20bc7a76556cecb0c063c87dad19246a570425d38f8107b8404ca3ac97"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win_arm64.whl", hash = "sha256:3a860d103bbb25c69c2e995fdf4fac8cb9f77fb69ec0a00469d7fd87ff148f46"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d9afad7b16d01c9e8929b6a205a18163c7e61b6cd9bcf9c81be77d5afc1067a"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb424ae7240f2d2f7d8dda66a61ebf603f74d92f109452c63b0dbf400204a437"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42149e6d13bd6d06437d2a954dae2184dadbbdec0fdb82dafe92860d99f80519"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:760ac95d788f2964b73da01e0bdffbe1bf2ad8273d0437565ce9092ae6ad1fbc"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cf27e8e4bf7bf9d92ef04f3d2b769e91c3f30ba99208c29f5b41e77271a2614"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00ceb8ff3c44ab0d6014106c71709c85dee9feedd6890eff77c814aa3798952b"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b61c558574fbc093d85940c3264c08c2b857b8916f8e8f222e7b86b0bb7d12"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:346a2d8f17224e99f9ef988606c83d809d5917d17ad00207237e0965e54f9730"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d60d1db1b7e470e71ae096b6456e20ec56b52bde6198e2dbbc5e6769fa6797dc"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2477da227e266f9c712f11393182c69a99d3c8007ea27f68c5afc3faf401cc43"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8499c7d963ddea8adb6cffac2861ee39a1053e22ca8a5ee9de1197f8dc0275a5"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12802e5c4d8ae104fb6efeeb436098325ce0dca33b461c46e8df015c84fbef26"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win32.whl", hash = "sha256:e1061311d07e7cdcffa92c9b50c2ab4192907e70ca01b2e8e1c0b6b4495faa37"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6e4ed63e204daa863a802eec09feea5448617981ba5d150f843ad8e3ae071a4"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:920733a28c3af47870835d59ca9879579f66238f10de91d2b4b3f809d1ebfc5b"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f6235b57ae3faa3f85cb3f90c9fee49b21bd671b76e90fc99e8ca2bdf0b5e4a3"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af4585e5812632c357fee5ab781c29f00cd06bea58f8882ff244cc4906ba6c9e"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5942dc4460e5030c5f9e1d4c9383de2f3564a2503fe25e13e89021bcbfea2f44"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b31ab59e1a0df5afc21f3109b6cfd77b34040dbf54f1bad3989f885cfae1e60"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97c885a7a480b21164f57a706418c9bbc9a496ec6da087e554424358cadde445"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d844c0587d969ce36fbf4b7cbf0860380ffeafc9ac5e17a7cbe8abf528d07bb"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93c95dce8917bf428064c64024de43ffd34ec5949dd4425780c72bd41f9d969"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:834f6113d538af358f39296604a1953e55f8eeffc20cb4caf82250edbb8bf679"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a940aa71a7f37d7f0daac186066bf6668d4d3b7e7ef464cb50bc7ba89eae1f51"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ec9eaf73501c9a7de2c6938cb3050392e2ee0c5ca3921482acf01476b85a7226"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c5ec360694ac14bfaeb6aea95737cf1a6cf805b5fe8ea7fd28814706c7fa838"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b5e176524653ac46f1802bdd273a4b44a5f8d0054ed5013a8e8a4b72f254599"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win32.whl", hash = "sha256:6f463c6f1c42ec90e45d12a6379e18eddd5cdf74138804d8215619b6f4d31cea"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:b894fa2b30cd6498a29e5c470cb01c6ea898540b7e048a0342775a5000531334"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:43bb17056c5d1332f517b888c4e57846c4b5f936ed304917eeb5c9ac85d940d4"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:97f824c15bc6933a31d6e3cbfa90188ba0e5043cf2b6dd342c2b90ee8b3fd47c"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a973b3f5cabf931029a3ae4a0f72e3222e53d412ea85fc37ddc49e1774f00fbf"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7880e012228722dec1be02b9ef3898ed023388b8a24d6fa8213d7581932510"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c78582f50e75e6c2bc38c791ed291cb89cf26a3148c47860c1a04d6e5379c8e"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d7d9e6a04d8344b0198c96394c28874086888d0a2b2f605f30d1b27b9377b7d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5620001fd4d6644a2f56880388179cc8f3767670f0670160fcb97c3b46c828af"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0666ab4c52e500af7ba5cc17389f5d15c0cdad06412c80312088519fdc25686d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:27b4d440fa50b50c515a91a01ee17e8ede719dca06eef4c0cccf1a111a4cfad3"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83dccfd5a754f2a0e8555b23dde31f0f7920601bfa807aa76829391ea81e7c67"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b572b634740e047c53743ed27a1bb3b4f93cf4abbac258cd7af377b2c4a9ba5b"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7fa7b81fb52902d5f78dac42b3d6c835a6633b01ddf9b202a3ca8443be4b2d6a"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1d4fbff980cb6baef4ee675963c081f7b5d6580a105d6a4962b20f1f880e1fb"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win32.whl", hash = "sha256:3fe8da12ea77271097b303fa7624cfaf5afd90261002314e3b0047d36f4afd8d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:6f7e92fc7d2a7f02e1e01fe4f539324dfab80f27cb70a30dd63a95445566946b"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:e31be53d7f4905a6a038296d8b773a79da9ee9f0cd19af9490c5c5a22e37d2e5"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bef5c91d5db776523530073cda5b2a276283258d2f86764be4a008c83caf7acd"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:841e0c2a5fbe8fc8b9b1a56e924c871899932c0ece7fbd970aa1c32bfd12d4bf"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046fc67f3885d94693a2151dd913aaf08b10931639cbb953dfeef3151cb1027c"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4d2d39b2e76c17f92edd6d384dc21fa020871c73251cdfa017149358937a41d"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5857dda85165b986c26a474b22907db6b93932c99397c818bcdec96340a76d5"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c26cd1b9969ea70dbf0dbda3d2b54ab4b2e683d0fd0f17282169a19563efeb1"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf56ea4edd69005786e6c80a9049d95003aeb5798803e7a2906194e7a3cb6472"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fbe7580b5fb2db8ebd53819171ff671124237a55ada3f64d20fc9a149d133960"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:018506a53c3b20dcbda8c93d4484b9eb1764c93d5ea16be103cf6b0d8b11d860"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:325c9c71b737fcd32e2a4e634c430c07dd3d374cfe134eded3fe46e4c6f9bf5d"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:930756639643e3aa02d3136b6fec74e5b9370a24f8796e1065cd8a857a6a6c50"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0acbd27543b158cb915fde03877383816a9e83257832818f1e803bac9b394900"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win32.whl", hash = "sha256:80ff9283c54d7d29b2d954181e137deee89bec62f4a54675d8b6dbb6b15d3e03"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd37e53f0ed239d0cec27b250cec958982a8ba252ce64aa5e6052de3a82fa8db"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win_arm64.whl", hash = "sha256:4a4422e4f73a579755ab60abccb3ff148b5c224b3c7454a13ca217dfbad54da6"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b7cba636c32a6fc3a402d1cb2c70c6c9f8e6319380aaf15559db09d868a23e56"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b79286738a43e8df8420c4b30a92712dec6247430b130f8e015c3a78b6d61ac2"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dc1937198e7ff67e217e60bfa339f05da268d91bb15fec710452d11fe2fdf60"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b85817a57cf8db32dd5d2d66ccfba656d299b09eaf86234295f89f91be1a0db2"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04283c6f3e79f13a784f844cd5b1df4f518ad0f70c789aea733d106c26e1b4fb"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a718f740553aad5f4daef790191511da9c6eae893ee1fc2677627e4b624ae2db"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cbdf145c7e4ebf2e81c794ed7a582c4acad19e886d5ad6676086369bd6760753"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0d03ad14a26a477be221fddc002954ae68a9e2402b9d85433f2d0a6af01aa2bb"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1187aeae9c89e838d2a0a2b954b4052e4897e5f62e5794ef42527bf039d469e"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd47dfb1bca9673a48b923b3d988b7668ee8efd0562027f58b0f2b7abf27144c"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187cdb402e223264eebed2fe671e367e636a499a7a9c82090b8d4b75aa416c2a"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6899b41bf6c30282179f77096c1939f1454836440a8ab05b48ebf7026a3b590"}, - {file = "rapidfuzz-3.12.1.tar.gz", hash = "sha256:6a98bbca18b4a37adddf2d8201856441c26e9c981d8895491b5bc857b5f780eb"}, -] - -[package.extras] -all = ["numpy"] - -[[package]] -name = "regex" -version = "2024.7.24" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, - {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, - {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, - {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, - {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, - {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, - {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, - {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, - {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, - {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, - {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, - {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-cache" -version = "1.2.1" -description = "A persistent cache for python requests" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, - {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, -] - -[package.dependencies] -attrs = ">=21.2" -cattrs = ">=22.2" -platformdirs = ">=2.5" -requests = ">=2.22" -url-normalize = ">=1.4" -urllib3 = ">=1.25.5" - -[package.extras] -all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=6.0.1)", "redis (>=3)", "ujson (>=5.4)"] -bson = ["bson (>=0.5)"] -docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.9)"] -dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] -json = ["ujson (>=5.4)"] -mongodb = ["pymongo (>=3)"] -redis = ["redis (>=3)"] -security = ["itsdangerous (>=2.0)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["main"] -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruff" -version = "0.3.7" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, - {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, - {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, - {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, - {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, -] - -[[package]] -name = "serpyco-rs" -version = "1.13.0" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "serpyco_rs-1.13.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e722b3053e627d8a304e462bce20cae1670a2c4b0ef875b84d0de0081bec4029"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f10e89c752ff78d720a42e026b0a9ada70717ad6306a9356f794280167d62bf"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99db4ec6b36130d81f409e64759d9e26290138b1000b62f9f244117738aae652"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3265d4467a52540e7309d117c87a0fe97a9c942960159b37af55a164186e4885"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb10e547491d960064e5d935770f503c425b4ab7fc8c81e05b3c4b13bef195b3"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd38a763e9e23f69aad6ba78a8793b6eab9272b51ab01b36a9e3f6208f5c5525"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d49d6d81739aa0b264e303e90bede58a6487cbc6a78019df5ab6059ea988cc"}, - {file = "serpyco_rs-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:cad636c425d942a8de63b4f0e5a8ff91382f36956c129a629bcfd499d8450de2"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2b7ff9a0afc417958203bbb8f4f21ecc47307f6691542af27f61c086870d1c90"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a765f588d23b02ba45b7d3fbe9d828f52eab30836408088abf08ebcbc1969"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c6ab6dfbb477745067871d896675d8c8d8d027d4b4bd36df8e6797caaaa169b"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bf3292411b63c89ca1273717cee374c03dd64da044f297166362b7e8d26bb440"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e568e4bb6ffe51e7f428fe3aa37baa4a0cb52d27a64d7e9a5adfec56d3e8b1f"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e6b53056de511e3d211e04e0006924e45591c8f68f2aaae024accf28a6127d"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ffe84bb8b5fb361499e1cd9b93b06081e5230b80713341be81793d1d590d9b"}, - {file = "serpyco_rs-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:5988d48f5df9558ea874e385b11b6cfc1dd434d5ed64eefb7762b203e74ce16c"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e7a9d3eb0494984066b6b4d3b8eb25f69a599f401e8ede487efdb035547381b1"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47b519aaddc42b9d56a3a4760c74e4ef3a945e84eefbec96788f279efbd050d8"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e2b463c6b9e307a5fda3579aacb96468c37d14314e3a0070eda2f5789e4b4c6"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f659d8c058f223711d475dcae47adb033e3902591cbb1e5e1b17687ca15cd0bb"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9edc243fdb67a6bd51ec13c9a17cb0d608cfde9beb171ab5200e24045ab123f"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad0b85f02f52ea93f8bf068f0e00c8980702be00375b8888090723a21f35e2bc"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9e49a1a2d6eb9b855b34ce6f27dcbfb6e0dbfa675b3e3f760178c059c9c1100"}, - {file = "serpyco_rs-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:915eb70006944a2819077bb706a1e5deb671f9ac0c963bb3b46073604837f7ff"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:69502ad3c56e6fdb700a27059590cf548a5e1aa03519eef607fdf8f8b320eeed"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:324c6e3eec175cd8aabef071bcef763836fee94012b0b0819952224f9c7b855c"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2097cda3ccf641a3c05ff2aa1a0122f4c47ef4de8807fec816afb93cc6570ee5"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:54fbcd983b50a5ee294ea6d5b1c99c3f8063206660baec96bb4ab5dc211abe3e"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea9608615a24aae8e95f3ccb58aeec7767ad54e6b69edffc7a231b4d6236b83c"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12e4c07e35064a6827473d318e3f60bdf49d8cc2726e5283a51de5ab41a9cc25"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fcbbe5c92d9517a2339a6c51f35c6eda8a0b5ed1a7e741feae960d07369459b"}, - {file = "serpyco_rs-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:11d430b894a2c003f103e1584b7ceb799235e4dcf109bc74e9937982ab8e48d6"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:323c8f2615236d16dcd7995712fe63a77e6362c791890ced8af13b3b205bdbc8"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70527c919bb1560728340d62974beaa1ed050f9d33603270477e089fc90dcc53"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3944a6527ebfa736fc0f5d1841b01fdf9838260aa0f92d9a43ed33b455f911dd"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2ce9f0592490eb87de2b91700c06c5fd7ab5112ae471b88ded9252af06b28f2b"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23a4b801abeb3494a4832250f8a41648914d0fdc8817bc010a2bbb190aa55ca"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d57dd8dc10aa7a81d06c4eb2d523b5241047764a8fa65b94e716975b888d1d54"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd96fc057c6cdc1336ab4e5072eb1710661464ae9b171950347c66c68e687d50"}, - {file = "serpyco_rs-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:94314ed652545eebdc90e83b671cf961ade881fdd0e1aa5579182d13050f8405"}, - {file = "serpyco_rs-1.13.0.tar.gz", hash = "sha256:981232323d2f195a0281b9beb735d5facfc919de6c65abd764cf908c7ad887fe"}, -] - -[package.dependencies] -attributes-doc = "*" -typing-extensions = "*" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "tenacity" -version = "8.5.0" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, - {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, -] - -[package.extras] -doc = ["reno", "sphinx"] -test = ["pytest", "tornado (>=4.5)", "typeguard"] - -[[package]] -name = "tqdm" -version = "4.66.5" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20241230" -description = "Typing stubs for PyYAML" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"}, - {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2025.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, -] - -[[package]] -name = "unidecode" -version = "1.3.8" -description = "ASCII transliterations of Unicode text" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, - {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, -] - -[[package]] -name = "uritemplate" -version = "4.1.1" -description = "Implementation of RFC 6570 URI Templates" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, - {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, -] - -[[package]] -name = "url-normalize" -version = "1.4.3" -description = "URL normalization for Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -groups = ["main"] -files = [ - {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, - {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "urllib3" -version = "2.2.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wcmatch" -version = "10.0" -description = "Wildcard/glob file name matcher." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, - {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, -] - -[package.dependencies] -bracex = ">=2.1.1" - -[[package]] -name = "whenever" -version = "0.6.17" -description = "Modern datetime library for Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "whenever-0.6.17-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8e9e905fd19b0679e5ab1a0d0110a1974b89bf4cbd1ff22c9e352db381e4ae4f"}, - {file = "whenever-0.6.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cd615e60f992fb9ae9d73fc3581ac63de981e51013b0fffbf8e2bd748c71e3df"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd717faa660771bf6f2fda4f75f2693cd79f2a7e975029123284ea3859fb329c"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2ea744d9666be8880062da0d6dee690e8f70a2bc2a42b96ee17e10e36b0b5266"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6b32593b44332660402c7e4c681cce6d7859b15a609d66ac3a28a6ad6357c2f"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a01e4daaac24e0be48a6cb0bb03fa000a40126b1e9cb8d721ee116b2f44c1bb1"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e88fe9fccb868ee88bb2ee8bfcbc55937d0b40747069f595f10b4832ff1545"}, - {file = "whenever-0.6.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2dce7b9faf23325b38ca713b2c7a150a8befc832995213a8ec46fe15af6a03e7"}, - {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0925f7bf3448ef4f8c9b93de2d1270b82450a81b5d025a89f486ea61aa94319"}, - {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:82203a572049070d685499dd695ff1914fee62f32aefa9e9952a60762217aa9e"}, - {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c30e5b5b82783bc85169c8208ab3acf58648092515017b2a185a598160503dbb"}, - {file = "whenever-0.6.17-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:763e59062adc9adfbde45c3ad8b5f472b337cc5cebc70760627d004a4c286d33"}, - {file = "whenever-0.6.17-cp310-cp310-win32.whl", hash = "sha256:f71387bbe95cd98fc78653b942c6e02ff4245b6add012b3f11796220272984ce"}, - {file = "whenever-0.6.17-cp310-cp310-win_amd64.whl", hash = "sha256:996ab1f6f09bc9e0c699fa58937b5adc25e39e979ebbebfd77bae09221350f3d"}, - {file = "whenever-0.6.17-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:87e28378945182e822e211fcea9e89c7428749fd440b616d6d81365202cbed09"}, - {file = "whenever-0.6.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf4ee3e8d5a55d788e8a79aeff29482dd4facc38241901f18087c3e662d16ba"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97ffc43cd278f6f58732cd9d83c822faff3b1987c3b7b448b59b208cf6b6293"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ce99533865fd63029fa64aef1cfbd42be1d2ced33da38c82f8c763986583982"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68b88e023d64e8ccfabe04028738d8041eccd5a078843cd9b506e51df3375e84"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9159bae31f2edaf5e70e4437d871e52f51e7e90f1b9faaac19a8c2bccba5170a"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f9c4ee1f1e85f857507d146d56973db28d148f50883babf1da3d24a40bbcf60"}, - {file = "whenever-0.6.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0acd8b3238aa28a20d1f93c74fd84c9b59e2662e553a55650a0e663a81d2908d"}, - {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ae238cd46567b5741806517d307a81cca45fd49902312a9bdde27db5226e8825"}, - {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:99f72853e8292284c2a89a06ab826892216c04540a0ca84b3d3eaa9317dbe026"}, - {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ccb6c77b497d651a283ef0f40ada326602b313ee71d22015f53d5496124dfc10"}, - {file = "whenever-0.6.17-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a1918c9836dc331cd9a39175806668b57b93d538d288469ad8bedb144ec11b"}, - {file = "whenever-0.6.17-cp311-cp311-win32.whl", hash = "sha256:72492f130a8c5b8abb2d7b16cec33b6d6ed9e294bb63c56ab1030623de4ae343"}, - {file = "whenever-0.6.17-cp311-cp311-win_amd64.whl", hash = "sha256:88dc4961f8f6cd16d9b70db022fd6c86193fad429f98daeb82c8e9ba0ca27e5c"}, - {file = "whenever-0.6.17-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d72c2413e32e3f382f6def337961ea7f20e66d0452ebc02e2fa215e1c45df73e"}, - {file = "whenever-0.6.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d12b891d780d9c98585b507e9f85097085337552b75f160ce6930af96509faa1"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:503aaf2acfd5a7926ca5c6dc6ec09fc6c2891f536ab9cbd26a072c94bda3927f"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6de09bcddfeb61c822019e88d8abed9ccc1d4f9d1a3a5d62d28d94d2fb6daff5"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfe430df7f336d8793b6b844f0d2552e1589e39e72b7414ba67139b9b402bed"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99776635ac174a3df4a372bfae7420b3de965044d69f2bee08a7486cabba0aaa"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbb6d8dae94b492370949c8d8bf818f9ee0b4a08f304dadf9d6d892b7513676"}, - {file = "whenever-0.6.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:45d66e68cdca52ca3e6e4990515d32f6bc4eb6a24ff8cbcbe4df16401dd2d3c7"}, - {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73947bd633bc658f8a8e2ff2bff34ee7caabd6edd9951bb2d778e6071c772df4"}, - {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9f9d5b108f9abf39471e3d5ef22ff2fed09cc51a0cfa63c833c393b21b8bdb81"}, - {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a42231e7623b50a60747a752a97499f6ad03e03ce128bf97ded84e12b0f4a77e"}, - {file = "whenever-0.6.17-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a6d9458d544006131e1210343bf660019abfa11d46f5be8ad2d7616dc82340f4"}, - {file = "whenever-0.6.17-cp312-cp312-win32.whl", hash = "sha256:ca1eda94ca2ef7ad1a1249ea80949be252e78a0f10463e12c81ad126ec6b99e5"}, - {file = "whenever-0.6.17-cp312-cp312-win_amd64.whl", hash = "sha256:fd7de20d6bbb74c6bad528c0346ef679957db21ce8a53f118e53b5f60f76495b"}, - {file = "whenever-0.6.17-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca9ee5b2b04c5a65112f55ff4a4efcba185f45b95766b669723e8b9a28bdb50b"}, - {file = "whenever-0.6.17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bef0cf1cd4282044d98e4af9969239dc139e5b192896d4110d0d3f4139bdb30"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04ac4e1fc1bc0bfb35f2c6a05d52de9fec297ea84ee60c655dec258cca1e6eb7"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c792f96d021ba2883e6f4b70cc58b5d970f026eb156ff93866686e27a7cce93"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a7f938b5533e751702de95a615b7903457a7618b94aef72c062fa871ad691b"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47d2dbb85c512e28c14eede36a148afbb90baa340e113b39b2b9f0e9a3b192dd"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2b49a91853c133e8954dffbf180adca539b3719fd269565bf085ba97b47f5f"}, - {file = "whenever-0.6.17-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:91fcb2f42381a8ad763fc7ee2259375b1ace1306a02266c195af27bd3696e0da"}, - {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e4d5e3429015a5082cd171ceea633c6ea565d90491005cdcef49a7d6a17c99"}, - {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f05731f530e4af29582a70cf02f8441027a4534e67b7c484efdf210fc09d0421"}, - {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0d417b7de29aea2cfa7ea47f344848491d44291f28c038df869017ae66a50b48"}, - {file = "whenever-0.6.17-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8208333ece7f2e0c232feeecbd21bde3888c6782d3b08372ae8b5269938645b3"}, - {file = "whenever-0.6.17-cp313-cp313-win32.whl", hash = "sha256:c4912104731fd2be89cd031d8d34227225f1fae5181f931b91f217e69ded48ff"}, - {file = "whenever-0.6.17-cp313-cp313-win_amd64.whl", hash = "sha256:4f46ad87fab336d7643e0c2248dcd27a0f4ae42ac2c5e864a9d06a8f5538efd0"}, - {file = "whenever-0.6.17-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:53f03ae8c54aa60f5f22c790eb63ad644e97f8fba4b22337572a4e16bc4abb73"}, - {file = "whenever-0.6.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42fce832892578455d46870dc074521e627ba9272b839a8297784059170030f5"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ac0786d6cb479275ea627d84536f38b6a408348961856e2e807d82d4dc768ed"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e2f490b5e90b314cf7615435e24effe2356b57fa907fedb98fe58d49c6109c5"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c1f25ab893cfa724b319a838ef60b918bd35be8f3f6ded73e6fd6e508b5237e"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac5f644d0d3228e806b5129cebfb824a5e26553a0d47d89fc9e962cffa1b99ed"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e185309314b1abcc14c18597dd0dfe7fd8b39670f63a7d9357544994cba0e251"}, - {file = "whenever-0.6.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cc78b8a73a71241bf356743dd76133ccf796616823d8bbe170701a51d10b9fd3"}, - {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0ea05123a0b3673c7cf3ea1fe3d8aa9362571db59f8ea15d7a8fb05d885fd756"}, - {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9f0c874dbb49c3a733ce4dde86ffa243f166b9d1db4195e05127ec352b49d617"}, - {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:86cfbd724b11e8a419056211381bde4c1d35ead4bea8d498c85bee3812cf4e7c"}, - {file = "whenever-0.6.17-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e1514f4a3094f11e1ad63b9defadf375d953709c7806cc1d2396634a7b00a009"}, - {file = "whenever-0.6.17-cp39-cp39-win32.whl", hash = "sha256:715ed172e929327c1b68e107f0dc9520237d92e11c26db95fd05869724f3e9d9"}, - {file = "whenever-0.6.17-cp39-cp39-win_amd64.whl", hash = "sha256:5fed15042b2b0ea44cafb8b7426e99170d3f4cd64dbeb966c77f14985e724d82"}, - {file = "whenever-0.6.17.tar.gz", hash = "sha256:9c4bfe755c8f06726c4031dbbecd0a7710e2058bc2f3b4e4e331755af015f55f"}, -] - -[package.dependencies] -tzdata = {version = ">=2020.1", markers = "sys_platform == \"win32\""} - -[[package]] -name = "xmltodict" -version = "0.14.2" -description = "Makes working with XML feel like you are working with JSON" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, - {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, -] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11,<3.12" -content-hash = "e466a804bd60f1e27e46d44e15e6af3bc4fc90f0b982f21b5c493346c3e09a76" diff --git a/airbyte-ci/connectors/erd/pyproject.toml b/airbyte-ci/connectors/erd/pyproject.toml deleted file mode 100644 index 5fab668695f5..000000000000 --- a/airbyte-ci/connectors/erd/pyproject.toml +++ /dev/null @@ -1,49 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "erd" -version = "0.1.1" -description = "Contains utilities for generating ERDs." -authors = ["Airbyte "] -license = "MIT" -homepage = "https://github.com/airbytehq/airbyte" -readme = "README.md" -packages = [ - { include = "erd", from = "src" }, -] - -[tool.poetry.dependencies] -python = "^3.11,<3.12" -airbyte-cdk = "^6.0.0" -click = "^8.1.3" -dpath = "^2.1.6" -google-generativeai = "^0.7.2" -markdown-it-py = ">=2.2.0" -pydbml = "^1.1.0" -pytest = "^8.1.1" -pyyaml = "^6.0" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.3.0" -mypy = "^1.8.0" -types-pyyaml = "^6.0.12.20240311" - -[tool.ruff.lint] -select = ["I", "F"] - -[tool.ruff.lint.isort] -known-first-party = ["connection-retriever"] - -[tool.poe.tasks] -test = "pytest tests" -type_check = "mypy src --disallow-untyped-defs" -pre-push = [] - -[tool.poetry.scripts] -erd = "erd.cli:main" - -[tool.airbyte_ci] -python_versions = ["3.11"] -poe_tasks = ["type_check", "test"] diff --git a/airbyte-ci/connectors/erd/src/erd/__init__.py b/airbyte-ci/connectors/erd/src/erd/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/erd/src/erd/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/erd/src/erd/dbml_assembler.py b/airbyte-ci/connectors/erd/src/erd/dbml_assembler.py deleted file mode 100644 index db5d8cd16855..000000000000 --- a/airbyte-ci/connectors/erd/src/erd/dbml_assembler.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from pathlib import Path -from typing import List, Set, Union - -import yaml -from airbyte_cdk.models import ( - AirbyteCatalog, - AirbyteStream, -) -from airbyte_cdk.sources.declarative.parsers.manifest_reference_resolver import ( - ManifestReferenceResolver, -) -from pydbml import Database # type: ignore # missing library stubs or py.typed marker -from pydbml.classes import ( # type: ignore # missing library stubs or py.typed marker - Column, - Index, - Reference, - Table, -) - -from erd.relationships import Relationships - - -class Source: - def __init__(self, source_folder: Path, source_technical_name: str) -> None: - self._source_folder = source_folder - self._source_technical_name = source_technical_name - - def is_dynamic(self, stream_name: str) -> bool: - """ - This method is a very flaky heuristic to know if a stream is dynamic or not. A stream will be considered dynamic if: - * The stream name is in the schemas folder - * The stream is within the manifest and the schema definition is `InlineSchemaLoader` - """ - manifest_static_streams = set() - if self._has_manifest(): - with open(self._get_manifest_path()) as manifest_file: - resolved_manifest = ManifestReferenceResolver().preprocess_manifest( - yaml.safe_load(manifest_file) - ) - for stream in resolved_manifest["streams"]: - if "schema_loader" not in stream: - # stream is assumed to have `DefaultSchemaLoader` which will show in the schemas folder so we can skip - continue - if stream["schema_loader"]["type"] == "InlineSchemaLoader": - name = ( - stream["name"] - if "name" in stream - else stream.get("$parameters").get("name", None) - ) - if not name: - print(f"Could not retrieve name for this stream: {stream}") - continue - manifest_static_streams.add( - stream["name"] - if "name" in stream - else stream.get("$parameters").get("name", None) - ) - - return ( - stream_name - not in manifest_static_streams | self._get_streams_from_schemas_folder() - ) - - def _get_streams_from_schemas_folder(self) -> Set[str]: - schemas_folder = ( - self._source_folder - / self._source_technical_name.replace("-", "_") - / "schemas" - ) - return ( - { - p.name.replace(".json", "") - for p in schemas_folder.iterdir() - if p.is_file() - } - if schemas_folder.exists() - else set() - ) - - def _get_manifest_path(self) -> Path: - return ( - self._source_folder - / self._source_technical_name.replace("-", "_") - / "manifest.yaml" - ) - - def _has_manifest(self) -> bool: - return self._get_manifest_path().exists() - - -class DbmlAssembler: - def assemble( - self, - source: Source, - discovered_catalog: AirbyteCatalog, - relationships: Relationships, - ) -> Database: - database = Database() - for stream in discovered_catalog.streams: - if source.is_dynamic(stream.name): - print(f"Skipping stream {stream.name} as it is dynamic") - continue - - database.add(self._create_table(stream)) - - self._add_references(source, database, relationships) - - return database - - def _create_table(self, stream: AirbyteStream) -> Table: - dbml_table = Table(stream.name) - for property_name, property_information in stream.json_schema.get( - "properties" - ).items(): - try: - dbml_table.add_column( - Column( - name=property_name, - type=self._extract_type(property_information["type"]), - pk=self._is_pk(stream, property_name), - ) - ) - except (KeyError, ValueError) as exception: - print(f"Ignoring field {property_name}: {exception}") - continue - - if ( - stream.source_defined_primary_key - and len(stream.source_defined_primary_key) > 1 - ): - if any(map(lambda key: len(key) != 1, stream.source_defined_primary_key)): - raise ValueError( - f"Does not support nested key as part of primary key `{stream.source_defined_primary_key}`" - ) - - composite_key_columns = [ - column - for key in stream.source_defined_primary_key - for column in dbml_table.columns - if column.name in key - ] - if len(composite_key_columns) < len(stream.source_defined_primary_key): - raise ValueError("Unexpected error: missing PK column from dbml table") - - dbml_table.add_index( - Index( - subjects=composite_key_columns, - pk=True, - ) - ) - return dbml_table - - def _add_references( - self, source: Source, database: Database, relationships: Relationships - ) -> None: - for stream in relationships["streams"]: - for column_name, relationship in stream["relations"].items(): - if source.is_dynamic(stream["name"]): - print( - f"Skipping relationship as stream {stream['name']} from relationship is dynamic" - ) - continue - - try: - target_table_name, target_column_name = relationship.split( - ".", 1 - ) # we support the field names having dots but not stream name hence we split on the first dot only - except ValueError as exception: - raise ValueError( - f"Could not handle relationship {relationship}" - ) from exception - - if source.is_dynamic(target_table_name): - print( - f"Skipping relationship as target stream {target_table_name} is dynamic" - ) - continue - - try: - database.add_reference( - Reference( - type="<>", # we don't have the information of which relationship type it is so we assume many-to-many for now - col1=self._get_column( - database, stream["name"], column_name - ), - col2=self._get_column( - database, target_table_name, target_column_name - ), - ) - ) - except ValueError as exception: - print(f"Skipping relationship: {exception}") - - def _extract_type(self, property_type: Union[str, List[str]]) -> str: - if isinstance(property_type, str): - return property_type - - types = list(property_type) - if "null" in types: - # As we flag everything as nullable (except PK and cursor field), there is little value in keeping the information in order to - # show this in DBML - types.remove("null") - if len(types) != 1: - raise ValueError( - f"Expected only one type apart from `null` but got {len(types)}: {property_type}" - ) - return types[0] - - def _is_pk(self, stream: AirbyteStream, property_name: str) -> bool: - return stream.source_defined_primary_key == [[property_name]] - - def _get_column( - self, database: Database, table_name: str, column_name: str - ) -> Column: - matching_tables = list( - filter(lambda dbml_table: dbml_table.name == table_name, database.tables) - ) - if len(matching_tables) == 0: - raise ValueError(f"Could not find table {table_name}") - elif len(matching_tables) > 1: - raise ValueError( - f"Unexpected error: many tables found with name {table_name}" - ) - - table: Table = matching_tables[0] - matching_columns = list( - filter(lambda column: column.name == column_name, table.columns) - ) - if len(matching_columns) == 0: - raise ValueError( - f"Could not find column {column_name} in table {table_name}. Columns are: {table.columns}" - ) - elif len(matching_columns) > 1: - raise ValueError( - f"Unexpected error: many columns found with name {column_name} for table {table_name}" - ) - - return matching_columns[0] diff --git a/airbyte-ci/connectors/erd/src/erd/erd_service.py b/airbyte-ci/connectors/erd/src/erd/erd_service.py deleted file mode 100644 index 8a07c918e8c4..000000000000 --- a/airbyte-ci/connectors/erd/src/erd/erd_service.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import copy -import json -from pathlib import Path -from typing import Any - -import dpath -import google.generativeai as genai # type: ignore # missing library stubs or py.typed marker -from airbyte_cdk.models import ( - AirbyteCatalog, -) -from markdown_it import MarkdownIt -from pydbml.renderer.dbml.default import ( # type: ignore # missing library stubs or py.typed marker - DefaultDBMLRenderer, # type: ignore # missing library stubs or py.typed marker -) - -from erd.dbml_assembler import DbmlAssembler, Source -from erd.relationships import Relationships, RelationshipsMerger - - -class ErdService: - def __init__(self, source_technical_name: str, source_path: Path) -> None: - self._source_technical_name = source_technical_name - self._source_path = source_path - self._model = genai.GenerativeModel("gemini-1.5-flash") - - if not self._discovered_catalog_path.exists(): - raise ValueError( - f"Could not find discovered catalog at path {self._discovered_catalog_path}" - ) - - def generate_estimated_relationships(self) -> None: - normalized_catalog = self._normalize_schema_catalog(self._get_catalog()) - estimated_relationships = self._get_relations_from_gemini( - source_name=self._source_path.name, catalog=normalized_catalog - ) - with open( - self._estimated_relationships_file, "w" - ) as estimated_relationship_file: - json.dump(estimated_relationships, estimated_relationship_file, indent=4) - - def write_dbml_file(self) -> None: - database = DbmlAssembler().assemble( - Source(self._source_path, self._source_technical_name), - self._get_catalog(), - RelationshipsMerger().merge( - self._get_relationships(self._estimated_relationships_file), - self._get_relationships(self._confirmed_relationships_file), - ), - ) - - with open(self._erd_folder / "source.dbml", "w") as f: - f.write(DefaultDBMLRenderer.render_db(database)) - - @staticmethod - def _normalize_schema_catalog(catalog: AirbyteCatalog) -> dict[str, Any]: - """ - Foreign key cannot be of type object or array, therefore, we can remove these properties. - :param schema: json_schema in draft7 - :return: json_schema in draft7 with TOP level properties only. - """ - streams = copy.deepcopy(catalog.model_dump())["streams"] - for stream in streams: - to_rem = dpath.search( - stream["json_schema"]["properties"], - ["**"], - afilter=lambda x: isinstance(x, dict) - and ( - "array" in str(x.get("type", "")) - or "object" in str(x.get("type", "")) - ), - ) - for key in to_rem: - stream["json_schema"]["properties"].pop(key) - return streams # type: ignore # as this comes from an AirbyteCatalog dump, the format should be fine - - def _get_relations_from_gemini( - self, source_name: str, catalog: dict[str, Any] - ) -> Relationships: - """ - - :param source_name: - :param catalog: - :return: {"streams":[{'name': 'ads', 'relations': {'account_id': 'ad_account.id', 'campaign_id': 'campaigns.id', 'adset_id': 'ad_sets.id'}}, ...]} - """ - system = "You are an Database developer in charge of communicating well to your users." - - source_desc = """ -You are working on the {source_name} API service. - -The current JSON Schema format is as follows: -{current_schema}, where "streams" has a list of streams, which represents database tables, and list of properties in each, which in turn, represent DB columns. Streams presented in list are the only available ones. -Generate and add a `foreign_key` with reference for each field in top level of properties that is helpful in understanding what the data represents and how are streams related to each other. Pay attention to fields ends with '_id'. - """.format(source_name=source_name, current_schema=catalog) - task = """ -Please provide answer in the following format: -{streams: [{"name": "", "relations": {"": ""} }]} -Pay extra attention that in " "ref_table" should be one of the list of streams, and "column_name" should be one of the property in respective reference stream. -Limitations: -- Not all tables should have relations -- Reference should point to 1 table only. -- table cannot reference on itself, on other words, e.g. `ad_account` cannot have relations with "ad_account" as a "ref_table" - """ - response = self._model.generate_content(f"{system} {source_desc} {task}") - md = MarkdownIt("commonmark") - tokens = md.parse(response.text) - response_json = json.loads(tokens[0].content) - return response_json # type: ignore # we blindly assume Gemini returns a response with the Relationships format as asked - - @staticmethod - def _get_relationships(path: Path) -> Relationships: - if not path.exists(): - return {"streams": []} - - with open(path, "r") as file: - return json.load(file) # type: ignore # we assume the content of the file matches Relationships - - def _get_catalog(self) -> AirbyteCatalog: - with open(self._discovered_catalog_path, "r") as file: - try: - return AirbyteCatalog.model_validate(json.loads(file.read())) - except json.JSONDecodeError as error: - raise ValueError( - f"Could not read json file {self._discovered_catalog_path}: {error}. Please ensure that it is a valid JSON." - ) - - @property - def _erd_folder(self) -> Path: - """ - Note: if this folder change, make sure to update the exported folder in the pipeline - """ - path = self._source_path / "erd" - if not path.exists(): - path.mkdir() - return path - - @property - def _estimated_relationships_file(self) -> Path: - return self._erd_folder / "estimated_relationships.json" - - @property - def _confirmed_relationships_file(self) -> Path: - return self._erd_folder / "confirmed_relationships.json" - - @property - def _discovered_catalog_path(self) -> Path: - """ - Note: if this folder change, make sure to update the exported folder in the pipeline - """ - return self._source_path / "erd" / "discovered_catalog.json" diff --git a/airbyte-ci/connectors/erd/src/erd/relationships.py b/airbyte-ci/connectors/erd/src/erd/relationships.py deleted file mode 100644 index 432b19981124..000000000000 --- a/airbyte-ci/connectors/erd/src/erd/relationships.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import copy -from typing import List, Optional, TypedDict - -from typing_extensions import NotRequired - - -class Relationship(TypedDict): - name: str - relations: dict[str, str] - false_positives: NotRequired[dict[str, str]] - - -Relationships = TypedDict("Relationships", {"streams": List[Relationship]}) - - -class RelationshipsMerger: - def merge( - self, - estimated_relationships: Relationships, - confirmed_relationships: Relationships, - ) -> Relationships: - streams = [] - for estimated_stream in estimated_relationships["streams"]: - confirmed_relationships_for_stream = self._get_stream( - confirmed_relationships, estimated_stream["name"] - ) - if confirmed_relationships_for_stream: - streams.append( - self._merge_for_stream( - estimated_stream, confirmed_relationships_for_stream - ) - ) # type: ignore # at this point, we know confirmed_relationships_for_stream is not None - else: - streams.append(estimated_stream) - - already_processed_streams = set( - map(lambda relationship: relationship["name"], streams) - ) - for confirmed_stream in confirmed_relationships["streams"]: - if confirmed_stream["name"] not in already_processed_streams: - streams.append( - { - "name": confirmed_stream["name"], - "relations": confirmed_stream["relations"], - } - ) - return {"streams": streams} - - def _merge_for_stream( - self, estimated: Relationship, confirmed: Relationship - ) -> Relationship: - relations = copy.deepcopy(confirmed.get("relations", {})) - - # get estimated but filter out false positives - for field, target in estimated.get("relations", {}).items(): - false_positives = ( - confirmed["false_positives"] if "false_positives" in confirmed else {} - ) - if field not in relations and ( - field not in false_positives - or false_positives.get(field, None) != target - ): # type: ignore # at this point, false_positives should not be None - relations[field] = target - - return { - "name": estimated["name"], - "relations": relations, - } - - def _get_stream( - self, relationships: Relationships, stream_name: str - ) -> Optional[Relationship]: - for stream in relationships["streams"]: - if stream.get("name", None) == stream_name: - return stream - - return None diff --git a/airbyte-ci/connectors/erd/tests/__init__.py b/airbyte-ci/connectors/erd/tests/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/erd/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/erd/tests/builder.py b/airbyte-ci/connectors/erd/tests/builder.py deleted file mode 100644 index e743edd2be50..000000000000 --- a/airbyte-ci/connectors/erd/tests/builder.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from erd.relationships import Relationship - - -class RelationshipBuilder: - def __init__(self, stream_name: str) -> None: - self._name = stream_name - self._relations: dict[str, str] = {} - self._false_positives: dict[str, str] = {} - - def with_relationship(self, column: str, target: str) -> "RelationshipBuilder": - self._relations[column] = target - return self - - def with_false_positive(self, column: str, target: str) -> "RelationshipBuilder": - self._false_positives[column] = target - return self - - def build(self) -> Relationship: - result = { - "name": self._name, - "relations": self._relations, - } - if self._false_positives: - result["false_positives"] = self._false_positives - return result diff --git a/airbyte-ci/connectors/erd/tests/test_dbml_assembler.py b/airbyte-ci/connectors/erd/tests/test_dbml_assembler.py deleted file mode 100644 index 08356e99856b..000000000000 --- a/airbyte-ci/connectors/erd/tests/test_dbml_assembler.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from unittest import TestCase -from unittest.mock import Mock - -from airbyte_cdk.models import AirbyteCatalog, AirbyteStream, SyncMode - -from erd.dbml_assembler import DbmlAssembler, Source -from tests.builder import RelationshipBuilder - -_A_STREAM_NAME = "a_stream_name" - - -class RelationshipsMergerTest(TestCase): - def setUp(self) -> None: - self._source = Mock(spec=Source) - self._source.is_dynamic.return_value = False - self._assembler = DbmlAssembler() - - def test_given_no_streams_then_database_is_empty(self) -> None: - dbml = self._assembler.assemble( - self._source, - AirbyteCatalog(streams=[]), - {"streams": [RelationshipBuilder(_A_STREAM_NAME).build()]}, - ) - assert not dbml.tables - - def test_given_stream_is_dynamic_then_ignore(self) -> None: - self._source.is_dynamic.return_value = True - dbml = self._assembler.assemble( - self._source, - AirbyteCatalog( - streams=[ - AirbyteStream( - name=_A_STREAM_NAME, - json_schema={"properties": {}}, - supported_sync_modes=[SyncMode.full_refresh], - ) - ] - ), - {"streams": [RelationshipBuilder(_A_STREAM_NAME).build()]}, - ) - assert not dbml.tables - - def test_given_stream_then_populate_table(self) -> None: - dbml = self._assembler.assemble( - self._source, - AirbyteCatalog( - streams=[ - AirbyteStream( - name=_A_STREAM_NAME, - json_schema={ - "properties": { - "a_primary_key": {"type": ["null", "string"]}, - "an_integer": {"type": ["null", "number"]}, - } - }, - supported_sync_modes=[SyncMode.full_refresh], - source_defined_primary_key=[["a_primary_key"]], - ) - ] - ), - {"streams": [RelationshipBuilder(_A_STREAM_NAME).build()]}, - ) - assert len(dbml.tables) == 1 - assert len(dbml.tables[0].columns) == 2 - assert dbml.tables[0].columns[0].name == "a_primary_key" - assert dbml.tables[0].columns[0].pk - assert dbml.tables[0].columns[1].name == "an_integer" diff --git a/airbyte-ci/connectors/erd/tests/test_relationships.py b/airbyte-ci/connectors/erd/tests/test_relationships.py deleted file mode 100644 index 968928eb1763..000000000000 --- a/airbyte-ci/connectors/erd/tests/test_relationships.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from unittest import TestCase - -from erd.relationships import Relationships, RelationshipsMerger -from tests.builder import RelationshipBuilder - -_A_STREAM_NAME = "a_stream_name" -_A_COLUMN = "a_column" -_ANOTHER_COLUMN = "another_column" -_A_TARGET = "a_target_table.a_target_column" -_ANOTHER_TARGET = "another_target_table.a_target_column" - - -class RelationshipsMergerTest(TestCase): - def setUp(self) -> None: - self._merger = RelationshipsMerger() - - def test_given_no_confirmed_then_return_estimation(self) -> None: - estimated: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _A_TARGET) - .build() - ] - } - confirmed: Relationships = {"streams": []} - - merged = self._merger.merge(estimated, confirmed) - - assert merged == estimated - - def test_given_confirmed_as_false_positive_then_remove_from_estimation( - self, - ) -> None: - estimated: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _A_TARGET) - .build() - ] - } - confirmed: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_false_positive(_A_COLUMN, _A_TARGET) - .build() - ] - } - - merged = self._merger.merge(estimated, confirmed) - - assert merged == {"streams": [{"name": "a_stream_name", "relations": {}}]} - - def test_given_no_estimated_but_confirmed_then_return_confirmed_without_false_positives( - self, - ) -> None: - estimated: Relationships = {"streams": []} - confirmed: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _A_TARGET) - .build() - ] - } - - merged = self._merger.merge(estimated, confirmed) - - assert merged == confirmed - - def test_given_different_columns_then_return_both(self) -> None: - estimated: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _A_TARGET) - .build() - ] - } - confirmed: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_ANOTHER_COLUMN, _A_TARGET) - .build() - ] - } - - merged = self._merger.merge(estimated, confirmed) - - assert merged == { - "streams": [ - { - "name": "a_stream_name", - "relations": { - _A_COLUMN: _A_TARGET, - _ANOTHER_COLUMN: _A_TARGET, - }, - } - ] - } - - def test_given_same_column_but_different_value_then_prioritize_confirmed( - self, - ) -> None: - estimated: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _A_TARGET) - .build() - ] - } - confirmed: Relationships = { - "streams": [ - RelationshipBuilder(_A_STREAM_NAME) - .with_relationship(_A_COLUMN, _ANOTHER_TARGET) - .build() - ] - } - - merged = self._merger.merge(estimated, confirmed) - - assert merged == { - "streams": [ - { - "name": "a_stream_name", - "relations": { - _A_COLUMN: _ANOTHER_TARGET, - }, - } - ] - } diff --git a/airbyte-ci/connectors/live-tests/.gitignore b/airbyte-ci/connectors/live-tests/.gitignore deleted file mode 100644 index 452eecef73dd..000000000000 --- a/airbyte-ci/connectors/live-tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -regression_tests_artifacts diff --git a/airbyte-ci/connectors/live-tests/README.md b/airbyte-ci/connectors/live-tests/README.md deleted file mode 100644 index c866821115bc..000000000000 --- a/airbyte-ci/connectors/live-tests/README.md +++ /dev/null @@ -1,420 +0,0 @@ -# Connector Live Testing - -This project contains utilities for running connector tests against live data. - -## Requirements - -- `docker` -- `Python ^3.11` -- `pipx` -- `poetry` - -## Install - -```bash -# From airbyte-ci/connectors/live-tests -poetry install -``` - -Note that `poetry lock` + `poetry install` didn't seem to have impact on the version of connection_retriever. In order to update this dependency to the latest, I had to `poetry add git+https://github.com/airbytehq/airbyte-platform-internal.git@master#subdirectory=tools/connection-retriever`. - -## Regression tests - -We created a regression test suite to run tests to compare the outputs of connector commands on different versions of the same connector. - -## Validation tests - -The validation test suite makes assertions about the output of airbyte commands for the target version of the connector only. - -## Tutorial(s) - -- [Loom Walkthrough (Airbyte Only)](https://www.loom.com/share/97c49d7818664b119cff6911a8a211a2?sid=4570a5b6-9c81-4db3-ba33-c74dc5845c3c) -- [Internal Docs (Airbyte Only)](https://docs.google.com/document/d/1pzTxJTsooc9iQDlALjvOWtnq6yRTvzVtbkJxY4R36_I/edit) - -### How to Use - -> ⚠️ **Note:** While you can use this tool without building a dev image, to achieve your goals you will likely need to have installed [airbyte-ci](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md) and know how to build a dev image. - -You can run the existing test suites with the following command: - -#### With local connection objects (`config.json`, `catalog.json`, `state.json`) - -```bash -poetry run pytest src/live_tests \ - --connector-image=airbyte/source-faker \ - --config-path= \ - --catalog-path= \ - --target-version=dev \ - --pr-url= # The URL of the PR you are testing -``` - -#### Using a live connection - -The live connection objects will be fetched. - -```bash - poetry run pytest src/live_tests \ - --connector-image=airbyte/source-faker \ - --target-version=dev \ - --pr-url= # The URL of the PR you are testing -``` - -You can also pass local connection objects path to override the live connection objects with `--config-path`, `--state-path` or `--catalog-path`. - -#### Test artifacts - -The test suite run will produce test artifacts in the `/tmp/regression_tests_artifacts/` folder. -**They will get cleared after each test run on prompt exit. Please do not copy them elsewhere in your filesystem as they contain sensitive data that are not meant to be stored outside of your debugging session!** - -##### Artifacts types - -- `report.html`: A report of the test run. -- `stdout.log`: The collected standard output following the command execution -- `stderr.log`: The collected standard error following the command execution -- `http_dump.mitm`: An `mitmproxy` http stream log. Can be consumed with `mitmweb` (version `>=10`) for debugging. -- `http_dump.har`: An `mitmproxy` http stream log in HAR format (a JSON encoded version of the mitm dump). -- `airbyte_messages`: A directory containing `.jsonl` files for each message type (logs, records, traces, controls, states etc.) produced by the connector. -- `duck.db`: A DuckDB database containing the messages produced by the connector. -- `dagger.log`: The log of the Dagger session, useful for debugging errors unrelated to the tests. - -**Tests can also write specific artifacts like diffs under a directory named after the test function.** - -``` -/tmp/regression_tests_artifacts -└── session_1710754231 - ├── duck.db - |── report.html - ├── command_execution_artifacts - │   └── source-orb - │   ├── check - │   │   ├── dev - │   │   │   ├── airbyte_messages - │   │   │   │   ├── connection_status.jsonl - │   │   │   │   └── logs.jsonl - │   │   │   ├── http_dump.har - │   │   │   ├── http_dump.mitm - │   │   │   ├── stderr.log - │   │   │   └── stdout.log - │   │   └── latest - │   │   ├── airbyte_messages - │   │   │   ├── connection_status.jsonl - │   │   │   └── logs.jsonl - │   │   ├── http_dump.har - │   │   ├── http_dump.mitm - │   │   ├── stderr.log - │   │   └── stdout.log - │   ├── discover - │   │   ├── dev - │   │   │   ├── airbyte_messages - │   │   │   │   └── catalog.jsonl - │   │   │   ├── http_dump.har - │   │   │   ├── http_dump.mitm - │   │   │   ├── stderr.log - │   │   │   └── stdout.log - │   │   └── latest - │   │   ├── airbyte_messages - │   │   │   └── catalog.jsonl - │   │   ├── http_dump.har - │   │   ├── http_dump.mitm - │   │   ├── stderr.log - │   │   └── stdout.log - │   ├── read-with-state - │   │   ├── dev - │   │   │   ├── airbyte_messages - │   │   │   │   ├── logs.jsonl - │   │   │   │   ├── records.jsonl - │   │   │   │   ├── states.jsonl - │   │   │   │   └── traces.jsonl - │   │   │   ├── http_dump.har - │   │   │   ├── http_dump.mitm - │   │   │   ├── stderr.log - │   │   │   └── stdout.log - │   │   └── latest - │   │   ├── airbyte_messages - │   │   │   ├── logs.jsonl - │   │   │   ├── records.jsonl - │   │   │   ├── states.jsonl - │   │   │   └── traces.jsonl - │   │   ├── http_dump.har - │   │   ├── http_dump.mitm - │   │   ├── stderr.log - │   │   └── stdout.log - │   └── spec - │   ├── dev - │   │   ├── airbyte_messages - │   │   │   └── spec.jsonl - │   │   ├── stderr.log - │   │   └── stdout.log - │   └── latest - │   ├── airbyte_messages - │   │   └── spec.jsonl - │   ├── stderr.log - │   └── stdout.log - └── dagger.log -``` - -#### HTTP Proxy and caching - -We use a containerized `mitmproxy` to capture the HTTP traffic between the connector and the source. Connector command runs produce `http_dump.mitm` (can be consumed with `mitmproxy` (version `>=10`) for debugging) and `http_dump.har` (a JSON encoded version of the mitm dump) artifacts. -The traffic recorded on the control connector is passed to the target connector proxy to cache the responses for requests with the same URL. This is useful to avoid hitting the source API multiple times when running the same command on different versions of the connector. - -### Custom CLI Arguments - -| Argument | Description | Required/Optional | -|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| ----------------- | -| `--connector-image` | Docker image name of the connector to debug (e.g., `airbyte/source-faker`, `airbyte/source-faker`). | Required | -| `--control-version` | Version of the control connector for regression testing. Must be an unambiguous connector version (e.g. 1.2.3 rather than `latest`) | Required | -| `--target-version` | Version of the connector being tested. (Defaults to dev) | Optional | -| `--pr-url` | URL of the pull request being tested. | Required | -| `--connection-id` | ID of the connection for live testing. If not provided, a prompt will appear to choose. | Optional | -| `--config-path` | Path to the custom source configuration file. | Optional | -| `--catalog-path` | Path to the custom configured catalog file. | Optional | -| `--state-path` | Path to the custom state file. | Optional | -| `--http-cache` | Use the HTTP cache for the connector. | Optional | -| `--run-id` | Unique identifier for the test run. If not provided, a timestamp will be used. | Optional | -| `--auto-select-connection` | Automatically select a connection for testing. | Optional | -| `--stream` | Name of the stream to test. Can be specified multiple times to test multiple streams. | Optional | -| `--should-read-with-state` | Specify whether to read with state. If not provided, a prompt will appear to choose. | Optional | -| `--disable-proxy` | Specify whether to disable proxy. If not provided, a proxy will be enabled. | Optional | -| `--test-evaluation-mode` | Whether to run tests in "diagnostic" mode or "strict" mode. In diagnostic mode, eligible tests will always pass unless there's an exception. | Optional | -| `--connection-subset` | The subset of connections to select from. Possible values are "sandboxes" or "all" (defaults to sandboxes). | Optional | - -## Changelog - - -### 0.21.4 -Update connection id to use first 8 chars in the report - -### 0.21.3 -Update dependencies to avoid genson issue - -### 0.21.2 -Fix selected streams filter in regression tests - -### 0.21.1 -Update Python version requirement from 3.10 to 3.11. - -### 0.21.0 -Add `disable_proxy` flag - - -### 0.20.0 -Support multiple connection objects in the regression tests suite. - - -### 0.19.10 -Pin the connection retriever until we make required changes to support the new version. - - -### 0.19.8 - -Give ownership of copied connection object files to the image user to make sure it has permission to write them (config migration). - -### 0.19.7 - -Mount connection objects to readable paths in the container for rootless images. - -### 0.19.6 - -Write connector output to a different in container path to avoid permission issues now that connector images are rootless. - -### 0.19.5 - -Fix `ZeroDivisionError` in Regression test tool - -### 0.19.4 - -Update `connection_retriever` to 0.7.4 - -### 0.19.3 - -Update `get_container_from_id` with the correct new Dagger API. - -### 0.19.2 - -Update Dagger to 0.13.3 - -### 0.19.1 - -Fixed the `UserDict` type annotation not found bug. - -### 0.19.0 - -Delete the `debug`command. - -### 0.18.8 - -Improve error message when failing to retrieve connection. -Ask to double-check that a sync ran with the control version on the selected connection. - -### 0.18.7 - -Improve error message when failing to retrieve connection. - -### 0.18.6 - -Disable the `SortQueryParams` MITM proxy addon to avoid double URL encoding. - -### 0.18.5 - -Relax test_oneof_usage criteria for constant value definitions in connector SPEC output. - -### 0.18.4 - -Bugfix: Use connection-retriever 0.7.2 - -### 0.18.3 - -Updated dependencies. - -### 0.18.2 - -Allow live tests with or without state in CI. - -### 0.18.1 - -Fix extra argument. - -### 0.18.0 - -Add support for selecting from a subset of connections. - -### 0.17.8 - -Fix the self-signed certificate path we bind to Python connectors. - -### 0.17.7 - -Explicitly pass the control version to the connection retriever. Defaults to the latest released version of the connector under test. - -### 0.17.6 - -Display diagnostic test with warning. - -### 0.17.5 - -Performance improvements using caching. - -### 0.17.4 - -Fix control image when running tests in CI. - -### 0.17.3 - -Pin requests dependency. - -### 0.17.2 - -Fix duckdb dependency. - -### 0.17.1 - -Bump the connection-retriever version to fix deprecated query. - -### 0.17.0 - -Enable running in GitHub actions. - -### 0.16.0 - -Enable running with airbyte-ci. - -### 0.15.0 - -Automatic retrieval of connection objects for regression tests. The connection id is not required anymore. - -### 0.14.2 - -Fix KeyError when target & control streams differ. - -### 0.14.1 - -Improve performance when reading records per stream. - -### 0.14.0 - -Track usage via Segment. - -### 0.13.0 - -Show test docstring in the test report. - -### 0.12.0 - -Implement a test to compare schema inferred on both control and target version. - -### 0.11.0 - -Create a global duckdb instance to store messages produced by the connector in target and control version. - -### 0.10.0 - -Show record count per stream in report and list untested streams. - -### 0.9.0 - -Make the regressions tests suite better at handling large connector outputs. - -### 0.8.1 - -Improve diff output. - -### 0.8.0 - -Regression tests: add an HTML report. - -### 0.7.0 - -Improve the proxy workflow and caching logic + generate HAR files. - -### 0.6.6 - -Exit pytest if connection can't be retrieved. - -### 0.6.6 - -Cleanup debug files when prompt is closed. - -### 0.6.5 - -Improve ConnectorRunner logging. - -### 0.6.4 - -Add more data integrity checks to the regression tests suite. - -### 0.6.3 - -Make catalog diffs more readable. - -### 0.6.2 - -Clean up regression test artifacts on any exception. - -### 0.6.1 - -Modify diff output for `discover` and `read` tests. - -### 0.5.1 - -Handle connector command execution errors. - -### 0.5.0 - -Add new tests and confirmation prompts. - -### 0.4.0 - -Introduce DuckDB to store the messages produced by the connector. - -### 0.3.0 - -Pass connection id to the regression tests suite. - -### 0.2.0 - -Declare the regression tests suite. - -### 0.1.0 - -Implement initial primitives and a `debug` command to run connector commands and persist the outputs to local storage. diff --git a/airbyte-ci/connectors/live-tests/poetry.lock b/airbyte-ci/connectors/live-tests/poetry.lock deleted file mode 100644 index 254f971fdab4..000000000000 --- a/airbyte-ci/connectors/live-tests/poetry.lock +++ /dev/null @@ -1,4667 +0,0 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. - -[[package]] -name = "aiofiles" -version = "24.1.0" -description = "File support for asyncio." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, - {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, -] - -[[package]] -name = "aiohappyeyeballs" -version = "2.4.3" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, -] - -[[package]] -name = "aiohttp" -version = "3.10.9" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aiohttp-3.10.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8b3fb28a9ac8f2558760d8e637dbf27aef1e8b7f1d221e8669a1074d1a266bb2"}, - {file = "aiohttp-3.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91aa966858593f64c8a65cdefa3d6dc8fe3c2768b159da84c1ddbbb2c01ab4ef"}, - {file = "aiohttp-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63649309da83277f06a15bbdc2a54fbe75efb92caa2c25bb57ca37762789c746"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e7fabedb3fe06933f47f1538df7b3a8d78e13d7167195f51ca47ee12690373"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c070430fda1a550a1c3a4c2d7281d3b8cfc0c6715f616e40e3332201a253067"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:51d0a4901b27272ae54e42067bc4b9a90e619a690b4dc43ea5950eb3070afc32"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fec5fac7aea6c060f317f07494961236434928e6f4374e170ef50b3001e14581"}, - {file = "aiohttp-3.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:172ad884bb61ad31ed7beed8be776eb17e7fb423f1c1be836d5cb357a096bf12"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d646fdd74c25bbdd4a055414f0fe32896c400f38ffbdfc78c68e62812a9e0257"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e86260b76786c28acf0b5fe31c8dca4c2add95098c709b11e8c35b424ebd4f5b"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d7cafc11d70fdd8801abfc2ff276744ae4cb39d8060b6b542c7e44e5f2cfc2"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc262c3df78c8ff6020c782d9ce02e4bcffe4900ad71c0ecdad59943cba54442"}, - {file = "aiohttp-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:482c85cf3d429844396d939b22bc2a03849cb9ad33344689ad1c85697bcba33a"}, - {file = "aiohttp-3.10.9-cp310-cp310-win32.whl", hash = "sha256:aeebd3061f6f1747c011e1d0b0b5f04f9f54ad1a2ca183e687e7277bef2e0da2"}, - {file = "aiohttp-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:fa430b871220dc62572cef9c69b41e0d70fcb9d486a4a207a5de4c1f25d82593"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16e6a51d8bc96b77f04a6764b4ad03eeef43baa32014fce71e882bd71302c7e4"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8bd9125dd0cc8ebd84bff2be64b10fdba7dc6fd7be431b5eaf67723557de3a31"}, - {file = "aiohttp-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dcf354661f54e6a49193d0b5653a1b011ba856e0b7a76bda2c33e4c6892f34ea"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42775de0ca04f90c10c5c46291535ec08e9bcc4756f1b48f02a0657febe89b10"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d1e4185c5d7187684d41ebb50c9aeaaaa06ca1875f4c57593071b0409d2444"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2695c61cf53a5d4345a43d689f37fc0f6d3a2dc520660aec27ec0f06288d1f9"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a3f063b41cc06e8d0b3fcbbfc9c05b7420f41287e0cd4f75ce0a1f3d80729e6"}, - {file = "aiohttp-3.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d37f4718002863b82c6f391c8efd4d3a817da37030a29e2682a94d2716209de"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2746d8994ebca1bdc55a1e998feff4e94222da709623bb18f6e5cfec8ec01baf"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6f3c6648aa123bcd73d6f26607d59967b607b0da8ffcc27d418a4b59f4c98c7c"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:558b3d223fd631ad134d89adea876e7fdb4c93c849ef195049c063ada82b7d08"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4e6cb75f8ddd9c2132d00bc03c9716add57f4beff1263463724f6398b813e7eb"}, - {file = "aiohttp-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:608cecd8d58d285bfd52dbca5b6251ca8d6ea567022c8a0eaae03c2589cd9af9"}, - {file = "aiohttp-3.10.9-cp311-cp311-win32.whl", hash = "sha256:36d4fba838be5f083f5490ddd281813b44d69685db910907636bc5dca6322316"}, - {file = "aiohttp-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:8be1a65487bdfc285bd5e9baf3208c2132ca92a9b4020e9f27df1b16fab998a9"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4fd16b30567c5b8e167923be6e027eeae0f20cf2b8a26b98a25115f28ad48ee0"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:40ff5b7660f903dc587ed36ef08a88d46840182d9d4b5694e7607877ced698a1"}, - {file = "aiohttp-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4edc3fd701e2b9a0d605a7b23d3de4ad23137d23fc0dbab726aa71d92f11aaaf"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e525b69ee8a92c146ae5b4da9ecd15e518df4d40003b01b454ad694a27f498b5"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5002a02c17fcfd796d20bac719981d2fca9c006aac0797eb8f430a58e9d12431"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4ceeae2fb8cabdd1b71c82bfdd39662473d3433ec95b962200e9e752fb70d0"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e395c3d1f773cf0651cd3559e25182eb0c03a2777b53b4575d8adc1149c6e9"}, - {file = "aiohttp-3.10.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbdb8def5268f3f9cd753a265756f49228a20ed14a480d151df727808b4531dd"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f82ace0ec57c94aaf5b0e118d4366cff5889097412c75aa14b4fd5fc0c44ee3e"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6ebdc3b3714afe1b134b3bbeb5f745eed3ecbcff92ab25d80e4ef299e83a5465"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f9ca09414003c0e96a735daa1f071f7d7ed06962ef4fa29ceb6c80d06696d900"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1298b854fd31d0567cbb916091be9d3278168064fca88e70b8468875ef9ff7e7"}, - {file = "aiohttp-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60ad5b8a7452c0f5645c73d4dad7490afd6119d453d302cd5b72b678a85d6044"}, - {file = "aiohttp-3.10.9-cp312-cp312-win32.whl", hash = "sha256:1a0ee6c0d590c917f1b9629371fce5f3d3f22c317aa96fbdcce3260754d7ea21"}, - {file = "aiohttp-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:c46131c6112b534b178d4e002abe450a0a29840b61413ac25243f1291613806a"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bd9f3eac515c16c4360a6a00c38119333901b8590fe93c3257a9b536026594d"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8cc0d13b4e3b1362d424ce3f4e8c79e1f7247a00d792823ffd640878abf28e56"}, - {file = "aiohttp-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba1a599255ad6a41022e261e31bc2f6f9355a419575b391f9655c4d9e5df5ff5"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:776e9f3c9b377fcf097c4a04b241b15691e6662d850168642ff976780609303c"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8debb45545ad95b58cc16c3c1cc19ad82cffcb106db12b437885dbee265f0ab5"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2555e4949c8d8782f18ef20e9d39730d2656e218a6f1a21a4c4c0b56546a02e"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54dc329cd44f7f7883a9f4baaefe686e8b9662e2c6c184ea15cceee587d8d69"}, - {file = "aiohttp-3.10.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e709d6ac598c5416f879bb1bae3fd751366120ac3fa235a01de763537385d036"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:17c272cfe7b07a5bb0c6ad3f234e0c336fb53f3bf17840f66bd77b5815ab3d16"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c21c82df33b264216abffff9f8370f303dab65d8eee3767efbbd2734363f677"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9331dd34145ff105177855017920dde140b447049cd62bb589de320fd6ddd582"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ac3196952c673822ebed8871cf8802e17254fff2a2ed4835d9c045d9b88c5ec7"}, - {file = "aiohttp-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2c33fa6e10bb7ed262e3ff03cc69d52869514f16558db0626a7c5c61dde3c29f"}, - {file = "aiohttp-3.10.9-cp313-cp313-win32.whl", hash = "sha256:a14e4b672c257a6b94fe934ee62666bacbc8e45b7876f9dd9502d0f0fe69db16"}, - {file = "aiohttp-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:a35ed3d03910785f7d9d6f5381f0c24002b2b888b298e6f941b2fc94c5055fcd"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f392ef50e22c31fa49b5a46af7f983fa3f118f3eccb8522063bee8bfa6755f8"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d1f5c9169e26db6a61276008582d945405b8316aae2bb198220466e68114a0f5"}, - {file = "aiohttp-3.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8d9d10d10ec27c0d46ddaecc3c5598c4db9ce4e6398ca872cdde0525765caa2f"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d97273a52d7f89a75b11ec386f786d3da7723d7efae3034b4dda79f6f093edc1"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d271f770b52e32236d945911b2082f9318e90ff835d45224fa9e28374303f729"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7003f33f5f7da1eb02f0446b0f8d2ccf57d253ca6c2e7a5732d25889da82b517"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6e00c8a92e7663ed2be6fcc08a2997ff06ce73c8080cd0df10cc0321a3168d7"}, - {file = "aiohttp-3.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a61df62966ce6507aafab24e124e0c3a1cfbe23c59732987fc0fd0d71daa0b88"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:60555211a006d26e1a389222e3fab8cd379f28e0fbf7472ee55b16c6c529e3a6"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d15a29424e96fad56dc2f3abed10a89c50c099f97d2416520c7a543e8fddf066"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a19caae0d670771ea7854ca30df76f676eb47e0fd9b2ee4392d44708f272122d"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:99f9678bf0e2b1b695e8028fedac24ab6770937932eda695815d5a6618c37e04"}, - {file = "aiohttp-3.10.9-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2914caa46054f3b5ff910468d686742ff8cff54b8a67319d75f5d5945fd0a13d"}, - {file = "aiohttp-3.10.9-cp38-cp38-win32.whl", hash = "sha256:0bc059ecbce835630e635879f5f480a742e130d9821fbe3d2f76610a6698ee25"}, - {file = "aiohttp-3.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:e883b61b75ca6efc2541fcd52a5c8ccfe288b24d97e20ac08fdf343b8ac672ea"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fcd546782d03181b0b1d20b43d612429a90a68779659ba8045114b867971ab71"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:85711eec2d875cd88c7eb40e734c4ca6d9ae477d6f26bd2b5bb4f7f60e41b156"}, - {file = "aiohttp-3.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:02d1d6610588bcd743fae827bd6f2e47e0d09b346f230824b4c6fb85c6065f9c"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3668d0c2a4d23fb136a753eba42caa2c0abbd3d9c5c87ee150a716a16c6deec1"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7c071235a47d407b0e93aa6262b49422dbe48d7d8566e1158fecc91043dd948"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac74e794e3aee92ae8f571bfeaa103a141e409863a100ab63a253b1c53b707eb"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bbf94d4a0447705b7775417ca8bb8086cc5482023a6e17cdc8f96d0b1b5aba6"}, - {file = "aiohttp-3.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb0b2d5d51f96b6cc19e6ab46a7b684be23240426ae951dcdac9639ab111b45e"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e83dfefb4f7d285c2d6a07a22268344a97d61579b3e0dce482a5be0251d672ab"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f0a44bb40b6aaa4fb9a5c1ee07880570ecda2065433a96ccff409c9c20c1624a"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c2b627d3c8982691b06d89d31093cee158c30629fdfebe705a91814d49b554f8"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:03690541e4cc866eef79626cfa1ef4dd729c5c1408600c8cb9e12e1137eed6ab"}, - {file = "aiohttp-3.10.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad3675c126f2a95bde637d162f8231cff6bc0bc9fbe31bd78075f9ff7921e322"}, - {file = "aiohttp-3.10.9-cp39-cp39-win32.whl", hash = "sha256:1321658f12b6caffafdc35cfba6c882cb014af86bef4e78c125e7e794dfb927b"}, - {file = "aiohttp-3.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:9fdf5c839bf95fc67be5794c780419edb0dbef776edcfc6c2e5e2ffd5ee755fa"}, - {file = "aiohttp-3.10.9.tar.gz", hash = "sha256:143b0026a9dab07a05ad2dd9e46aa859bffdd6348ddc5967b42161168c24f857"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.12.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] - -[[package]] -name = "aioquic" -version = "1.2.0" -description = "An implementation of QUIC and HTTP/3" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aioquic-1.2.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3e23964dfb04526ade6e66f5b7cd0c830421b8138303ab60ba6e204015e7cb0b"}, - {file = "aioquic-1.2.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:84d733332927b76218a3b246216104116f766f5a9b2308ec306cd017b3049660"}, - {file = "aioquic-1.2.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2466499759b31ea4f1d17f4aeb1f8d4297169e05e3c1216d618c9757f4dd740d"}, - {file = "aioquic-1.2.0-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd75015462ca5070a888110dc201f35a9f4c7459f9201b77adc3c06013611bb8"}, - {file = "aioquic-1.2.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43ae3b11d43400a620ca0b4b4885d12b76a599c2cbddba755f74bebfa65fe587"}, - {file = "aioquic-1.2.0-cp38-abi3-win32.whl", hash = "sha256:910d8c91da86bba003d491d15deaeac3087d1b9d690b9edc1375905d8867b742"}, - {file = "aioquic-1.2.0-cp38-abi3-win_amd64.whl", hash = "sha256:e3dcfb941004333d477225a6689b55fc7f905af5ee6a556eb5083be0354e653a"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8e600da7aa7e4a7bc53ee8f45fd66808032127ae00938c119ac77d66633b8961"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:358e2b9c1e0c24b9933094c3c2cf990faf44d03b64d6f8ff79b4b3f510c6c268"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe683943ea3439dd0aca05ff80e85a552d4b39f9f34858c76ac54c205990e88"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22689c33fe4799624aed6faaba0af9e6ea7d31ac45047745828ee68d67fe1d9"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3976b75e82d83742c8b811e38d273eda2ca7f81394b6a85da33a02849c5f1d9d"}, - {file = "aioquic-1.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cbe7167b2faee887e115d83d25332c4b8fa4604d5175807d978cb4fe39b4e36e"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f81e7946f09501a7c27e3f71b84a455e6bf92346fb5a28ef2d73c9d564463c63"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:e2c3c127cc3d9eac7a6d05142036bf4b2c593d750a115a2fa42c1f86cbe8c0a0"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb917143e7a4de5beba1e695fa89f2b05ef080b450dea07338cc67a9c75e0a4d"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1de513772fd04ff38028fdf748a9e2dec33d7aa2fbf67fda3011d9a85b620c54"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcc212bb529900757d8e99a76198b42c2a978ce735a1bfca394033c16cfc33c"}, - {file = "aioquic-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e7ce10198f8efa91986ad8ac83fa08e50972e0aacde45bdaf7b9365094e72c0c"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e418c92898a0af306e6f1b6a55a0d3d2597001c57a7b1ba36cf5b47bf41233b"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:81650d59bef05c514af2cfdcb2946e9d13367b745e68b36881d43630ef563d38"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6371c3afa1036294e1505fdbda8c147bc41c5b6709a47459e8c1b4eec41a86ef"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332cffa3c2124e5db82b2b9eb2662bd7c39ee2247606b74de689f6d3091b61a"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcc1eb083ed9f8d903482e375281c8c26a5ed2b6bee5ee2be3f13275d8fdb146"}, - {file = "aioquic-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f209ad5edbff8239e994c189dc74428420957448a190f4343faee4caedef4eee"}, - {file = "aioquic-1.2.0.tar.gz", hash = "sha256:f91263bb3f71948c5c8915b4d50ee370004f20a416f67fab3dcc90556c7e7199"}, -] - -[package.dependencies] -certifi = "*" -cryptography = ">=42.0.0" -pylsqpack = ">=0.3.3,<0.4.0" -pyopenssl = ">=24" -service-identity = ">=24.1.0" - -[package.extras] -dev = ["coverage[toml] (>=7.2.2)"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "airbyte-protocol-models" -version = "0.13.0" -description = "Declares the Airbyte Protocol." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "airbyte_protocol_models-0.13.0-py3-none-any.whl", hash = "sha256:fa8b7e1a85f9ae171c50b30d23b317da1740d051994fd3ed648f9dfba00250e2"}, - {file = "airbyte_protocol_models-0.13.0.tar.gz", hash = "sha256:09d8900ba8674a9315fa1799d17026f6b38d2187c08160449540ee93331ed2e7"}, -] - -[package.dependencies] -pydantic = ">=1.9.2,<2.0.0" - -[[package]] -name = "ansicon" -version = "1.89.0" -description = "Python wrapper for loading Jason Hood's ANSICON" -optional = false -python-versions = "*" -groups = ["main"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"}, - {file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"}, -] - -[[package]] -name = "anyio" -version = "4.6.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"}, - {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "asgiref" -version = "3.8.1" -description = "ASGI specs, helper code, and adapters" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, - {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, -] - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - -[[package]] -name = "asn1crypto" -version = "1.5.1" -description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, - {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, -] - -[[package]] -name = "asyncclick" -version = "8.1.7.2" -description = "Composable command line interface toolkit, async version" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "asyncclick-8.1.7.2-py3-none-any.whl", hash = "sha256:1ab940b04b22cb89b5b400725132b069d01b0c3472a9702c7a2c9d5d007ded02"}, - {file = "asyncclick-8.1.7.2.tar.gz", hash = "sha256:219ea0f29ccdc1bb4ff43bcab7ce0769ac6d48a04f997b43ec6bee99a222daa0"}, -] - -[package.dependencies] -anyio = "*" -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "asyncer" -version = "0.0.5" -description = "Asyncer, async and await, focused on developer experience." -optional = false -python-versions = ">=3.8,<4.0" -groups = ["main"] -files = [ - {file = "asyncer-0.0.5-py3-none-any.whl", hash = "sha256:ba06d6de3c750763868dffacf89b18d40b667605b0241d31c2ee43f188e2ab74"}, - {file = "asyncer-0.0.5.tar.gz", hash = "sha256:2979f3e04cbedfe5cfeb79027dcf7d004fcc4430a0ca0066ae20490f218ec06e"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5.0" - -[[package]] -name = "attrs" -version = "24.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, -] - -[package.extras] -benchmark = ["cloudpickle", "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 = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "beartype" -version = "0.19.0" -description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699"}, - {file = "beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573"}, -] - -[package.extras] -dev = ["autoapi (>=0.9.0)", "coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] -test = ["coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] -test-tox = ["equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "typing-extensions (>=3.10.0.0)"] -test-tox-coverage = ["coverage (>=5.5)"] - -[[package]] -name = "beautifulsoup4" -version = "4.14.2" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, -] - -[package.dependencies] -soupsieve = ">1.2" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "blessed" -version = "1.20.0" -description = "Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities." -optional = false -python-versions = ">=2.7" -groups = ["main"] -files = [ - {file = "blessed-1.20.0-py2.py3-none-any.whl", hash = "sha256:0c542922586a265e699188e52d5f5ac5ec0dd517e5a1041d90d2bbf23f906058"}, - {file = "blessed-1.20.0.tar.gz", hash = "sha256:2cdd67f8746e048f00df47a2880f4d6acbcdb399031b604e34ba8f71d5787680"}, -] - -[package.dependencies] -jinxed = {version = ">=1.1.0", markers = "platform_system == \"Windows\""} -six = ">=1.9.0" -wcwidth = ">=0.1.4" - -[[package]] -name = "blinker" -version = "1.8.2" -description = "Fast, simple object-to-object and broadcast signaling" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, - {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, -] - -[[package]] -name = "brotli" -version = "1.1.0" -description = "Python bindings for the Brotli compression library" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, - {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, - {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, - {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, - {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, - {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, - {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, - {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, - {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, - {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, - {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, - {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, - {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, - {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, - {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, - {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, - {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, - {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, -] - -[[package]] -name = "cachetools" -version = "5.3.3" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, -] - -[[package]] -name = "cattrs" -version = "24.1.2" -description = "Composable complex class support for attrs and dataclasses." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cattrs-24.1.2-py3-none-any.whl", hash = "sha256:67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0"}, - {file = "cattrs-24.1.2.tar.gz", hash = "sha256:8028cfe1ff5382df59dd36474a86e02d817b06eaf8af84555441bac915d2ef85"}, -] - -[package.dependencies] -attrs = ">=23.1.0" - -[package.extras] -bson = ["pymongo (>=4.4.0)"] -cbor2 = ["cbor2 (>=5.4.6)"] -msgpack = ["msgpack (>=1.0.5)"] -msgspec = ["msgspec (>=0.18.5)"] -orjson = ["orjson (>=3.9.2)"] -pyyaml = ["pyyaml (>=6.0)"] -tomlkit = ["tomlkit (>=0.11.8)"] -ujson = ["ujson (>=5.7.0)"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "cloud-sql-python-connector" -version = "1.12.1" -description = "The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cloud_sql_python_connector-1.12.1-py2.py3-none-any.whl", hash = "sha256:4cb5750dec34f52c70701c07a3c059f9d3e3c7cfeb2f4eff0691abd10a25204a"}, - {file = "cloud_sql_python_connector-1.12.1.tar.gz", hash = "sha256:94d0adf9bb42685ff0b401c2c51d031d9875d33aa614164f585e178824cced0a"}, -] - -[package.dependencies] -aiofiles = "*" -aiohttp = "*" -cryptography = ">=42.0.0" -google-auth = ">=2.28.0" -pg8000 = {version = ">=1.31.1", optional = true, markers = "extra == \"pg8000\""} -Requests = "*" - -[package.extras] -asyncpg = ["asyncpg (>=0.29.0)"] -pg8000 = ["pg8000 (>=1.31.1)"] -pymysql = ["PyMySQL (>=1.1.0)"] -pytds = ["python-tds (>=1.15.0)"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "connection-retriever" -version = "1.0.0" -description = "A tool to retrieve connection information from our Airbyte Cloud config api database" -optional = false -python-versions = "^3.10" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -click = "^8.1.7" -cloud-sql-python-connector = {version = "^1.7.0", extras = ["pg8000"]} -dpath = "^2.1.6" -google-cloud-iam = "^2.14.3" -google-cloud-logging = "^3.9.0" -google-cloud-secret-manager = "^2.18.3" -inquirer = "^3.4" -jinja2 = "^3.1.3" -pandas-gbq = "^0.22.0" -python-dotenv = "^1.0.1" -requests = "^2.31.0" -sqlalchemy = "^2.0.28" -tqdm = "^4.66.2" - -[package.source] -type = "git" -url = "ssh://git@github.com/airbytehq/airbyte-platform-internal" -reference = "HEAD" -resolved_reference = "d71a5ae1f1621628f49d88832d7496949b89e1c7" -subdirectory = "tools/connection-retriever" - -[[package]] -name = "cryptography" -version = "42.0.8" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, - {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, - {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, - {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, - {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, - {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, - {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, - {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, - {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, - {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, - {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, - {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, - {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, - {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "dagger-io" -version = "0.13.3" -description = "A client package for running Dagger pipelines in Python." -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "dagger_io-0.13.3-py3-none-any.whl", hash = "sha256:c3be14bd2c77ad265f944612123ef6f7653cf0365ffee0c70bf2a2662dc9783d"}, - {file = "dagger_io-0.13.3.tar.gz", hash = "sha256:fb9f602b8493f6e5f66afba4c6f51485dccb7c4795fbde7d92e188c69e8961b7"}, -] - -[package.dependencies] -anyio = ">=3.6.2" -beartype = ">=0.18.2" -cattrs = ">=22.2.0" -gql = {version = ">=3.5.0", extras = ["httpx"]} -opentelemetry-exporter-otlp-proto-grpc = ">=1.23.0" -opentelemetry-sdk = ">=1.23.0" -platformdirs = ">=2.6.2" -rich = ">=10.11.0" -typing-extensions = ">=4.8.0" - -[[package]] -name = "db-dtypes" -version = "1.3.0" -description = "Pandas Data Types for SQL systems (BigQuery, Spanner)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "db_dtypes-1.3.0-py2.py3-none-any.whl", hash = "sha256:7e65c59f849ccbe6f7bc4d0253edcc212a7907662906921caba3e4aadd0bc277"}, - {file = "db_dtypes-1.3.0.tar.gz", hash = "sha256:7bcbc8858b07474dc85b77bb2f3ae488978d1336f5ea73b58c39d9118bc3e91b"}, -] - -[package.dependencies] -numpy = ">=1.16.6" -packaging = ">=17.0" -pandas = ">=0.24.2" -pyarrow = ">=3.0.0" - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - -[[package]] -name = "deepdiff" -version = "6.7.1" -description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "deepdiff-6.7.1-py3-none-any.whl", hash = "sha256:58396bb7a863cbb4ed5193f548c56f18218060362311aa1dc36397b2f25108bd"}, - {file = "deepdiff-6.7.1.tar.gz", hash = "sha256:b367e6fa6caac1c9f500adc79ada1b5b1242c50d5f716a1a4362030197847d30"}, -] - -[package.dependencies] -ordered-set = ">=4.0.2,<4.2.0" - -[package.extras] -cli = ["click (==8.1.3)", "pyyaml (==6.0.1)"] -optimize = ["orjson"] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - -[[package]] -name = "docker" -version = "6.1.3" -description = "A Python library for the Docker Engine API." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, - {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, -] - -[package.dependencies] -packaging = ">=14.0" -pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} -requests = ">=2.26.0" -urllib3 = ">=1.26.0" -websocket-client = ">=0.32.0" - -[package.extras] -ssh = ["paramiko (>=2.4.3)"] - -[[package]] -name = "dpath" -version = "2.2.0" -description = "Filesystem-like pathing and searching for dictionaries" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576"}, - {file = "dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e"}, -] - -[[package]] -name = "duckdb" -version = "0.10.1" -description = "DuckDB in-process database" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "duckdb-0.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0ac172788e3d8e410e009e3699016a4d7f17b4c7cde20f98856fca1fea79d247"}, - {file = "duckdb-0.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f754c20d3b963574da58b0d22029681b79c63f2e32060f10b687f41b7bba54d7"}, - {file = "duckdb-0.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c68b1ef88b8cce185381ec69f437d20059c30623375bab41ac07a1104acdb57"}, - {file = "duckdb-0.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f566f615278844ea240c9a3497c0ef201331628f78e0f9f4d64f72f82210e750"}, - {file = "duckdb-0.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67d2996c3372a0f7d8f41f1c49e00ecdb26f83cdd9132b76730224ad68b1f1e3"}, - {file = "duckdb-0.10.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c3b3a18a58eebabb426beafc2f7da01d59805d660fc909e5e143b6db04d881a"}, - {file = "duckdb-0.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:343795d13ec3d8cd06c250225a05fd3c348c3ed49cccdde01addd46cb50f3559"}, - {file = "duckdb-0.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:33f99c2e9e4060464673912312b4ec91060d66638756592c9484c62824ff4e85"}, - {file = "duckdb-0.10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdbe4173729043b2fd949be83135b035820bb2faf64648500563b16f3f6f02ee"}, - {file = "duckdb-0.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f90738310a76bd1618acbc7345175582d36b6907cb0ed07841a3d800dea189d6"}, - {file = "duckdb-0.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d14d00560832592cbac2817847b649bd1d573f125d064518afb6eec5b02e15a"}, - {file = "duckdb-0.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11c0bf253c96079c6139e8a0880300d80f4dc9f21a8c5c239d2ebc060b227d46"}, - {file = "duckdb-0.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcc60833bb1a1fb2c33b052cf793fef48f681c565d982acff6ac7a86369794da"}, - {file = "duckdb-0.10.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88cdc0c2501dd7a65b1df2a76d7624b93d9b6d27febd2ee80b7e5643a0b40bcb"}, - {file = "duckdb-0.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:698a8d1d48b150d344d8aa6dbc30a22ea30fb14ff2b15c90004fc9fcb0b3a3e9"}, - {file = "duckdb-0.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:b450aa2b3e0eb1fc0f7ad276bd1e4a5a03b1a4def6c45366af17557de2cafbdf"}, - {file = "duckdb-0.10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:40dd55ea9c31abc69e5a8299f16c877e0b1950fd9a311c117efb4dd3c0dc8458"}, - {file = "duckdb-0.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7c1b3538bb9c2b49f48b26f092444525b22186efa4e77ba070603ed4a348a66"}, - {file = "duckdb-0.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bce024b69bae426b0739c470803f7b44261bdc0c0700ea7c41dff5f2d70ca4f3"}, - {file = "duckdb-0.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52af2a078340b2e1b57958477ebc1be07786d3ad5796777e87d4f453e0477b4c"}, - {file = "duckdb-0.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c52b08c773e52484542300339ebf295e3c9b12d5d7d49b2567e252c16205a7"}, - {file = "duckdb-0.10.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:097aa9b6d5c9f5d3ed8c35b16020a67731d04befc35f6b89ccb5db9d5f1489c4"}, - {file = "duckdb-0.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b5a14a80ad09d65c270d16761b04ea6b074811cdfde6b5e4db1a8b0184125d1b"}, - {file = "duckdb-0.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fb98dbbdbf8048b07223dc6e7401333bb4e83681dde4cded2d239051ea102b5"}, - {file = "duckdb-0.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28857b0d595c229827cc3631ae9b74ff52d11614435aa715e09d8629d2e1b609"}, - {file = "duckdb-0.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d85645136fc25026978b5db81869e8a120cfb60e1645a29a0f6dd155be9e59e"}, - {file = "duckdb-0.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2e10582db74b99051e718279c1be204c98a63a5b6aa4e09226b7249e414146"}, - {file = "duckdb-0.10.1-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6a88358d86a8ce689fdd4136514aebedf958e910361156a0bb0e53dc3c55f7d"}, - {file = "duckdb-0.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b025afa30fcdcede094386e7c519e6964d26de5ad95f4e04a2a0a713676d4465"}, - {file = "duckdb-0.10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:910be5005de7427c5231a7200027e0adb951e048c612b895340effcd3e660d5a"}, - {file = "duckdb-0.10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:13d81752763f14203a53981f32bd09731900eb6fda4048fbc532eae5e7bf30e5"}, - {file = "duckdb-0.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:21858225b8a5c5dead128f62e4e88facdcbfdce098e18cbcd86a6cd8f48fb2b3"}, - {file = "duckdb-0.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8bf46d55685906729998eca70ee751934e0425d86863148e658277526c54282e"}, - {file = "duckdb-0.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f786b4402b9c31461ea0520d919e2166df4f9e6e21fd3c7bb0035fa985b5dfe"}, - {file = "duckdb-0.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32e52c6e939a4bada220803e6bde6fc0ce870da5662a33cabdd3be14824183a6"}, - {file = "duckdb-0.10.1-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c563b565ea68cfebe9c4078646503b3d38930218f9c3c278277d58952873771"}, - {file = "duckdb-0.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af8382280f24273a535e08b80e9383ad739c66e22855ce68716dfbaeaf8910b9"}, - {file = "duckdb-0.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:2e6e01e2499e07873b09316bf4d6808f712c57034fa24c255565c4f92386e8e3"}, - {file = "duckdb-0.10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7791a0aa2cea972a612d31d4a289c81c5d00181328ed4f7642907f68f8b1fb9f"}, - {file = "duckdb-0.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ace20383fb0ba06229e060a6bb0bcfd48a4582a02e43f05991720504508eb59"}, - {file = "duckdb-0.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5aad3e085c33253c689205b5ea3c5d9d54117c1249276c90d495cb85d9adce76"}, - {file = "duckdb-0.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa08173f68e678793dfe6aab6490ac753204ca7935beb8dbde778dbe593552d8"}, - {file = "duckdb-0.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:525efad4e6caff80d0f6a51d466470839146e3880da36d4544fee7ff842e7e20"}, - {file = "duckdb-0.10.1-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48d84577216010ee407913bad9dc47af4cbc65e479c91e130f7bd909a32caefe"}, - {file = "duckdb-0.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6e65f00294c3b8576ae651e91e732ea1cefc4aada89c307fb02f49231fd11e1f"}, - {file = "duckdb-0.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:30aa9dbbfc1f9607249fc148af9e6d6fd253fdc2f4c9924d4957d6a535558b4f"}, - {file = "duckdb-0.10.1.tar.gz", hash = "sha256:0d5b6daa9bb54a635e371798994caa08f26d2f145ebcbc989e16b0a0104e84fb"}, -] - -[[package]] -name = "editor" -version = "1.6.6" -description = "🖋 Open the default text editor 🖋" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "editor-1.6.6-py3-none-any.whl", hash = "sha256:e818e6913f26c2a81eadef503a2741d7cca7f235d20e217274a009ecd5a74abf"}, - {file = "editor-1.6.6.tar.gz", hash = "sha256:bb6989e872638cd119db9a4fce284cd8e13c553886a1c044c6b8d8a160c871f8"}, -] - -[package.dependencies] -runs = "*" -xmod = "*" - -[[package]] -name = "flask" -version = "3.0.3" -description = "A simple framework for building complex web applications." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, - {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, -] - -[package.dependencies] -blinker = ">=1.6.2" -click = ">=8.1.3" -itsdangerous = ">=2.1.2" -Jinja2 = ">=3.1.2" -Werkzeug = ">=3.0.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[[package]] -name = "genson" -version = "1.3.0" -description = "GenSON is a powerful, user-friendly JSON Schema generator." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, - {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, -] - -[[package]] -name = "google-api-core" -version = "2.20.0" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_api_core-2.20.0-py3-none-any.whl", hash = "sha256:ef0591ef03c30bb83f79b3d0575c3f31219001fc9c5cf37024d08310aeffed8a"}, - {file = "google_api_core-2.20.0.tar.gz", hash = "sha256:f74dff1889ba291a4b76c5079df0711810e2d9da81abfdc99957bc961c1eb28f"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-auth" -version = "2.35.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, - {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-auth-oauthlib" -version = "1.2.1" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"}, - {file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"}, -] - -[package.dependencies] -google-auth = ">=2.15.0" -requests-oauthlib = ">=0.7.0" - -[package.extras] -tool = ["click (>=6.0.0)"] - -[[package]] -name = "google-cloud-appengine-logging" -version = "1.4.5" -description = "Google Cloud Appengine Logging API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_appengine_logging-1.4.5-py2.py3-none-any.whl", hash = "sha256:344e0244404049b42164e4d6dc718ca2c81b393d066956e7cb85fd9407ed9c48"}, - {file = "google_cloud_appengine_logging-1.4.5.tar.gz", hash = "sha256:de7d766e5d67b19fc5833974b505b32d2a5bbdfb283fd941e320e7cfdae4cb83"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "google-cloud-audit-log" -version = "0.3.0" -description = "Google Cloud Audit Protos" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_audit_log-0.3.0-py2.py3-none-any.whl", hash = "sha256:8340793120a1d5aa143605def8704ecdcead15106f754ef1381ae3bab533722f"}, - {file = "google_cloud_audit_log-0.3.0.tar.gz", hash = "sha256:901428b257020d8c1d1133e0fa004164a555e5a395c7ca3cdbb8486513df3a65"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.56.2,<2.0dev" -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "google-cloud-bigquery" -version = "3.26.0" -description = "Google BigQuery API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_bigquery-3.26.0-py2.py3-none-any.whl", hash = "sha256:e0e9ad28afa67a18696e624cbccab284bf2c0a3f6eeb9eeb0426c69b943793a8"}, - {file = "google_cloud_bigquery-3.26.0.tar.gz", hash = "sha256:edbdc788beea659e04c0af7fe4dcd6d9155344b98951a0d5055bd2f15da4ba23"}, -] - -[package.dependencies] -google-api-core = {version = ">=2.11.1,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0dev" -google-cloud-core = ">=2.4.1,<3.0.0dev" -google-resumable-media = ">=2.0.0,<3.0dev" -packaging = ">=20.0.0" -python-dateutil = ">=2.7.3,<3.0dev" -requests = ">=2.21.0,<3.0.0dev" - -[package.extras] -all = ["Shapely (>=1.8.4,<3.0.0dev)", "bigquery-magics (>=0.1.0)", "db-dtypes (>=0.3.0,<2.0.0dev)", "geopandas (>=0.9.0,<1.0dev)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "importlib-metadata (>=1.0.0)", "ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.22.3,<2.0.0dev)", "protobuf (>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0dev)"] -bigquery-v2 = ["proto-plus (>=1.22.3,<2.0.0dev)", "protobuf (>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev)"] -bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "pyarrow (>=3.0.0)"] -geopandas = ["Shapely (>=1.8.4,<3.0.0dev)", "geopandas (>=0.9.0,<1.0dev)"] -ipython = ["bigquery-magics (>=0.1.0)"] -ipywidgets = ["ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)"] -opentelemetry = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)"] -pandas = ["db-dtypes (>=0.3.0,<2.0.0dev)", "importlib-metadata (>=1.0.0)", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] -tqdm = ["tqdm (>=4.7.4,<5.0.0dev)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-iam" -version = "2.15.2" -description = "Google Cloud Iam API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_iam-2.15.2-py2.py3-none-any.whl", hash = "sha256:e1b00c63fa932b9f144527915d88e9c94eb4f748795721afb09f97200b49947e"}, - {file = "google_cloud_iam-2.15.2.tar.gz", hash = "sha256:09b135d96ba2cf6f80a7ed8011436e89d2588e8bb23cd6145c476302f4871a82"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "google-cloud-logging" -version = "3.11.2" -description = "Stackdriver Logging API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_logging-3.11.2-py2.py3-none-any.whl", hash = "sha256:0a755f04f184fbe77ad608258dc283a032485ebb4d0e2b2501964059ee9c898f"}, - {file = "google_cloud_logging-3.11.2.tar.gz", hash = "sha256:4897441c2b74f6eda9181c23a8817223b6145943314a821d64b729d30766cb2b"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -google-cloud-appengine-logging = ">=0.1.3,<2.0.0dev" -google-cloud-audit-log = ">=0.2.4,<1.0.0dev" -google-cloud-core = ">=2.0.0,<3.0.0dev" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -opentelemetry-api = ">=1.9.0" -proto-plus = {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""} -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "google-cloud-secret-manager" -version = "2.20.2" -description = "Google Cloud Secret Manager API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_cloud_secret_manager-2.20.2-py2.py3-none-any.whl", hash = "sha256:99b342ff722feef78aa5bad1c05c6be204f8fee01373a2eb6f05dba710b32879"}, - {file = "google_cloud_secret_manager-2.20.2.tar.gz", hash = "sha256:bbe24825e334f9e679e825e70d932118a7ff536e67c1ceb048da44111c87a45c"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "google-crc32c" -version = "1.6.0" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa"}, - {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e"}, - {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc"}, - {file = "google_crc32c-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4"}, - {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d"}, - {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f"}, - {file = "google_crc32c-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d"}, - {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00"}, - {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3"}, - {file = "google_crc32c-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205"}, - {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871"}, - {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57"}, - {file = "google_crc32c-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc"}, - {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24"}, - {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d"}, - {file = "google_crc32c-1.6.0.tar.gz", hash = "sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, - {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.65.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, - {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "gql" -version = "3.5.0" -description = "GraphQL client for Python" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "gql-3.5.0-py2.py3-none-any.whl", hash = "sha256:70dda5694a5b194a8441f077aa5fb70cc94e4ec08016117523f013680901ecb7"}, - {file = "gql-3.5.0.tar.gz", hash = "sha256:ccb9c5db543682b28f577069950488218ed65d4ac70bb03b6929aaadaf636de9"}, -] - -[package.dependencies] -anyio = ">=3.0,<5" -backoff = ">=1.11.1,<3.0" -graphql-core = ">=3.2,<3.3" -httpx = {version = ">=0.23.1,<1", optional = true, markers = "extra == \"httpx\""} -yarl = ">=1.6,<2.0" - -[package.extras] -aiohttp = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)"] -all = ["aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "websockets (>=10,<12)"] -botocore = ["botocore (>=1.21,<2)"] -dev = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "httpx (>=0.23.1,<1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "sphinx (>=5.3.0,<6)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "vcrpy (==4.4.0)", "websockets (>=10,<12)"] -httpx = ["httpx (>=0.23.1,<1)"] -requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)"] -test = ["aiofiles", "aiohttp (>=3.8.0,<4)", "aiohttp (>=3.9.0b0,<4)", "botocore (>=1.21,<2)", "httpx (>=0.23.1,<1)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=1.0.0,<2)", "vcrpy (==4.4.0)", "websockets (>=10,<12)"] -test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==7.4.2)", "pytest-asyncio (==0.21.1)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.4.0)"] -websockets = ["websockets (>=10,<12)"] - -[[package]] -name = "graphql-core" -version = "3.2.4" -description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." -optional = false -python-versions = "<4,>=3.6" -groups = ["main"] -files = [ - {file = "graphql-core-3.2.4.tar.gz", hash = "sha256:acbe2e800980d0e39b4685dd058c2f4042660b89ebca38af83020fd872ff1264"}, - {file = "graphql_core-3.2.4-py3-none-any.whl", hash = "sha256:1604f2042edc5f3114f49cac9d77e25863be51b23a54a61a23245cf32f6476f0"}, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -groups = ["main"] -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\"" -files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.13.1" -description = "IAM API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, - {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" - -[[package]] -name = "grpcio" -version = "1.66.2" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"}, - {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"}, - {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"}, - {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"}, - {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"}, - {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"}, - {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"}, - {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"}, - {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"}, - {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"}, - {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"}, - {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"}, - {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"}, - {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"}, - {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"}, - {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"}, - {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"}, - {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"}, - {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"}, - {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"}, - {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"}, - {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"}, - {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"}, - {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"}, - {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.66.2)"] - -[[package]] -name = "grpcio-status" -version = "1.62.3" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "grpcio-status-1.62.3.tar.gz", hash = "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485"}, - {file = "grpcio_status-1.62.3-py3-none-any.whl", hash = "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.3" -protobuf = ">=4.21.6" - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "h2" -version = "4.1.0" -description = "HTTP/2 State-Machine based protocol implementation" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, - {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, -] - -[package.dependencies] -hpack = ">=4.0,<5" -hyperframe = ">=6.0,<7" - -[[package]] -name = "hpack" -version = "4.0.0" -description = "Pure-Python HPACK header compression" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, - {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, -] - -[[package]] -name = "httpcore" -version = "1.0.6" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, - {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<1.0)"] - -[[package]] -name = "httpx" -version = "0.27.2" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "hyperframe" -version = "6.0.1" -description = "HTTP/2 framing layer for Python" -optional = false -python-versions = ">=3.6.1" -groups = ["main"] -files = [ - {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, - {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "importlib-metadata" -version = "8.4.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -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 = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "inquirer" -version = "3.4.0" -description = "Collection of common interactive command line user interfaces, based on Inquirer.js" -optional = false -python-versions = ">=3.8.1" -groups = ["main"] -files = [ - {file = "inquirer-3.4.0-py3-none-any.whl", hash = "sha256:bb0ec93c833e4ce7b51b98b1644b0a4d2bb39755c39787f6a504e4fee7a11b60"}, - {file = "inquirer-3.4.0.tar.gz", hash = "sha256:8edc99c076386ee2d2204e5e3653c2488244e82cb197b2d498b3c1b5ffb25d0b"}, -] - -[package.dependencies] -blessed = ">=1.19.0" -editor = ">=1.6.0" -readchar = ">=4.2.0" - -[[package]] -name = "itsdangerous" -version = "2.2.0" -description = "Safely pass data to untrusted environments and back." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, - {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jinxed" -version = "1.3.0" -description = "Jinxed Terminal Library" -optional = false -python-versions = "*" -groups = ["main"] -markers = "platform_system == \"Windows\"" -files = [ - {file = "jinxed-1.3.0-py2.py3-none-any.whl", hash = "sha256:b993189f39dc2d7504d802152671535b06d380b26d78070559551cbf92df4fc5"}, - {file = "jinxed-1.3.0.tar.gz", hash = "sha256:1593124b18a41b7a3da3b078471442e51dbad3d77b4d4f2b0c26ab6f7d660dbf"}, -] - -[package.dependencies] -ansicon = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "jiter" -version = "0.11.1" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, - {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87b2821795e28cc990939b68ce7a038edea680a24910bd68a79d54ff3f03c02"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83f6fa494d8bba14ab100417c80e70d32d737e805cb85be2052d771c76fcd1f8"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fbc6aea1daa2ec6f5ed465f0c5e7b0607175062ceebbea5ca70dd5ddab58083"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:302288e2edc43174bb2db838e94688d724f9aad26c5fb9a74f7a5fb427452a6a"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85db563fe3b367bb568af5d29dea4d4066d923b8e01f3417d25ebecd958de815"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1c1ba2b6b22f775444ef53bc2d5778396d3520abc7b2e1da8eb0c27cb3ffb10"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:523be464b14f8fd0cc78da6964b87b5515a056427a2579f9085ce30197a1b54a"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25b99b3f04cd2a38fefb22e822e35eb203a2cd37d680dbbc0c0ba966918af336"}, - {file = "jiter-0.11.1-cp310-cp310-win32.whl", hash = "sha256:47a79e90545a596bb9104109777894033347b11180d4751a216afef14072dbe7"}, - {file = "jiter-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:cace75621ae9bd66878bf69fbd4dfc1a28ef8661e0c2d0eb72d3d6f1268eddf5"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5"}, - {file = "jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a"}, - {file = "jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19"}, - {file = "jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250"}, - {file = "jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e"}, - {file = "jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87"}, - {file = "jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a"}, - {file = "jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b"}, - {file = "jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed"}, - {file = "jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d"}, - {file = "jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789"}, - {file = "jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec"}, - {file = "jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3"}, - {file = "jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea"}, - {file = "jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c"}, - {file = "jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991"}, - {file = "jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111"}, - {file = "jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7"}, - {file = "jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1"}, - {file = "jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:baa99c8db49467527658bb479857344daf0a14dff909b7f6714579ac439d1253"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:860fe55fa3b01ad0edf2adde1098247ff5c303d0121f9ce028c03d4f88c69502"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:173dd349d99b6feaf5a25a6fbcaf3489a6f947708d808240587a23df711c67db"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14ac1dca837514cc946a6ac2c4995d9695303ecc754af70a3163d057d1a444ab"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69af47de5f93a231d5b85f7372d3284a5be8edb4cc758f006ec5a1406965ac5e"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:685f8b3abd3bbd3e06e4dfe2429ff87fd5d7a782701151af99b1fcbd80e31b2b"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04afa2d4e5526e54ae8a58feea953b1844bf6e3526bc589f9de68e86d0ea01"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e92b927259035b50d8e11a8fdfe0ebd014d883e4552d37881643fa289a4bcf1"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e7bd8be4fad8d4c5558b7801770cd2da6c072919c6f247cc5336edb143f25304"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:121381a77a3c85987f3eba0d30ceaca9116f7463bedeec2fa79b2e7286b89b60"}, - {file = "jiter-0.11.1-cp39-cp39-win32.whl", hash = "sha256:160225407f6dfabdf9be1b44e22f06bc293a78a28ffa4347054698bd712dad06"}, - {file = "jiter-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:028e0d59bcdfa1079f8df886cdaefc6f515c27a5288dec956999260c7e4a7cfd"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960"}, - {file = "jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc"}, -] - -[[package]] -name = "jsonschema" -version = "3.2.0" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, - {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, -] - -[package.dependencies] -attrs = ">=17.4.0" -pyrsistent = ">=0.14.0" -setuptools = "*" -six = ">=1.11.0" - -[package.extras] -format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] -format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] - -[[package]] -name = "kaitaistruct" -version = "0.10" -description = "Kaitai Struct declarative parser generator for binary data: runtime library for Python" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] -files = [ - {file = "kaitaistruct-0.10-py2.py3-none-any.whl", hash = "sha256:a97350919adbf37fda881f75e9365e2fb88d04832b7a4e57106ec70119efb235"}, - {file = "kaitaistruct-0.10.tar.gz", hash = "sha256:a044dee29173d6afbacf27bcac39daf89b654dd418cfa009ab82d9178a9ae52a"}, -] - -[[package]] -name = "ldap3" -version = "2.9.1" -description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"}, - {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6" - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "3.0.1" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, - {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mitmproxy" -version = "10.3.0" -description = "An interactive, SSL/TLS-capable intercepting proxy for HTTP/1, HTTP/2, and WebSockets." -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "mitmproxy-10.3.0-py3-none-any.whl", hash = "sha256:e9c5330ddad4589bfbe001ba35a9654676c97ab51a7a714990f4a83324eab84c"}, -] - -[package.dependencies] -aioquic = ">=1.0.0,<2.0.0" -asgiref = ">=3.2.10,<3.9" -Brotli = ">=1.0,<1.2" -certifi = ">=2019.9.11" -cryptography = ">=42.0,<42.1" -flask = ">=1.1.1,<3.1" -h11 = ">=0.11,<0.15" -h2 = ">=4.1,<5" -hyperframe = ">=6.0,<7" -kaitaistruct = ">=0.10,<0.11" -ldap3 = ">=2.8,<2.10" -mitmproxy-rs = ">=0.5.1,<0.6" -msgpack = ">=1.0.0,<1.1.0" -passlib = ">=1.6.5,<1.8" -protobuf = ">=3.14,<6" -publicsuffix2 = ">=2.20190812,<3" -pydivert = {version = ">=2.0.3,<2.2", markers = "sys_platform == \"win32\""} -pyOpenSSL = ">=22.1,<24.2" -pyparsing = ">=2.4.2,<3.2" -pyperclip = ">=1.6.0,<1.9" -"ruamel.yaml" = ">=0.16,<0.19" -sortedcontainers = ">=2.3,<2.5" -tornado = ">=6.2,<7" -urwid-mitmproxy = ">=2.1.1,<2.2" -wsproto = ">=1.0,<1.3" -zstandard = ">=0.11,<0.23" - -[package.extras] -dev = ["build (>=0.10.0)", "click (>=7.0,<8.2)", "hypothesis (>=5.8,<7)", "pdoc (>=4.0.0)", "pyinstaller (==6.5.0)", "pytest (>=6.1.0,<9)", "pytest-asyncio (>=0.23,<0.24)", "pytest-cov (>=2.7.1,<5.1)", "pytest-timeout (>=1.3.3,<2.4)", "pytest-xdist (>=2.1.0,<3.6)", "requests (>=2.9.1,<3)", "tox (>=3.5,<5)", "wheel (>=0.36.2,<0.44)"] - -[[package]] -name = "mitmproxy-macos" -version = "0.5.2" -description = "" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "sys_platform == \"darwin\"" -files = [ - {file = "mitmproxy_macos-0.5.2-py3-none-any.whl", hash = "sha256:4aeee54ea4ecf7320b248292ef6dbc668ab14478efbdbf1234ae5ca120a13e63"}, -] - -[[package]] -name = "mitmproxy-rs" -version = "0.5.2" -description = "" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "mitmproxy_rs-0.5.2-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c9e0c7136579adb5f23b3d12c40b392122276133e5cd1b2319ad0e01d1ec8ec0"}, - {file = "mitmproxy_rs-0.5.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ca572479f32787de94b574dbedec042ab1d34d727d3597812fbdbd2f41922e"}, - {file = "mitmproxy_rs-0.5.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b4624e6b302d67fa94e50858a14a171708437e73146e3372ed042e01a09ca85"}, - {file = "mitmproxy_rs-0.5.2-cp310-abi3-win_amd64.whl", hash = "sha256:5e9f07b86b8a0f6a2c3c86c1fe902070e65868a0cf4d668ca7d1e2a802fe6e3f"}, - {file = "mitmproxy_rs-0.5.2.tar.gz", hash = "sha256:7583bea1ff5ea8e96c5cf12127e1698c52725f1dfdac6802891a4675b7287ba5"}, -] - -[package.dependencies] -mitmproxy_macos = {version = "0.5.2", markers = "sys_platform == \"darwin\""} -mitmproxy_windows = {version = "0.5.2", markers = "os_name == \"nt\""} - -[[package]] -name = "mitmproxy-windows" -version = "0.5.2" -description = "" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "os_name == \"nt\"" -files = [ - {file = "mitmproxy_windows-0.5.2-py3-none-any.whl", hash = "sha256:e7834cd4825a55d703b4aed34d2d7f85a2749ccb86396e328339070e528a3561"}, -] - -[[package]] -name = "msgpack" -version = "1.0.8" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, -] - -[[package]] -name = "multidict" -version = "6.1.0" -description = "multidict implementation" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, -] - -[[package]] -name = "mypy" -version = "1.11.2" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "numpy" -version = "2.1.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, - {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, - {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, - {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, - {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, - {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, - {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, - {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, - {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, - {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, -] - -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, -] - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "openai" -version = "1.109.1" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315"}, - {file = "openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.8)"] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<16)"] -voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] - -[[package]] -name = "opentelemetry-api" -version = "1.27.0" -description = "OpenTelemetry Python API" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"}, - {file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.4.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.27.0" -description = "OpenTelemetry Protobuf encoding" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.27.0-py3-none-any.whl", hash = "sha256:675db7fffcb60946f3a5c43e17d1168a3307a94a930ecf8d2ea1f286f3d4f79a"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.27.0.tar.gz", hash = "sha256:159d27cf49f359e3798c4c3eb8da6ef4020e292571bd8c5604a2a573231dd5c8"}, -] - -[package.dependencies] -opentelemetry-proto = "1.27.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.27.0" -description = "OpenTelemetry Collector Protobuf over gRPC Exporter" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.27.0-py3-none-any.whl", hash = "sha256:56b5bbd5d61aab05e300d9d62a6b3c134827bbd28d0b12f2649c2da368006c9e"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.27.0.tar.gz", hash = "sha256:af6f72f76bcf425dfb5ad11c1a6d6eca2863b91e63575f89bb7b4b55099d968f"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" -grpcio = ">=1.0.0,<2.0.0" -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.27.0" -opentelemetry-proto = "1.27.0" -opentelemetry-sdk = ">=1.27.0,<1.28.0" - -[[package]] -name = "opentelemetry-proto" -version = "1.27.0" -description = "OpenTelemetry Python Proto" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_proto-1.27.0-py3-none-any.whl", hash = "sha256:b133873de5581a50063e1e4b29cdcf0c5e253a8c2d8dc1229add20a4c3830ace"}, - {file = "opentelemetry_proto-1.27.0.tar.gz", hash = "sha256:33c9345d91dafd8a74fc3d7576c5a38f18b7fdf8d02983ac67485386132aedd6"}, -] - -[package.dependencies] -protobuf = ">=3.19,<5.0" - -[[package]] -name = "opentelemetry-sdk" -version = "1.27.0" -description = "OpenTelemetry Python SDK" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_sdk-1.27.0-py3-none-any.whl", hash = "sha256:365f5e32f920faf0fd9e14fdfd92c086e317eaa5f860edba9cdc17a380d9197d"}, - {file = "opentelemetry_sdk-1.27.0.tar.gz", hash = "sha256:d525017dea0ccce9ba4e0245100ec46ecdc043f2d7b8315d56b19aff0904fa6f"}, -] - -[package.dependencies] -opentelemetry-api = "1.27.0" -opentelemetry-semantic-conventions = "0.48b0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.48b0" -description = "OpenTelemetry Semantic Conventions" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl", hash = "sha256:a0de9f45c413a8669788a38569c7e0a11ce6ce97861a628cca785deecdc32a1f"}, - {file = "opentelemetry_semantic_conventions-0.48b0.tar.gz", hash = "sha256:12d74983783b6878162208be57c9effcb89dc88691c64992d70bb89dc00daa1a"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.27.0" - -[[package]] -name = "ordered-set" -version = "4.1.0" -description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, - {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, -] - -[package.extras] -dev = ["black", "mypy", "pytest"] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, -] - -[package.dependencies] -numpy = {version = ">=1.23.2", markers = "python_version == \"3.11\""} -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "pandas-gbq" -version = "0.22.0" -description = "Google BigQuery connector for pandas" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pandas-gbq-0.22.0.tar.gz", hash = "sha256:3fb24010c96e795c22b35d86601ef76f8aed84d5d17ceb8a4396a354c1949ece"}, - {file = "pandas_gbq-0.22.0-py2.py3-none-any.whl", hash = "sha256:e3bc1f9903928e4923dc5ff7f29fad9e9799aa406058567f1c654d934bf41323"}, -] - -[package.dependencies] -db-dtypes = ">=1.0.4,<2.0.0" -google-api-core = ">=2.10.2,<3.0.0dev" -google-auth = ">=2.13.0" -google-auth-oauthlib = ">=0.7.0" -google-cloud-bigquery = ">=3.3.5,<4.0.0dev" -numpy = ">=1.16.6" -packaging = ">=20.0.0" -pandas = ">=1.1.4" -pyarrow = ">=3.0.0" -pydata-google-auth = ">=1.5.0" -setuptools = "*" - -[package.extras] -bqstorage = ["google-cloud-bigquery-storage (>=2.16.2,<3.0.0dev)"] -tqdm = ["tqdm (>=4.23.0)"] - -[[package]] -name = "pandas-stubs" -version = "2.2.2.240909" -description = "Type annotations for pandas" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pandas_stubs-2.2.2.240909-py3-none-any.whl", hash = "sha256:e230f5fa4065f9417804f4d65cd98f86c002efcc07933e8abcd48c3fad9c30a2"}, - {file = "pandas_stubs-2.2.2.240909.tar.gz", hash = "sha256:3c0951a2c3e45e3475aed9d80b7147ae82f176b9e42e9fb321cfdebf3d411b3d"}, -] - -[package.dependencies] -numpy = ">=1.23.5" -types-pytz = ">=2022.1.1" - -[[package]] -name = "passlib" -version = "1.7.4" -description = "comprehensive password hashing framework supporting over 30 schemes" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, - {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, -] - -[package.extras] -argon2 = ["argon2-cffi (>=18.2.0)"] -bcrypt = ["bcrypt (>=3.1.0)"] -build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] -totp = ["cryptography"] - -[[package]] -name = "pg8000" -version = "1.31.2" -description = "PostgreSQL interface library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pg8000-1.31.2-py3-none-any.whl", hash = "sha256:436c771ede71af4d4c22ba867a30add0bc5c942d7ab27fadbb6934a487ecc8f6"}, - {file = "pg8000-1.31.2.tar.gz", hash = "sha256:1ea46cf09d8eca07fe7eaadefd7951e37bee7fabe675df164f1a572ffb300876"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.2" -scramp = ">=1.4.5" - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "propcache" -version = "0.2.0" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] - -[[package]] -name = "proto-plus" -version = "1.24.0" -description = "Beautiful, Pythonic protocol buffers." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<6.0.0dev" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "4.25.5" -description = "" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "protobuf-4.25.5-cp310-abi3-win32.whl", hash = "sha256:5e61fd921603f58d2f5acb2806a929b4675f8874ff5f330b7d6f7e2e784bbcd8"}, - {file = "protobuf-4.25.5-cp310-abi3-win_amd64.whl", hash = "sha256:4be0571adcbe712b282a330c6e89eae24281344429ae95c6d85e79e84780f5ea"}, - {file = "protobuf-4.25.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b2fde3d805354df675ea4c7c6338c1aecd254dfc9925e88c6d31a2bcb97eb173"}, - {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:919ad92d9b0310070f8356c24b855c98df2b8bd207ebc1c0c6fcc9ab1e007f3d"}, - {file = "protobuf-4.25.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fe14e16c22be926d3abfcb500e60cab068baf10b542b8c858fa27e098123e331"}, - {file = "protobuf-4.25.5-cp38-cp38-win32.whl", hash = "sha256:98d8d8aa50de6a2747efd9cceba361c9034050ecce3e09136f90de37ddba66e1"}, - {file = "protobuf-4.25.5-cp38-cp38-win_amd64.whl", hash = "sha256:b0234dd5a03049e4ddd94b93400b67803c823cfc405689688f59b34e0742381a"}, - {file = "protobuf-4.25.5-cp39-cp39-win32.whl", hash = "sha256:abe32aad8561aa7cc94fc7ba4fdef646e576983edb94a73381b03c53728a626f"}, - {file = "protobuf-4.25.5-cp39-cp39-win_amd64.whl", hash = "sha256:7a183f592dc80aa7c8da7ad9e55091c4ffc9497b3054452d629bb85fa27c2a45"}, - {file = "protobuf-4.25.5-py3-none-any.whl", hash = "sha256:0aebecb809cae990f8129ada5ca273d9d670b76d9bfc9b1809f0a9c02b7dbf41"}, - {file = "protobuf-4.25.5.tar.gz", hash = "sha256:7f8249476b4a9473645db7f8ab42b02fe1488cbe5fb72fddd445e0665afd8584"}, -] - -[[package]] -name = "publicsuffix2" -version = "2.20191221" -description = "Get a public suffix for a domain name using the Public Suffix List. Forked from and using the same API as the publicsuffix package." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "publicsuffix2-2.20191221-py2.py3-none-any.whl", hash = "sha256:786b5e36205b88758bd3518725ec8cfe7a8173f5269354641f581c6b80a99893"}, - {file = "publicsuffix2-2.20191221.tar.gz", hash = "sha256:00f8cc31aa8d0d5592a5ced19cccba7de428ebca985db26ac852d920ddd6fe7b"}, -] - -[[package]] -name = "pyarrow" -version = "17.0.0" -description = "Python library for Apache Arrow" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {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 = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.1" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, - {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "1.10.18" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, - {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, - {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, - {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, - {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, - {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, - {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, - {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, - {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, - {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, - {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, - {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, - {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, - {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, - {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, - {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, - {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, - {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, - {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, - {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, - {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, - {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, - {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, - {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, - {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, - {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, - {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pydash" -version = "7.0.7" -description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydash-7.0.7-py3-none-any.whl", hash = "sha256:c3c5b54eec0a562e0080d6f82a14ad4d5090229847b7e554235b5c1558c745e1"}, - {file = "pydash-7.0.7.tar.gz", hash = "sha256:cc935d5ac72dd41fb4515bdf982e7c864c8b5eeea16caffbab1936b849aaa49a"}, -] - -[package.dependencies] -typing-extensions = ">=3.10,<4.6.0 || >4.6.0" - -[package.extras] -dev = ["black", "build", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "furo", "invoke", "isort", "mypy", "pylint", "pytest", "pytest-cov", "pytest-mypy-testing", "sphinx", "sphinx-autodoc-typehints", "tox", "twine", "wheel"] - -[[package]] -name = "pydata-google-auth" -version = "1.8.2" -description = "PyData helpers for authenticating to Google APIs" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pydata-google-auth-1.8.2.tar.gz", hash = "sha256:547b6c0fbea657dcecd50887c5db8640ebec062a59a2b88e8ff8e53a04818303"}, - {file = "pydata_google_auth-1.8.2-py2.py3-none-any.whl", hash = "sha256:a9dce59af4a170ea60c4b2ebbc83ee1f74d34255a4f97b2469ae9a4a0dc98e99"}, -] - -[package.dependencies] -google-auth = {version = ">=1.25.0,<3.0dev", markers = "python_version >= \"3.6\""} -google-auth-oauthlib = {version = ">=0.4.0", markers = "python_version >= \"3.6\""} -setuptools = "*" - -[[package]] -name = "pydivert" -version = "2.1.0" -description = "Python binding to windivert driver" -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pydivert-2.1.0-py2.py3-none-any.whl", hash = "sha256:382db488e3c37c03ec9ec94e061a0b24334d78dbaeebb7d4e4d32ce4355d9da1"}, - {file = "pydivert-2.1.0.tar.gz", hash = "sha256:f0e150f4ff591b78e35f514e319561dadff7f24a82186a171dd4d465483de5b4"}, -] - -[package.extras] -docs = ["sphinx (>=1.4.8)"] -test = ["codecov (>=2.0.5)", "hypothesis (>=3.5.3)", "mock (>=1.0.1)", "pytest (>=3.0.3)", "pytest-cov (>=2.2.1)", "pytest-faulthandler (>=1.3.0,<2)", "pytest-timeout (>=1.0.0,<2)", "wheel (>=0.29)"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.9.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {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", "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 = "pylsqpack" -version = "0.3.18" -description = "Python wrapper for the ls-qpack QPACK library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pylsqpack-0.3.18-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1f415d2e03c779261ac7ed421a009a4c752eef6f1ef7b5a34c4a463a5e17fbad"}, - {file = "pylsqpack-0.3.18-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c84e6d4dcb708d766a50bfd16579d8a0bff4eb4e5f5dff9f3df4018454d4013b"}, - {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bac5f2dc255ae70e5a14033e769769b38bd4c980b365dacd88665610f245e36f"}, - {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75042b442a0a7a283b5adc21045e6583f3c817d40ccec769837bf2f90b79c494"}, - {file = "pylsqpack-0.3.18-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b5fd04bb27180286811f8e1659974e6e5e854a882de3f2aba8caefc1bb9ab81"}, - {file = "pylsqpack-0.3.18-cp38-abi3-win32.whl", hash = "sha256:a2798e1c08bd36875f77a1ebec0f130fdf9e27eebdb0499a764201d55ef78770"}, - {file = "pylsqpack-0.3.18-cp38-abi3-win_amd64.whl", hash = "sha256:40465d025b946bca195bdaed74b3b79fe3f7f419ab1d4bc4109dca34ba9881d7"}, - {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ae628cd359ecb466dd85f151ea1ad53de3114e5a9ae0f0ac1408fb43a4318032"}, - {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a248be29d9ca1fa2ebd7ef4b8ac166d17df0d8d4631b4499c8c566e221d4e5b"}, - {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:005ddce84bdcbf5c3cf99f764504208e1aa0a91a8331bf47108f2708f2a315e6"}, - {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dd664354422d4cd51c189febb5f5d22bf3d8c453cc25517c04ce01a57478060"}, - {file = "pylsqpack-0.3.18-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c003eb882f41e4dbd093243c67b97c8634209b4d5ba7edd16163b1ff37306254"}, - {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8ea75152e8cb8b8c7cfef11c3aa5ebe5b226bd850889f56ff70a688e9680acbf"}, - {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4cccfd91afd589994f844fd1dbae0acdb58a8ab929d8edeadb25339deb6590"}, - {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e1bbe47514b83cd03158e5558ef8cc44f578169c1820098be9f3cc4137f16a"}, - {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1054b0b44f6141a99e84a9aa6a27c9df028e9223747b893e8e37cdc95b602f1"}, - {file = "pylsqpack-0.3.18-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:711f3aa645f72a928e22606c1f026cde905de23efc07028fe1bc7429f73ec8ee"}, - {file = "pylsqpack-0.3.18.tar.gz", hash = "sha256:45ae55e721877505f4d5ccd49591d69353f2a548a8673dfafb251d385b3c097f"}, -] - -[[package]] -name = "pyopenssl" -version = "24.1.0" -description = "Python wrapper module around the OpenSSL library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "pyOpenSSL-24.1.0-py3-none-any.whl", hash = "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad"}, - {file = "pyOpenSSL-24.1.0.tar.gz", hash = "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"}, -] - -[package.dependencies] -cryptography = ">=41.0.5,<43" - -[package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] -test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] - -[[package]] -name = "pyparsing" -version = "3.1.4" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -groups = ["main"] -files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyperclip" -version = "1.8.2" -description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, -] - -[[package]] -name = "pyrsistent" -version = "0.20.0" -description = "Persistent/Functional/Immutable data structures" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, - {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, - {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, - {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, - {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, - {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, - {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, -] - -[[package]] -name = "pytest" -version = "8.3.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.8" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, - {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-sugar" -version = "1.0.0" -description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, - {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, -] - -[package.dependencies] -packaging = ">=21.3" -pytest = ">=6.2.0" -termcolor = ">=2.1.0" - -[package.extras] -dev = ["black", "flake8", "pre-commit"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-slugify" -version = "8.0.4" -description = "A Python slugify application that also handles Unicode" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, - {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, -] - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - -[[package]] -name = "pywin32" -version = "307" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pywin32-307-cp310-cp310-win32.whl", hash = "sha256:f8f25d893c1e1ce2d685ef6d0a481e87c6f510d0f3f117932781f412e0eba31b"}, - {file = "pywin32-307-cp310-cp310-win_amd64.whl", hash = "sha256:36e650c5e5e6b29b5d317385b02d20803ddbac5d1031e1f88d20d76676dd103d"}, - {file = "pywin32-307-cp310-cp310-win_arm64.whl", hash = "sha256:0c12d61e0274e0c62acee79e3e503c312426ddd0e8d4899c626cddc1cafe0ff4"}, - {file = "pywin32-307-cp311-cp311-win32.whl", hash = "sha256:fec5d27cc893178fab299de911b8e4d12c5954e1baf83e8a664311e56a272b75"}, - {file = "pywin32-307-cp311-cp311-win_amd64.whl", hash = "sha256:987a86971753ed7fdd52a7fb5747aba955b2c7fbbc3d8b76ec850358c1cc28c3"}, - {file = "pywin32-307-cp311-cp311-win_arm64.whl", hash = "sha256:fd436897c186a2e693cd0437386ed79f989f4d13d6f353f8787ecbb0ae719398"}, - {file = "pywin32-307-cp312-cp312-win32.whl", hash = "sha256:07649ec6b01712f36debf39fc94f3d696a46579e852f60157a729ac039df0815"}, - {file = "pywin32-307-cp312-cp312-win_amd64.whl", hash = "sha256:00d047992bb5dcf79f8b9b7c81f72e0130f9fe4b22df613f755ab1cc021d8347"}, - {file = "pywin32-307-cp312-cp312-win_arm64.whl", hash = "sha256:b53658acbfc6a8241d72cc09e9d1d666be4e6c99376bc59e26cdb6223c4554d2"}, - {file = "pywin32-307-cp313-cp313-win32.whl", hash = "sha256:ea4d56e48dc1ab2aa0a5e3c0741ad6e926529510516db7a3b6981a1ae74405e5"}, - {file = "pywin32-307-cp313-cp313-win_amd64.whl", hash = "sha256:576d09813eaf4c8168d0bfd66fb7cb3b15a61041cf41598c2db4a4583bf832d2"}, - {file = "pywin32-307-cp313-cp313-win_arm64.whl", hash = "sha256:b30c9bdbffda6a260beb2919f918daced23d32c79109412c2085cbc513338a0a"}, - {file = "pywin32-307-cp37-cp37m-win32.whl", hash = "sha256:5101472f5180c647d4525a0ed289ec723a26231550dbfd369ec19d5faf60e511"}, - {file = "pywin32-307-cp37-cp37m-win_amd64.whl", hash = "sha256:05de55a7c110478dc4b202230e98af5e0720855360d2b31a44bb4e296d795fba"}, - {file = "pywin32-307-cp38-cp38-win32.whl", hash = "sha256:13d059fb7f10792542082f5731d5d3d9645320fc38814759313e5ee97c3fac01"}, - {file = "pywin32-307-cp38-cp38-win_amd64.whl", hash = "sha256:7e0b2f93769d450a98ac7a31a087e07b126b6d571e8b4386a5762eb85325270b"}, - {file = "pywin32-307-cp39-cp39-win32.whl", hash = "sha256:55ee87f2f8c294e72ad9d4261ca423022310a6e79fb314a8ca76ab3f493854c6"}, - {file = "pywin32-307-cp39-cp39-win_amd64.whl", hash = "sha256:e9d5202922e74985b037c9ef46778335c102b74b95cec70f629453dbe7235d87"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "readchar" -version = "4.2.0" -description = "Library to easily read single chars and key strokes" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "readchar-4.2.0-py3-none-any.whl", hash = "sha256:2a587a27c981e6d25a518730ad4c88c429c315439baa6fda55d7a8b3ac4cb62a"}, - {file = "readchar-4.2.0.tar.gz", hash = "sha256:44807cbbe377b72079fea6cba8aa91c809982d7d727b2f0dbb2d1a8084914faa"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -description = "OAuthlib authentication support for Requests." -optional = false -python-versions = ">=3.4" -groups = ["main"] -files = [ - {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, - {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[package]] -name = "rich" -version = "13.9.2" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, - {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -groups = ["main"] -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruamel-yaml" -version = "0.18.6" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, - {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, -] - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} - -[package.extras] -docs = ["mercurial (>5.7)", "ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.8" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -optional = false -python-versions = ">=3.6" -groups = ["main"] -markers = "platform_python_implementation == \"CPython\"" -files = [ - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, - {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, - {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, -] - -[[package]] -name = "ruff" -version = "0.6.9" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, -] - -[[package]] -name = "runs" -version = "1.2.2" -description = "🏃 Run a block of text as a subprocess 🏃" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "runs-1.2.2-py3-none-any.whl", hash = "sha256:0980dcbc25aba1505f307ac4f0e9e92cbd0be2a15a1e983ee86c24c87b839dfd"}, - {file = "runs-1.2.2.tar.gz", hash = "sha256:9dc1815e2895cfb3a48317b173b9f1eac9ba5549b36a847b5cc60c3bf82ecef1"}, -] - -[package.dependencies] -xmod = "*" - -[[package]] -name = "scramp" -version = "1.4.5" -description = "An implementation of the SCRAM protocol." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "scramp-1.4.5-py3-none-any.whl", hash = "sha256:50e37c464fc67f37994e35bee4151e3d8f9320e9c204fca83a5d313c121bbbe7"}, - {file = "scramp-1.4.5.tar.gz", hash = "sha256:be3fbe774ca577a7a658117dca014e5d254d158cecae3dd60332dfe33ce6d78e"}, -] - -[package.dependencies] -asn1crypto = ">=1.5.1" - -[[package]] -name = "segment-analytics-python" -version = "2.3.3" -description = "The hassle-free way to integrate analytics into any python application." -optional = false -python-versions = ">=3.6.0" -groups = ["main"] -files = [ - {file = "segment-analytics-python-2.3.3.tar.gz", hash = "sha256:ce6b3b4387ec9ebc5b55842c44d7dd63b4d4b0b8188e268c4492f909e5eeeed8"}, - {file = "segment_analytics_python-2.3.3-py2.py3-none-any.whl", hash = "sha256:769251706d71f4c96d2039391d119222dbd9faf00308400f7b314ec9fb86cfc7"}, -] - -[package.dependencies] -backoff = ">=2.1,<3.0" -PyJWT = ">=2.8,<3.0" -python-dateutil = ">=2.2,<3.0" -requests = ">=2.7,<3.0" - -[package.extras] -test = ["flake8 (==3.7.9)", "mock (==2.0.0)", "pylint (==2.8.0)"] - -[[package]] -name = "service-identity" -version = "24.1.0" -description = "Service identity verification for pyOpenSSL & cryptography." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "service_identity-24.1.0-py3-none-any.whl", hash = "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a"}, - {file = "service_identity-24.1.0.tar.gz", hash = "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221"}, -] - -[package.dependencies] -attrs = ">=19.1.0" -cryptography = "*" -pyasn1 = "*" -pyasn1-modules = "*" - -[package.extras] -dev = ["pyopenssl", "service-identity[idna,mypy,tests]"] -docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"] -idna = ["idna"] -mypy = ["idna", "mypy", "types-pyopenssl"] -tests = ["coverage[toml] (>=5.0.2)", "pytest"] - -[[package]] -name = "setuptools" -version = "75.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["main"] -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] - -[[package]] -name = "soupsieve" -version = "2.8" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, - {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.35" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, - {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e"}, - {file = "SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf"}, - {file = "SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f021d334f2ca692523aaf7bbf7592ceff70c8594fad853416a81d66b35e3abf9"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05c3f58cf91683102f2f0265c0db3bd3892e9eedabe059720492dbaa4f922da1"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:032d979ce77a6c2432653322ba4cbeabf5a6837f704d16fa38b5a05d8e21fa00"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2e795c2f7d7249b75bb5f479b432a51b59041580d20599d4e112b5f2046437a3"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:cc32b2990fc34380ec2f6195f33a76b6cdaa9eecf09f0c9404b74fc120aef36f"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-win32.whl", hash = "sha256:9509c4123491d0e63fb5e16199e09f8e262066e58903e84615c301dde8fa2e87"}, - {file = "SQLAlchemy-2.0.35-cp37-cp37m-win_amd64.whl", hash = "sha256:3655af10ebcc0f1e4e06c5900bb33e080d6a1fa4228f502121f28a3b1753cde5"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-win32.whl", hash = "sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0"}, - {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, - {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, - {file = "SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1"}, - {file = "sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f"}, -] - -[package.dependencies] -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] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "termcolor" -version = "2.5.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, - {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, - {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, -] - -[[package]] -name = "tornado" -version = "6.4.1" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, - {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, - {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, - {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, - {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, - {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, - {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, -] - -[[package]] -name = "tqdm" -version = "4.66.5" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "types-cachetools" -version = "5.5.0.20240820" -description = "Typing stubs for cachetools" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-cachetools-5.5.0.20240820.tar.gz", hash = "sha256:b888ab5c1a48116f7799cd5004b18474cd82b5463acb5ffb2db2fc9c7b053bc0"}, - {file = "types_cachetools-5.5.0.20240820-py3-none-any.whl", hash = "sha256:efb2ed8bf27a4b9d3ed70d33849f536362603a90b8090a328acf0cd42fda82e2"}, -] - -[[package]] -name = "types-pytz" -version = "2024.2.0.20241003" -description = "Typing stubs for pytz" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-pytz-2024.2.0.20241003.tar.gz", hash = "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44"}, - {file = "types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7"}, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20240917" -description = "Typing stubs for PyYAML" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, - {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, -] - -[[package]] -name = "types-requests" -version = "2.32.0.20240914" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405"}, - {file = "types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2024.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -groups = ["main"] -files = [ - {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, - {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "urwid-mitmproxy" -version = "2.1.2.1" -description = "A full-featured console (xterm et al.) user interface library" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "urwid-mitmproxy-2.1.2.1.tar.gz", hash = "sha256:be6238e587acb92bdd43b241af0a10dc23798e8cf3eddef834164eb637686cda"}, - {file = "urwid_mitmproxy-2.1.2.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:29c62a593235d2b69ba4557648588c54420ef030794b9d28e65f50bffdde85c3"}, - {file = "urwid_mitmproxy-2.1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:d93bdc87cbb329cd262f8ada586e954a95ca4cc7249eca5b348b87f47ef1adb5"}, - {file = "urwid_mitmproxy-2.1.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb7eb42fcc426ea02c321159631d396ec0cd6ebebabb310f3a4493579ff2e09"}, - {file = "urwid_mitmproxy-2.1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:66c40dcead7fedbb312516e18574d216b0e7c728bf5cd0e240eee53737234b45"}, - {file = "urwid_mitmproxy-2.1.2.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7a8a95460a519e0388d91a198acb31836dce40d14e599a0b9c24ba70fa4ec64b"}, - {file = "urwid_mitmproxy-2.1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:d2d536ad412022365b5e1974cde9029b86cfc30f3960ae073f959630f0c27c21"}, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[[package]] -name = "websocket-client" -version = "1.8.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, - {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "werkzeug" -version = "3.0.4" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, - {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, -] - -[package.dependencies] -h11 = ">=0.9.0,<1" - -[[package]] -name = "xmod" -version = "1.8.1" -description = "🌱 Turn any object into a module 🌱" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "xmod-1.8.1-py3-none-any.whl", hash = "sha256:a24e9458a4853489042522bdca9e50ee2eac5ab75c809a91150a8a7f40670d48"}, - {file = "xmod-1.8.1.tar.gz", hash = "sha256:38c76486b9d672c546d57d8035df0beb7f4a9b088bc3fb2de5431ae821444377"}, -] - -[[package]] -name = "yarl" -version = "1.14.0" -description = "Yet another URL library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "yarl-1.14.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1bfc25aa6a7c99cf86564210f79a0b7d4484159c67e01232b116e445b3036547"}, - {file = "yarl-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0cf21f46a15d445417de8fc89f2568852cf57fe8ca1ab3d19ddb24d45c0383ae"}, - {file = "yarl-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1dda53508df0de87b6e6b0a52d6718ff6c62a5aca8f5552748404963df639269"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:587c3cc59bc148a9b1c07a019346eda2549bc9f468acd2f9824d185749acf0a6"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3007a5b75cb50140708420fe688c393e71139324df599434633019314ceb8b59"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06ff23462398333c78b6f4f8d3d70410d657a471c2c5bbe6086133be43fc8f1a"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689a99a42ee4583fcb0d3a67a0204664aa1539684aed72bdafcbd505197a91c4"}, - {file = "yarl-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0547ab1e9345dc468cac8368d88ea4c5bd473ebc1d8d755347d7401982b5dd8"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:742aef0a99844faaac200564ea6f5e08facb285d37ea18bd1a5acf2771f3255a"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:176110bff341b6730f64a1eb3a7070e12b373cf1c910a9337e7c3240497db76f"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46a9772a1efa93f9cd170ad33101c1817c77e0e9914d4fe33e2da299d7cf0f9b"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ee2c68e4f2dd1b1c15b849ba1c96fac105fca6ffdb7c1e8be51da6fabbdeafb9"}, - {file = "yarl-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:047b258e00b99091b6f90355521f026238c63bd76dcf996d93527bb13320eefd"}, - {file = "yarl-1.14.0-cp310-cp310-win32.whl", hash = "sha256:0aa92e3e30a04f9462a25077db689c4ac5ea9ab6cc68a2e563881b987d42f16d"}, - {file = "yarl-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:d9baec588f015d0ee564057aa7574313c53a530662ffad930b7886becc85abdf"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:07f9eaf57719d6721ab15805d85f4b01a5b509a0868d7320134371bcb652152d"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c14b504a74e58e2deb0378b3eca10f3d076635c100f45b113c18c770b4a47a50"}, - {file = "yarl-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a682a127930f3fc4e42583becca6049e1d7214bcad23520c590edd741d2114"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73bedd2be05f48af19f0f2e9e1353921ce0c83f4a1c9e8556ecdcf1f1eae4892"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3ab950f8814f3b7b5e3eebc117986f817ec933676f68f0a6c5b2137dd7c9c69"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b693c63e7e64b524f54aa4888403c680342d1ad0d97be1707c531584d6aeeb4f"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cb3e40eaa98489f1e2e8b29f5ad02ee1ee40d6ce6b88d50cf0f205de1d9d2c"}, - {file = "yarl-1.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f24f08b6c9b9818fd80612c97857d28f9779f0d1211653ece9844fc7b414df2"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29a84a46ec3ebae7a1c024c055612b11e9363a8a23238b3e905552d77a2bc51b"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5cd5dad8366e0168e0fd23d10705a603790484a6dbb9eb272b33673b8f2cce72"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a152751af7ef7b5d5fa6d215756e508dd05eb07d0cf2ba51f3e740076aa74373"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3d569f877ed9a708e4c71a2d13d2940cb0791da309f70bd970ac1a5c088a0a92"}, - {file = "yarl-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a615cad11ec3428020fb3c5a88d85ce1b5c69fd66e9fcb91a7daa5e855325dd"}, - {file = "yarl-1.14.0-cp311-cp311-win32.whl", hash = "sha256:bab03192091681d54e8225c53f270b0517637915d9297028409a2a5114ff4634"}, - {file = "yarl-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:985623575e5c4ea763056ffe0e2d63836f771a8c294b3de06d09480538316b13"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fc2c80bc87fba076e6cbb926216c27fba274dae7100a7b9a0983b53132dd99f2"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:55c144d363ad4626ca744556c049c94e2b95096041ac87098bb363dcc8635e8d"}, - {file = "yarl-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b03384eed107dbeb5f625a99dc3a7de8be04fc8480c9ad42fccbc73434170b20"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f72a0d746d38cb299b79ce3d4d60ba0892c84bbc905d0d49c13df5bace1b65f8"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8648180b34faaea4aa5b5ca7e871d9eb1277033fa439693855cf0ea9195f85f1"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9557c9322aaa33174d285b0c1961fb32499d65ad1866155b7845edc876c3c835"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f50eb3837012a937a2b649ec872b66ba9541ad9d6f103ddcafb8231cfcafd22"}, - {file = "yarl-1.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8892fa575ac9b1b25fae7b221bc4792a273877b9b56a99ee2d8d03eeb3dbb1d2"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6a2c5c5bb2556dfbfffffc2bcfb9c235fd2b566d5006dfb2a37afc7e3278a07"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ab3abc0b78a5dfaa4795a6afbe7b282b6aa88d81cf8c1bb5e394993d7cae3457"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:47eede5d11d669ab3759b63afb70d28d5328c14744b8edba3323e27dc52d298d"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fe4d2536c827f508348d7b40c08767e8c7071614250927233bf0c92170451c0a"}, - {file = "yarl-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0fd7b941dd1b00b5f0acb97455fea2c4b7aac2dd31ea43fb9d155e9bc7b78664"}, - {file = "yarl-1.14.0-cp312-cp312-win32.whl", hash = "sha256:99ff3744f5fe48288be6bc402533b38e89749623a43208e1d57091fc96b783b9"}, - {file = "yarl-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ca3894e9e9f72da93544f64988d9c052254a338a9f855165f37f51edb6591de"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d02d700705d67e09e1f57681f758f0b9d4412eeb70b2eb8d96ca6200b486db3"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:30600ba5db60f7c0820ef38a2568bb7379e1418ecc947a0f76fd8b2ff4257a97"}, - {file = "yarl-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e85d86527baebb41a214cc3b45c17177177d900a2ad5783dbe6f291642d4906f"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37001e5d4621cef710c8dc1429ca04e189e572f128ab12312eab4e04cf007132"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4f4547944d4f5cfcdc03f3f097d6f05bbbc915eaaf80a2ee120d0e756de377d"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ff4c819757f9bdb35de049a509814d6ce851fe26f06eb95a392a5640052482"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68ac1a09392ed6e3fd14be880d39b951d7b981fd135416db7d18a6208c536561"}, - {file = "yarl-1.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96952f642ac69075e44c7d0284528938fdff39422a1d90d3e45ce40b72e5e2d9"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a56fbe3d7f3bce1d060ea18d2413a2ca9ca814eea7cedc4d247b5f338d54844e"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e2637d75e92763d1322cb5041573279ec43a80c0f7fbbd2d64f5aee98447b17"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9abe80ae2c9d37c17599557b712e6515f4100a80efb2cda15f5f070306477cd2"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:217a782020b875538eebf3948fac3a7f9bbbd0fd9bf8538f7c2ad7489e80f4e8"}, - {file = "yarl-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9cfef3f14f75bf6aba73a76caf61f9d00865912a04a4393c468a7ce0981b519"}, - {file = "yarl-1.14.0-cp313-cp313-win32.whl", hash = "sha256:d8361c7d04e6a264481f0b802e395f647cd3f8bbe27acfa7c12049efea675bd1"}, - {file = "yarl-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:bc24f968b82455f336b79bf37dbb243b7d76cd40897489888d663d4e028f5069"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:91d875f75fabf76b3018c5f196bf3d308ed2b49ddcb46c1576d6b075754a1393"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4009def9be3a7e5175db20aa2d7307ecd00bbf50f7f0f989300710eee1d0b0b9"}, - {file = "yarl-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:582cedde49603f139be572252a318b30dc41039bc0b8165f070f279e5d12187f"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbd9ff43a04f8ffe8a959a944c2dca10d22f5f99fc6a459f49c3ebfb409309d9"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f805e37ed16cc212fdc538a608422d7517e7faf539bedea4fe69425bc55d76"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95e16e9eaa2d7f5d87421b8fe694dd71606aa61d74b824c8d17fc85cc51983d1"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:816d24f584edefcc5ca63428f0b38fee00b39fe64e3c5e558f895a18983efe96"}, - {file = "yarl-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd2660c01367eb3ef081b8fa0a5da7fe767f9427aa82023a961a5f28f0d4af6c"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:94b2bb9bcfd5be9d27004ea4398fb640373dd0c1a9e219084f42c08f77a720ab"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c2089a9afef887664115f7fa6d3c0edd6454adaca5488dba836ca91f60401075"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2192f718db4a8509f63dd6d950f143279211fa7e6a2c612edc17d85bf043d36e"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:8385ab36bf812e9d37cf7613999a87715f27ef67a53f0687d28c44b819df7cb0"}, - {file = "yarl-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b4c1ecba93e7826dc71ddba75fb7740cdb52e7bd0be9f03136b83f54e6a1f511"}, - {file = "yarl-1.14.0-cp38-cp38-win32.whl", hash = "sha256:e749af6c912a7bb441d105c50c1a3da720474e8acb91c89350080dd600228f0e"}, - {file = "yarl-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:147e36331f6f63e08a14640acf12369e041e0751bb70d9362df68c2d9dcf0c87"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a9f917966d27f7ce30039fe8d900f913c5304134096554fd9bea0774bcda6d1"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a2f8fb7f944bcdfecd4e8d855f84c703804a594da5123dd206f75036e536d4d"}, - {file = "yarl-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f4e475f29a9122f908d0f1f706e1f2fc3656536ffd21014ff8a6f2e1b14d1d8"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8089d4634d8fa2b1806ce44fefa4979b1ab2c12c0bc7ef3dfa45c8a374811348"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b16f6c75cffc2dc0616ea295abb0e1967601bd1fb1e0af6a1de1c6c887f3439"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498b3c55087b9d762636bca9b45f60d37e51d24341786dc01b81253f9552a607"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3f8bfc1db82589ef965ed234b87de30d140db8b6dc50ada9e33951ccd8ec07a"}, - {file = "yarl-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:625f207b1799e95e7c823f42f473c1e9dbfb6192bd56bba8695656d92be4535f"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:781e2495e408a81e4eaeedeb41ba32b63b1980dddf8b60dbbeff6036bcd35049"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:659603d26d40dd4463200df9bfbc339fbfaed3fe32e5c432fe1dc2b5d4aa94b4"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4e0d45ebf975634468682c8bec021618b3ad52c37619e5c938f8f831fa1ac5c0"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a2e4725a08cb2b4794db09e350c86dee18202bb8286527210e13a1514dc9a59a"}, - {file = "yarl-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19268b4fec1d7760134f2de46ef2608c2920134fb1fa61e451f679e41356dc55"}, - {file = "yarl-1.14.0-cp39-cp39-win32.whl", hash = "sha256:337912bcdcf193ade64b9aae5a4017a0a1950caf8ca140362e361543c6773f21"}, - {file = "yarl-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:b6d0147574ce2e7b812c989e50fa72bbc5338045411a836bd066ce5fc8ac0bce"}, - {file = "yarl-1.14.0-py3-none-any.whl", hash = "sha256:c8ed4034f0765f8861620c1f2f2364d2e58520ea288497084dae880424fc0d9f"}, - {file = "yarl-1.14.0.tar.gz", hash = "sha256:88c7d9d58aab0724b979ab5617330acb1c7030b79379c8138c1c8c94e121d1b3"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" - -[[package]] -name = "zipp" -version = "3.20.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[[package]] -name = "zstandard" -version = "0.22.0" -description = "Zstandard bindings for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "zstandard-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275df437ab03f8c033b8a2c181e51716c32d831082d93ce48002a5227ec93019"}, - {file = "zstandard-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ac9957bc6d2403c4772c890916bf181b2653640da98f32e04b96e4d6fb3252a"}, - {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe3390c538f12437b859d815040763abc728955a52ca6ff9c5d4ac707c4ad98e"}, - {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2"}, - {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e1856c8313bc688d5df069e106a4bc962eef3d13372020cc6e3ebf5e045202"}, - {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1a90ba9a4c9c884bb876a14be2b1d216609385efb180393df40e5172e7ecf356"}, - {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3db41c5e49ef73641d5111554e1d1d3af106410a6c1fb52cf68912ba7a343a0d"}, - {file = "zstandard-0.22.0-cp310-cp310-win32.whl", hash = "sha256:d8593f8464fb64d58e8cb0b905b272d40184eac9a18d83cf8c10749c3eafcd7e"}, - {file = "zstandard-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a4b358947a65b94e2501ce3e078bbc929b039ede4679ddb0460829b12f7375"}, - {file = "zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08"}, - {file = "zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26"}, - {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09"}, - {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775"}, - {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8"}, - {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94"}, - {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88"}, - {file = "zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440"}, - {file = "zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd"}, - {file = "zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a"}, - {file = "zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912"}, - {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f"}, - {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c"}, - {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4"}, - {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc"}, - {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45"}, - {file = "zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2"}, - {file = "zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c"}, - {file = "zstandard-0.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d75f693bb4e92c335e0645e8845e553cd09dc91616412d1d4650da835b5449df"}, - {file = "zstandard-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36a47636c3de227cd765e25a21dc5dace00539b82ddd99ee36abae38178eff9e"}, - {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68953dc84b244b053c0d5f137a21ae8287ecf51b20872eccf8eaac0302d3e3b0"}, - {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2612e9bb4977381184bb2463150336d0f7e014d6bb5d4a370f9a372d21916f69"}, - {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23d2b3c2b8e7e5a6cb7922f7c27d73a9a615f0a5ab5d0e03dd533c477de23004"}, - {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d43501f5f31e22baf822720d82b5547f8a08f5386a883b32584a185675c8fbf"}, - {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a493d470183ee620a3df1e6e55b3e4de8143c0ba1b16f3ded83208ea8ddfd91d"}, - {file = "zstandard-0.22.0-cp38-cp38-win32.whl", hash = "sha256:7034d381789f45576ec3f1fa0e15d741828146439228dc3f7c59856c5bcd3292"}, - {file = "zstandard-0.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:d8fff0f0c1d8bc5d866762ae95bd99d53282337af1be9dc0d88506b340e74b73"}, - {file = "zstandard-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fdd53b806786bd6112d97c1f1e7841e5e4daa06810ab4b284026a1a0e484c0b"}, - {file = "zstandard-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73a1d6bd01961e9fd447162e137ed949c01bdb830dfca487c4a14e9742dccc93"}, - {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9501f36fac6b875c124243a379267d879262480bf85b1dbda61f5ad4d01b75a3"}, - {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f260e4c7294ef275744210a4010f116048e0c95857befb7462e033f09442fe"}, - {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959665072bd60f45c5b6b5d711f15bdefc9849dd5da9fb6c873e35f5d34d8cfb"}, - {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d22fdef58976457c65e2796e6730a3ea4a254f3ba83777ecfc8592ff8d77d303"}, - {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7ccf5825fd71d4542c8ab28d4d482aace885f5ebe4b40faaa290eed8e095a4c"}, - {file = "zstandard-0.22.0-cp39-cp39-win32.whl", hash = "sha256:f058a77ef0ece4e210bb0450e68408d4223f728b109764676e1a13537d056bb0"}, - {file = "zstandard-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:e9e9d4e2e336c529d4c435baad846a181e39a982f823f7e4495ec0b0ec8538d2"}, - {file = "zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70"}, -] - -[package.dependencies] -cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} - -[package.extras] -cffi = ["cffi (>=1.11)"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.11,<3.12" -content-hash = "d6f93822b88fad4aaf0ae5d60932e0e93d9e95aa79fdddc44f9f6045387d3b4c" diff --git a/airbyte-ci/connectors/live-tests/pyproject.toml b/airbyte-ci/connectors/live-tests/pyproject.toml deleted file mode 100644 index 6f1a882b0590..000000000000 --- a/airbyte-ci/connectors/live-tests/pyproject.toml +++ /dev/null @@ -1,75 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "live-tests" -version = "0.21.4" -description = "Contains utilities for testing connectors against live data." -authors = ["Airbyte "] -license = "MIT" -homepage = "https://github.com/airbytehq/airbyte" -readme = "README.md" -packages = [ - { include = "live_tests", from = "src" }, -] - -[tool.poetry.dependencies] -python = "^3.11,<3.12" -airbyte-protocol-models = "<1.0.0" -cachetools = "~=5.3.3" -dagger-io = "==0.13.3" -decorator = ">=5.1.1" -deepdiff = "6.7.1" -jsonschema = "*" -pydantic = "*" -pytest-asyncio = "~=0.23.5" -pytest = "^8.1.1" -pydash = "~=7.0.7" -docker = ">=6,<7" -asyncclick = "^8.1.7.1" -connection-retriever = {git = "git@github.com:airbytehq/airbyte-platform-internal", subdirectory = "tools/connection-retriever"} -duckdb = "<=0.10.1" # Pinned due to this issue https://github.com/duckdb/duckdb/issues/11152 -pandas = "^2.2.1" -pytest-sugar = "^1.0.0" -asyncer = "^0.0.5" -rich = "^13.7.1" -mitmproxy = "^10.2.4" -requests = "<=2.31.1" # Pinned due to this issue https://github.com/docker/docker-py/issues/3256#issuecomment-2127688011 -pyyaml = "~=6.0.1" -dpath = "^2.1.6" -genson = "^1.3.0" -segment-analytics-python = "^2.3.2" -python-slugify = ">=8.0.4" -beautifulsoup4 = "^4.12.0" -openai = "^1.0.0" - - -[tool.poetry.group.dev.dependencies] -ruff = "^0.6" -mypy = "^1.8.0" -types-cachetools = "^5.3.0.7" -pandas-stubs = "^2.2.0.240218" -types-requests = "^2.31.0.20240311" -types-pyyaml = "^6.0.12.20240311" - -[tool.ruff] -target-version = "py311" -line-length = 140 - -[tool.ruff.lint] -select = ["I", "F"] - -[tool.ruff.lint.isort] -known-first-party = ["connection-retriever"] - -[tool.poe.tasks] -test = "pytest tests" -type_check = "mypy src --disallow-untyped-defs" -pre-push = [] - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = [] -required_environment_variables = ["DOCKER_HUB_USERNAME", "DOCKER_HUB_PASSWORD"] diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/__init__.py deleted file mode 100644 index 51502a263eae..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/__init__.py deleted file mode 100644 index 62501987fb84..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from .base_backend import BaseBackend -from .duckdb_backend import DuckDbBackend -from .file_backend import FileBackend - -__all__ = ["BaseBackend", "FileBackend", "DuckDbBackend"] diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/base_backend.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/base_backend.py deleted file mode 100644 index 303799e245eb..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/base_backend.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from abc import ABC, abstractmethod -from collections.abc import Iterable - -from airbyte_protocol.models import AirbyteMessage # type: ignore - - -class BaseBackend(ABC): - """ - Interface to be shared between the file backend and the database backend(s) - """ - - @abstractmethod - def write(self, airbyte_messages: Iterable[AirbyteMessage]) -> None: ... diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/duckdb_backend.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/duckdb_backend.py deleted file mode 100644 index d5bfa0fdff27..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/duckdb_backend.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging -import re -from collections.abc import Iterable -from pathlib import Path -from typing import Optional - -import duckdb -from airbyte_protocol.models import AirbyteMessage # type: ignore - -from live_tests.commons.backends.file_backend import FileBackend - - -class DuckDbBackend(FileBackend): - SAMPLE_SIZE = -1 - - def __init__( - self, - output_directory: Path, - duckdb_path: Path, - schema: Optional[Iterable[str]] = None, - ): - super().__init__(output_directory) - self.duckdb_path = duckdb_path - self.schema = schema - - @property - def jsonl_files_to_insert(self) -> Iterable[Path]: - return [ - self.jsonl_catalogs_path, - self.jsonl_connection_status_path, - self.jsonl_specs_path, - self.jsonl_states_path, - self.jsonl_traces_path, - self.jsonl_logs_path, - self.jsonl_controls_path, - self.jsonl_records_path, - ] - - @staticmethod - def sanitize_table_name(table_name: str) -> str: - sanitized = str(table_name).replace(" ", "_") - sanitized = re.sub(r"[^\w\s]", "", sanitized) - if sanitized and sanitized[0].isdigit(): - sanitized = "_" + sanitized - return sanitized - - def write(self, airbyte_messages: Iterable[AirbyteMessage]) -> None: - # Use the FileBackend to write the messages to disk as jsonl files - super().write(airbyte_messages) - duck_db_conn = duckdb.connect(str(self.duckdb_path)) - - if self.schema: - sanitized_schema_name = "_".join([self.sanitize_table_name(s) for s in self.schema]) - duck_db_conn.sql(f"CREATE SCHEMA IF NOT EXISTS {sanitized_schema_name}") - duck_db_conn.sql(f"USE {sanitized_schema_name}") - logging.info(f"Using schema {sanitized_schema_name}") - - for json_file in self.jsonl_files_to_insert: - if json_file.exists(): - table_name = self.sanitize_table_name(json_file.stem) - logging.info(f"Creating table {table_name} from {json_file} in schema {sanitized_schema_name}") - duck_db_conn.sql( - f"CREATE TABLE {table_name} AS SELECT * FROM read_json_auto('{json_file}', sample_size = {self.SAMPLE_SIZE}, format = 'newline_delimited')" - ) - logging.info(f"Table {table_name} created in schema {sanitized_schema_name}") - - for json_file in self.record_per_stream_paths_data_only.values(): - if json_file.exists(): - table_name = self.sanitize_table_name(f"records_{json_file.stem}") - logging.info( - f"Creating table {table_name} from {json_file} in schema {sanitized_schema_name} to store stream records with the data field only" - ) - duck_db_conn.sql( - f"CREATE TABLE {self.sanitize_table_name(table_name)} AS SELECT * FROM read_json_auto('{json_file}', sample_size = {self.SAMPLE_SIZE}, format = 'newline_delimited')" - ) - logging.info(f"Table {table_name} created in schema {sanitized_schema_name}") - duck_db_conn.close() diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/file_backend.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/file_backend.py deleted file mode 100644 index ae3e68c228f8..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/backends/file_backend.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import json -import logging -from collections.abc import Iterable -from pathlib import Path -from typing import Any, TextIO - -from airbyte_protocol.models import AirbyteMessage # type: ignore -from airbyte_protocol.models import Type as AirbyteMessageType -from cachetools import LRUCache, cached - -from live_tests.commons.backends.base_backend import BaseBackend -from live_tests.commons.utils import sanitize_stream_name - - -class FileDescriptorLRUCache(LRUCache): - def popitem(self) -> tuple[Any, Any]: - filepath, fd = LRUCache.popitem(self) - fd.close() # type: ignore # Close the file descriptor when it's evicted from the cache - return filepath, fd - - -class FileBackend(BaseBackend): - RELATIVE_CATALOGS_PATH = "catalog.jsonl" - RELATIVE_CONNECTION_STATUS_PATH = "connection_status.jsonl" - RELATIVE_RECORDS_PATH = "records.jsonl" - RELATIVE_SPECS_PATH = "spec.jsonl" - RELATIVE_STATES_PATH = "states.jsonl" - RELATIVE_TRACES_PATH = "traces.jsonl" - RELATIVE_LOGS_PATH = "logs.jsonl" - RELATIVE_CONTROLS_PATH = "controls.jsonl" - CACHE = FileDescriptorLRUCache(maxsize=250) - - def __init__(self, output_directory: Path): - self._output_directory = output_directory - self.record_per_stream_directory = self._output_directory / "records_per_stream" - self.record_per_stream_directory.mkdir(exist_ok=True, parents=True) - self.record_per_stream_paths: dict[str, Path] = {} - self.record_per_stream_paths_data_only: dict[str, Path] = {} - - @property - def jsonl_specs_path(self) -> Path: - return (self._output_directory / self.RELATIVE_SPECS_PATH).resolve() - - @property - def jsonl_catalogs_path(self) -> Path: - return (self._output_directory / self.RELATIVE_CATALOGS_PATH).resolve() - - @property - def jsonl_connection_status_path(self) -> Path: - return (self._output_directory / self.RELATIVE_CONNECTION_STATUS_PATH).resolve() - - @property - def jsonl_records_path(self) -> Path: - return (self._output_directory / self.RELATIVE_RECORDS_PATH).resolve() - - @property - def jsonl_states_path(self) -> Path: - return (self._output_directory / self.RELATIVE_STATES_PATH).resolve() - - @property - def jsonl_traces_path(self) -> Path: - return (self._output_directory / self.RELATIVE_TRACES_PATH).resolve() - - @property - def jsonl_logs_path(self) -> Path: - return (self._output_directory / self.RELATIVE_LOGS_PATH).resolve() - - @property - def jsonl_controls_path(self) -> Path: - return (self._output_directory / self.RELATIVE_CONTROLS_PATH).resolve() - - @property - def jsonl_files(self) -> Iterable[Path]: - return [ - self.jsonl_catalogs_path, - self.jsonl_connection_status_path, - self.jsonl_records_path, - self.jsonl_specs_path, - self.jsonl_states_path, - self.jsonl_traces_path, - self.jsonl_logs_path, - self.jsonl_controls_path, - ] - - def write(self, airbyte_messages: Iterable[AirbyteMessage]) -> None: - """ - Write AirbyteMessages to the appropriate file. - - We use an LRU cache here to manage open file objects, in order to limit the number of concurrently open file - descriptors. This mitigates the risk of hitting limits on the number of open file descriptors, particularly for - connections with a high number of streams. The cache is designed to automatically close files upon eviction. - """ - - @cached(cache=self.CACHE) - def _open_file(path: Path) -> TextIO: - return open(path, "a") - - try: - logging.info("Writing airbyte messages to disk") - for _message in airbyte_messages: - if not isinstance(_message, AirbyteMessage): - continue - filepaths, messages = self._get_filepaths_and_messages(_message) - for filepath, message in zip(filepaths, messages, strict=False): - _open_file(self._output_directory / filepath).write(f"{message}\n") - logging.info("Finished writing airbyte messages to disk") - finally: - for f in self.CACHE.values(): - f.close() - - def _get_filepaths_and_messages(self, message: AirbyteMessage) -> tuple[tuple[str, ...], tuple[str, ...]]: - if message.type == AirbyteMessageType.CATALOG: - return (self.RELATIVE_CATALOGS_PATH,), (message.catalog.json(),) - - elif message.type == AirbyteMessageType.CONNECTION_STATUS: - return (self.RELATIVE_CONNECTION_STATUS_PATH,), (message.connectionStatus.json(),) - - elif message.type == AirbyteMessageType.RECORD: - stream_name = message.record.stream - stream_file_path = self.record_per_stream_directory / f"{sanitize_stream_name(stream_name)}.jsonl" - stream_file_path_data_only = self.record_per_stream_directory / f"{sanitize_stream_name(stream_name)}_data_only.jsonl" - self.record_per_stream_paths[stream_name] = stream_file_path - self.record_per_stream_paths_data_only[stream_name] = stream_file_path_data_only - return ( - self.RELATIVE_RECORDS_PATH, - str(stream_file_path), - str(stream_file_path_data_only), - ), ( - message.json(sort_keys=True), - message.json(sort_keys=True), - json.dumps(message.record.data, sort_keys=True), - ) - - elif message.type == AirbyteMessageType.SPEC: - return (self.RELATIVE_SPECS_PATH,), (message.spec.json(),) - - elif message.type == AirbyteMessageType.STATE: - return (self.RELATIVE_STATES_PATH,), (message.state.json(),) - - elif message.type == AirbyteMessageType.TRACE: - return (self.RELATIVE_TRACES_PATH,), (message.trace.json(),) - - elif message.type == AirbyteMessageType.LOG: - return (self.RELATIVE_LOGS_PATH,), (message.log.json(),) - - elif message.type == AirbyteMessageType.CONTROL: - return (self.RELATIVE_CONTROLS_PATH,), (message.control.json(),) - - raise NotImplementedError(f"No handling for AirbyteMessage type {message.type} has been implemented. This is unexpected.") diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/connection_objects_retrieval.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/connection_objects_retrieval.py deleted file mode 100644 index b531e3425be8..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/connection_objects_retrieval.py +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import json -import os -import textwrap -from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple - -import rich -from connection_retriever import ConnectionObject, retrieve_objects # type: ignore -from connection_retriever.retrieval import TestingCandidate, retrieve_testing_candidates - -from live_tests.commons import hacks -from live_tests.commons.models import ConnectionSubset -from live_tests.commons.utils import build_connection_url - -from .models import AirbyteCatalog, Command, ConfiguredAirbyteCatalog, ConnectionObjects, SecretDict - -console = rich.get_console() - - -class InvalidConnectionError(Exception): - pass - - -def parse_config(config: dict | str | None) -> Optional[SecretDict]: - if not config: - return None - if isinstance(config, str): - return SecretDict(json.loads(config)) - else: - return SecretDict(config) - - -def parse_catalog(catalog: dict | str | None) -> Optional[AirbyteCatalog]: - if not catalog: - return None - if isinstance(catalog, str): - return AirbyteCatalog.parse_obj(json.loads(catalog)) - else: - return AirbyteCatalog.parse_obj(catalog) - - -def parse_configured_catalog( - configured_catalog: dict | str | None, selected_streams: set[str] | None = None -) -> Optional[ConfiguredAirbyteCatalog]: - if not configured_catalog: - return None - if isinstance(configured_catalog, str): - configured_catalog = json.loads(configured_catalog) - patched_catalog = hacks.patch_configured_catalog(configured_catalog) - catalog = ConfiguredAirbyteCatalog.parse_obj(patched_catalog) - if selected_streams: - return ConfiguredAirbyteCatalog(streams=[stream for stream in catalog.streams if stream.stream.name in selected_streams]) - return catalog - - -def parse_state(state: dict | str | None) -> Optional[dict]: - if not state: - return None - if isinstance(state, str): - return json.loads(state) - else: - return state - - -def get_connector_config_from_path(config_path: Path) -> Optional[SecretDict]: - return parse_config(config_path.read_text()) - - -def get_state_from_path(state_path: Path) -> Optional[dict]: - return parse_state(state_path.read_text()) - - -def get_configured_catalog_from_path(path: Path, selected_streams: Optional[set[str]] = None) -> Optional[ConfiguredAirbyteCatalog]: - return parse_configured_catalog(path.read_text(), selected_streams) - - -COMMAND_TO_REQUIRED_OBJECT_TYPES = { - Command.SPEC: set(), - Command.CHECK: {ConnectionObject.SOURCE_CONFIG}, - Command.DISCOVER: {ConnectionObject.SOURCE_CONFIG}, - Command.READ: {ConnectionObject.SOURCE_CONFIG, ConnectionObject.CONFIGURED_CATALOG}, - Command.READ_WITH_STATE: { - ConnectionObject.SOURCE_CONFIG, - ConnectionObject.CONFIGURED_CATALOG, - ConnectionObject.STATE, - }, -} - - -def get_connection_objects( - requested_objects: set[ConnectionObject], - connection_id: Optional[str], - custom_config_path: Optional[Path], - custom_configured_catalog_path: Optional[Path], - custom_state_path: Optional[Path], - retrieval_reason: Optional[str], - connector_image: Optional[str] = None, - connector_version: Optional[str] = None, - auto_select_connections: bool = False, - selected_streams: Optional[set[str]] = None, - connection_subset: ConnectionSubset = ConnectionSubset.SANDBOXES, - max_connections: Optional[int] = None, -) -> List[ConnectionObjects]: - """This function retrieves the connection objects values. - It checks that the required objects are available and raises a UsageError if they are not. - If a connection_id is provided, it retrieves the connection objects from the connection. - If custom objects are provided, it overrides the retrieved objects with them. - - Args: - requested_objects (Set[ConnectionObject]): The set of requested connection objects. - connection_id (Optional[str]): The connection id to retrieve the connection objects for. - custom_config_path (Optional[Path]): The local path to the custom config to use. - custom_configured_catalog_path (Optional[Path]): The local path to the custom catalog to use. - custom_state_path (Optional[Path]): The local path to the custom state to use. - retrieval_reason (Optional[str]): The reason to access the connection objects. - fail_if_missing_objects (bool, optional): Whether to raise a ValueError if a required object is missing. Defaults to True. - connector_image (Optional[str]): The image name for the connector under test. - connector_version (Optional[str]): The version for the connector under test. - auto_select_connections (bool, optional): Whether to automatically select connections if no connection id is passed. Defaults to False. - selected_streams (Optional[Set[str]]): The set of selected streams to use when auto selecting a connection. - connection_subset (ConnectionSubset): The subset of connections to select from. - max_connections (Optional[int]): The maximum number of connections to retrieve. - Raises: - click.UsageError: If a required object is missing for the command. - click.UsageError: If a retrieval reason is missing when passing a connection id. - Returns: - List[ConnectionObjects]: List of connection objects. - """ - if connection_id and auto_select_connections: - raise ValueError("Cannot set both `connection_id` and `auto_select_connections`.") - if auto_select_connections and not connector_image: - raise ValueError("A connector image must be provided when using auto_select_connections.") - - custom_config = get_connector_config_from_path(custom_config_path) if custom_config_path else None - custom_configured_catalog = ( - get_configured_catalog_from_path(custom_configured_catalog_path, selected_streams) if custom_configured_catalog_path else None - ) - custom_state = get_state_from_path(custom_state_path) if custom_state_path else None - is_ci = os.getenv("CI", False) - - if connection_id: - if not retrieval_reason: - raise ValueError("A retrieval reason is required to access the connection objects when passing a connection id.") - - connection_objects = _get_connection_objects_from_retrieved_objects( - requested_objects, - retrieval_reason=retrieval_reason, - source_docker_repository=connector_image, - source_docker_image_tag=connector_version, - selected_streams=selected_streams, - connection_id=connection_id, - custom_config=custom_config, - custom_configured_catalog=custom_configured_catalog, - custom_state=custom_state, - connection_subset=connection_subset, - max_connections=max_connections, - ) - - else: - if auto_select_connections: - connection_objects = _get_connection_objects_from_retrieved_objects( - requested_objects, - retrieval_reason=retrieval_reason, - source_docker_repository=connector_image, - source_docker_image_tag=connector_version, - selected_streams=selected_streams, - custom_config=custom_config, - custom_configured_catalog=custom_configured_catalog, - custom_state=custom_state, - connection_subset=connection_subset, - max_connections=max_connections, - ) - - else: - # We don't make any requests to the connection-retriever; it is expected that config/catalog/state have been provided if needed for the commands being run. - connection_objects = [ - ConnectionObjects( - source_config=custom_config, - destination_config=custom_config, - catalog=None, - configured_catalog=custom_configured_catalog, - state=custom_state, - workspace_id=None, - source_id=None, - destination_id=None, - connection_id=None, - source_docker_image=None, - ) - ] - if not connection_objects: - raise ValueError("No connection objects could be fetched.") - - all_connection_ids = [connection_object.connection_id for connection_object in connection_objects] - assert len(set(all_connection_ids)) == len(all_connection_ids), "Connection IDs must be unique." - return connection_objects - - -def _find_best_candidates_subset(candidates: List[TestingCandidate]) -> List[Tuple[TestingCandidate, List[str]]]: - """ - This function reduces the list of candidates to the best subset of candidates. - The best subset is the one which maximizes the number of streams tested and minimizes the number of candidates. - """ - candidates_sorted_by_duration = sorted(candidates, key=lambda x: x.last_attempt_duration_in_microseconds) - - tested_streams = set() - candidates_and_streams_to_test = [] - - for candidate in candidates_sorted_by_duration: - candidate_streams_to_test = [] - for stream in candidate.streams_with_data: - # The candidate is selected if one of its streams has not been tested yet - if stream not in tested_streams: - candidate_streams_to_test.append(stream) - tested_streams.add(stream) - if candidate_streams_to_test: - candidates_and_streams_to_test.append((candidate, candidate_streams_to_test)) - return candidates_and_streams_to_test - - -def _get_connection_objects_from_retrieved_objects( - requested_objects: Set[ConnectionObject], - retrieval_reason: str, - source_docker_repository: str, - source_docker_image_tag: str, - selected_streams: Optional[Set[str]], - connection_id: Optional[str] = None, - custom_config: Optional[Dict] = None, - custom_configured_catalog: Optional[ConfiguredAirbyteCatalog] = None, - custom_state: Optional[Dict] = None, - connection_subset: ConnectionSubset = ConnectionSubset.SANDBOXES, - max_connections: Optional[int] = None, -): - console.log( - textwrap.dedent( - """ - Retrieving connection objects from the database. - We will build a subset of candidates to test. - This subset should minimize the number of candidates and sync duration while maximizing the number of streams tested. - We patch configured catalogs to only test streams once. - If the max_connections parameter is set, we will only keep the top connections with the most streams to test. - """ - ) - ) - try: - candidates = retrieve_testing_candidates( - source_docker_repository=source_docker_repository, - source_docker_image_tag=source_docker_image_tag, - with_streams=selected_streams, - connection_subset=connection_subset, - ) - except IndexError: - raise InvalidConnectionError( - f"No candidates were found for the provided source docker image ({source_docker_repository}:{source_docker_image_tag})." - ) - # If the connection_id is provided, we filter the candidates to only keep the ones with the same connection_id - if connection_id: - candidates = [candidate for candidate in candidates if candidate.connection_id == connection_id] - - candidates_and_streams_to_test = _find_best_candidates_subset(candidates) - candidates_and_streams_to_test = sorted(candidates_and_streams_to_test, key=lambda x: len(x[1]), reverse=True) - if max_connections: - candidates_and_streams_to_test = candidates_and_streams_to_test[:max_connections] - - number_of_streams_tested = sum([len(streams_to_test) for _, streams_to_test in candidates_and_streams_to_test]) - console.log(f"Selected {len(candidates_and_streams_to_test)} candidates to test {number_of_streams_tested} streams.") - - all_connection_objects = [] - for candidate, streams_to_test in candidates_and_streams_to_test: - retrieved_objects = retrieve_objects( - requested_objects, - retrieval_reason=retrieval_reason, - source_docker_repository=source_docker_repository, - source_docker_image_tag=source_docker_image_tag, - connection_id=candidate.connection_id, - connection_subset=connection_subset, - ) - retrieved_objects = retrieved_objects[0] - retrieved_source_config = parse_config(retrieved_objects.source_config) - retrieved_destination_config = parse_config(retrieved_objects.destination_config) - retrieved_catalog = parse_catalog(retrieved_objects.catalog) - retrieved_configured_catalog = parse_configured_catalog(retrieved_objects.configured_catalog, selected_streams) - retrieved_state = parse_state(retrieved_objects.state) - - retrieved_source_docker_image = retrieved_objects.source_docker_image - connection_url = build_connection_url(retrieved_objects.workspace_id, retrieved_objects.connection_id) - if retrieved_source_docker_image is None: - raise InvalidConnectionError( - f"No docker image was found for connection ID {retrieved_objects.connection_id}. Please double check that the latest job run used version {source_docker_image_tag}. Connection URL: {connection_url}" - ) - elif retrieved_source_docker_image.split(":")[0] != source_docker_repository: - raise InvalidConnectionError( - f"The provided docker image ({source_docker_repository}) does not match the image for connection ID {retrieved_objects.connection_id}. Please double check that this connection is using the correct image. Connection URL: {connection_url}" - ) - elif retrieved_source_docker_image.split(":")[1] != source_docker_image_tag: - raise InvalidConnectionError( - f"The provided docker image tag ({source_docker_image_tag}) does not match the image tag for connection ID {retrieved_objects.connection_id}. Please double check that this connection is using the correct image tag and the latest job ran using this version. Connection URL: {connection_url}" - ) - - all_connection_objects.append( - ConnectionObjects( - source_config=custom_config if custom_config else retrieved_source_config, - destination_config=custom_config if custom_config else retrieved_destination_config, - catalog=retrieved_catalog, - configured_catalog=custom_configured_catalog if custom_configured_catalog else retrieved_configured_catalog, - state=custom_state if custom_state else retrieved_state, - workspace_id=retrieved_objects.workspace_id, - source_id=retrieved_objects.source_id, - destination_id=retrieved_objects.destination_id, - source_docker_image=retrieved_source_docker_image, - connection_id=retrieved_objects.connection_id, - ) - ) - return all_connection_objects diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/connector_runner.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/connector_runner.py deleted file mode 100644 index a2bd441efab3..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/connector_runner.py +++ /dev/null @@ -1,220 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from __future__ import annotations - -import datetime -import json -import logging -import os -import subprocess -import uuid -from pathlib import Path - -import anyio -import asyncer -import dagger - -from live_tests.commons import errors -from live_tests.commons.models import Command, ExecutionInputs, ExecutionResult -from live_tests.commons.proxy import Proxy - - -class ConnectorRunner: - DATA_DIR = "/airbyte/data" - IN_CONTAINER_CONFIG_PATH = f"{DATA_DIR}/config.json" - IN_CONTAINER_CONFIGURED_CATALOG_PATH = f"{DATA_DIR}/catalog.json" - IN_CONTAINER_STATE_PATH = f"{DATA_DIR}/state.json" - IN_CONTAINER_OUTPUT_PATH = f"{DATA_DIR}/output.txt" - IN_CONTAINER_OBFUSCATOR_PATH = "/user/local/bin/record_obfuscator.py" - - def __init__( - self, - dagger_client: dagger.Client, - execution_inputs: ExecutionInputs, - is_airbyte_ci: bool, - http_proxy: Proxy | None = None, - ): - self.connector_under_test = execution_inputs.connector_under_test - self.command = execution_inputs.command - self.output_dir = execution_inputs.output_dir - self.config = execution_inputs.config - self.configured_catalog = execution_inputs.configured_catalog - self.state = execution_inputs.state - self.duckdb_path = execution_inputs.duckdb_path - self.actor_id = execution_inputs.actor_id - self.hashed_connection_id = execution_inputs.hashed_connection_id - self.environment_variables = execution_inputs.environment_variables if execution_inputs.environment_variables else {} - - self.full_command: list[str] = self._get_full_command(execution_inputs.command) - self.completion_event = anyio.Event() - self.http_proxy = http_proxy - self.logger = logging.getLogger(f"{self.connector_under_test.name}-{self.connector_under_test.version}") - self.dagger_client = dagger_client - if is_airbyte_ci: - self.host_obfuscator_path = "/tmp/record_obfuscator.py" - else: - repo_root = Path(subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).strip().decode()) - self.host_obfuscator_path = f"{repo_root}/tools/bin/record_obfuscator.py" - - @property - def _connector_under_test_container(self) -> dagger.Container: - return self.connector_under_test.container - - @property - def stdout_file_path(self) -> Path: - return (self.output_dir / "stdout.log").resolve() - - @property - def stderr_file_path(self) -> Path: - return (self.output_dir / "stderr.log").resolve() - - def _get_full_command(self, command: Command) -> list[str]: - """Returns a list with a full Airbyte command invocation and all it's arguments and options.""" - if command is Command.SPEC: - return ["spec"] - elif command is Command.CHECK: - return ["check", "--config", self.IN_CONTAINER_CONFIG_PATH] - elif command is Command.DISCOVER: - return ["discover", "--config", self.IN_CONTAINER_CONFIG_PATH] - elif command is Command.READ: - return [ - "read", - "--config", - self.IN_CONTAINER_CONFIG_PATH, - "--catalog", - self.IN_CONTAINER_CONFIGURED_CATALOG_PATH, - ] - elif command is Command.READ_WITH_STATE: - return [ - "read", - "--config", - self.IN_CONTAINER_CONFIG_PATH, - "--catalog", - self.IN_CONTAINER_CONFIGURED_CATALOG_PATH, - "--state", - self.IN_CONTAINER_STATE_PATH, - ] - else: - raise NotImplementedError(f"The connector runner does not support the {command} command") - - async def get_container_env_variable_value(self, name: str) -> str | None: - return await self._connector_under_test_container.env_variable(name) - - async def get_container_label(self, label: str) -> str | None: - return await self._connector_under_test_container.label(label) - - async def get_container_entrypoint(self) -> str: - entrypoint = await self._connector_under_test_container.entrypoint() - assert entrypoint, "The connector container has no entrypoint" - return " ".join(entrypoint) - - async def run(self) -> ExecutionResult: - async with asyncer.create_task_group() as task_group: - soon_result = task_group.soonify(self._run)() - task_group.soonify(self._log_progress)() - return soon_result.value - - async def _run( - self, - ) -> ExecutionResult: - container = self._connector_under_test_container - current_user = (await container.with_exec(["whoami"]).stdout()).strip() - container = container.with_user(current_user) - container = container.with_exec(["mkdir", "-p", self.DATA_DIR]) - # Do not cache downstream dagger layers - container = container.with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - - # When running locally, it's likely that record_obfuscator is within the user's home directory, so we expand it. - expanded_host_executable_path = os.path.expanduser(self.host_obfuscator_path) - container = container.with_file( - self.IN_CONTAINER_OBFUSCATOR_PATH, - self.dagger_client.host().file(expanded_host_executable_path), - ) - - for env_var_name, env_var_value in self.environment_variables.items(): - container = container.with_env_variable(env_var_name, env_var_value) - if self.config: - container = container.with_new_file(self.IN_CONTAINER_CONFIG_PATH, contents=json.dumps(dict(self.config)), owner=current_user) - if self.state: - container = container.with_new_file(self.IN_CONTAINER_STATE_PATH, contents=json.dumps(self.state), owner=current_user) - if self.configured_catalog: - container = container.with_new_file( - self.IN_CONTAINER_CONFIGURED_CATALOG_PATH, - contents=self.configured_catalog.json(), - owner=current_user, - ) - if self.http_proxy: - container = await self.http_proxy.bind_container(container) - - self.logger.info(f"⏳ Start running {self.command.value} command") - - try: - entrypoint = await container.entrypoint() - assert entrypoint, "The connector container has no entrypoint" - airbyte_command = entrypoint + self.full_command - - container = container.with_exec( - [ - "sh", - "-c", - " ".join(airbyte_command) - + f"| {self.IN_CONTAINER_OBFUSCATOR_PATH} > {self.IN_CONTAINER_OUTPUT_PATH} 2>&1 | tee -a {self.IN_CONTAINER_OUTPUT_PATH}", - ] - ) - executed_container = await container.sync() - # We exporting to disk as we can't read .stdout() or await file.contents() as it might blow up the memory - stdout_exported = await executed_container.file(self.IN_CONTAINER_OUTPUT_PATH).export(str(self.stdout_file_path)) - if not stdout_exported: - raise errors.ExportError(f"Failed to export {self.IN_CONTAINER_OUTPUT_PATH}") - - stderr = await executed_container.stderr() - self.stderr_file_path.write_text(stderr) - success = True - except dagger.ExecError as e: - self.stderr_file_path.write_text(e.stderr) - self.stdout_file_path.write_text(e.stdout) - executed_container = None - success = False - - self.completion_event.set() - if not success: - self.logger.error(f"❌ Failed to run {self.command.value} command") - else: - self.logger.info(f"⌛ Finished running {self.command.value} command") - - execution_result = await ExecutionResult.load( - command=self.command, - connector_under_test=self.connector_under_test, - actor_id=self.actor_id, - hashed_connection_id=self.hashed_connection_id, - configured_catalog=self.configured_catalog, - stdout_file_path=self.stdout_file_path, - stderr_file_path=self.stderr_file_path, - success=success, - http_dump=await self.http_proxy.retrieve_http_dump() if self.http_proxy else None, - executed_container=executed_container, - config=self.config, - ) - await execution_result.save_artifacts(self.output_dir, self.duckdb_path) - return execution_result - - async def _log_progress(self) -> None: - start_time = datetime.datetime.utcnow() - message = f"⏳ Still running {self.command.value} command" - while not self.completion_event.is_set(): - duration = datetime.datetime.utcnow() - start_time - elapsed_seconds = duration.total_seconds() - if elapsed_seconds > 10 and round(elapsed_seconds) % 10 == 0: - self.logger.info(f"{message} (duration: {self.format_duration(duration)})") - await anyio.sleep(1) - - @staticmethod - def format_duration(time_delta: datetime.timedelta) -> str: - total_seconds = time_delta.total_seconds() - if total_seconds < 60: - return f"{total_seconds:.2f}s" - minutes = int(total_seconds // 60) - seconds = int(total_seconds % 60) - return f"{minutes:02d}mn{seconds:02d}s" diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/errors.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/errors.py deleted file mode 100644 index cb13b4ab629e..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/errors.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - - -class ExportError(Exception): - def __init__(self, message: str): - super().__init__(message) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/evaluation_modes.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/evaluation_modes.py deleted file mode 100644 index 2edf391d1568..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/evaluation_modes.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from enum import Enum - - -class TestEvaluationMode(Enum): - """ - Tests may be run in "diagnostic" mode or "strict" mode. - - When run in "diagnostic" mode, `AssertionError`s won't fail the test, but we will continue to surface - any errors to the test report. - - In "strict" mode, tests pass/fail as usual. - - In live tests, diagnostic mode is used for tests that don't affect the overall functionality of the - connector but that test an ideal state of the connector. Currently this is applicable to validation - tests only. - - The diagnostic mode can be made available to a test using the @pytest.mark.allow_diagnostic_mode decorator, - and passing in the --test-evaluation-mode=diagnostic flag. - """ - - DIAGNOSTIC = "diagnostic" - STRICT = "strict" diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/hacks.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/hacks.py deleted file mode 100644 index 77c2adbd8d7a..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/hacks.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import copy - -import rich - -console = rich.get_console() - - -def patch_configured_catalog(configured_catalog: dict) -> dict: - """ - The configured catalog extracted from the platform can be incompatible with the airbyte-protocol. - This leads to validation error when we serialize the configured catalog into a ConfiguredAirbyteCatalog object. - This functions is a best effort to patch the configured catalog to make it compatible with the airbyte-protocol. - """ - patched_catalog = copy.deepcopy(configured_catalog) - for stream in patched_catalog["streams"]: - if stream.get("destination_sync_mode") == "overwrite_dedup": - stream["destination_sync_mode"] = "overwrite" - console.log( - f"Stream {stream['stream']['name']} destination_sync_mode has been patched from 'overwrite_dedup' to 'overwrite' to guarantee compatibility with the airbyte-protocol." - ) - return patched_catalog diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/json_schema_helper.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/json_schema_helper.py deleted file mode 100644 index 5ad60c2a14f9..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/json_schema_helper.py +++ /dev/null @@ -1,367 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from enum import Enum -from functools import reduce, total_ordering -from typing import Any, Dict, List, Mapping, Optional, Set, Text, Union - -import dpath.util -import pendulum -from jsonref import JsonRef - - -class CatalogField: - """Field class to represent cursor/pk fields. - It eases the read of values from records according to schema definition. - """ - - def __init__(self, schema: Mapping[str, Any], path: List[str]): - self.schema = schema - self.path = path - self.formats = self._detect_formats() - - def _detect_formats(self) -> Set[str]: - """Extract set of formats/types for this field""" - format_ = [] - try: - format_ = self.schema.get("format", self.schema["type"]) - if not isinstance(format_, List): - format_ = [format_] - except KeyError: - pass - return set(format_) - - def _parse_value(self, value: Any) -> Any: - """Do actual parsing of the serialized value""" - if self.formats.intersection({"datetime", "date-time", "date"}): - if value is None and "null" not in self.formats: - raise ValueError(f"Invalid field format. Value: {value}. Format: {self.formats}") - # handle beautiful MySQL datetime, i.e. NULL datetime - if value.startswith("0000-00-00"): - value = value.replace("0000-00-00", "0001-01-01") - return pendulum.parse(value) - return value - - def parse(self, record: Mapping[str, Any], path: Optional[List[Union[int, str]]] = None) -> Any: - """Extract field value from the record and cast it to native type""" - path = path or self.path - value = reduce(lambda data, key: data[key], path, record) - return self._parse_value(value) - - -@total_ordering -class ComparableType(Enum): - NULL = 0 - BOOLEAN = 1 - INTEGER = 2 - NUMBER = 3 - STRING = 4 - OBJECT = 5 - - def __lt__(self, other: Any) -> bool: - if self.__class__ is other.__class__: - return self.value < other.value # type: ignore - else: - return NotImplemented - - -class JsonSchemaHelper: - """Helper class to simplify schema validation and read of records according to their schema.""" - - def __init__(self, schema): - self._schema = schema - - def get_ref(self, path: str) -> Any: - """Resolve reference - - :param path: reference (#/definitions/SomeClass, etc) - :return: part of schema that is definition of the reference - :raises KeyError: in case path can't be followed - """ - node = self._schema - for segment in path.split("/")[1:]: - node = node[segment] - return node - - def get_property(self, path: List[str]) -> Mapping[str, Any]: - """Get any part of schema according to provided path, resolves $refs if necessary - - schema = { - "properties": { - "field1": { - "properties": { - "nested_field": { - - } - } - }, - "field2": ... - } - } - - helper = JsonSchemaHelper(schema) - helper.get_property(["field1", "nested_field"]) == - - :param path: list of fields in the order of navigation - :return: discovered part of schema - :raises KeyError: in case path can't be followed - """ - node = self._schema - for segment in path: - if "$ref" in node: - node = self.get_ref(node["$ref"]) - node = node["properties"][segment] - return node - - def field(self, path: List[str]) -> CatalogField: - """Get schema property and wrap it into CatalogField. - - CatalogField is a helper to ease the read of values from records according to schema definition. - - :param path: list of fields in the order of navigation - :return: discovered part of schema wrapped in CatalogField - :raises KeyError: in case path can't be followed - """ - return CatalogField(schema=self.get_property(path), path=path) - - def get_node(self, path: List[Union[str, int]]) -> Any: - """Return part of schema by specified path - - :param path: list of fields in the order of navigation - """ - - node = self._schema - for segment in path: - if "$ref" in node: - node = self.get_ref(node["$ref"]) - node = node[segment] - return node - - def get_parent_path(self, path: str, separator="/") -> Any: - """ - Returns the parent path of the supplied path - """ - absolute_path = f"{separator}{path}" if not path.startswith(separator) else path - parent_path, _ = absolute_path.rsplit(sep=separator, maxsplit=1) - return parent_path - - def get_parent(self, path: str, separator="/") -> Any: - """ - Returns the parent dict of a given path within the `obj` dict - """ - parent_path = self.get_parent_path(path, separator=separator) - if parent_path == "": - return self._schema - return dpath.util.get(self._schema, parent_path, separator=separator) - - def find_nodes(self, keys: List[str]) -> List[List[Union[str, int]]]: - """Find all paths that lead to nodes with the specified keys. - - :param keys: list of keys - :return: list of json object paths - """ - variant_paths = [] - - def traverse_schema(_schema: Union[Dict[Text, Any], List], path=None): - path = path or [] - if path and path[-1] in keys: - variant_paths.append(path) - if isinstance(_schema, dict): - for item in _schema: - traverse_schema(_schema[item], [*path, item]) - elif isinstance(_schema, list): - for i, item in enumerate(_schema): - traverse_schema(_schema[i], [*path, i]) - - traverse_schema(self._schema) - return variant_paths - - -def get_object_structure(obj: dict) -> List[str]: - """ - Traverse through object structure and compose a list of property keys including nested one. - This list reflects object's structure with list of all obj property key - paths. In case if object is nested inside array we assume that it has same - structure as first element. - :param obj: data object to get its structure - :returns list of object property keys paths - """ - paths = [] - - def _traverse_obj_and_get_path(obj, path=""): - if path: - paths.append(path) - if isinstance(obj, dict): - return {k: _traverse_obj_and_get_path(v, path + "/" + k) for k, v in obj.items()} - elif isinstance(obj, list) and len(obj) > 0: - return [_traverse_obj_and_get_path(obj[0], path + "/[]")] - - _traverse_obj_and_get_path(obj) - - return paths - - -def get_expected_schema_structure(schema: dict, annotate_one_of: bool = False) -> List[str]: - """ - Traverse through json schema and compose list of property keys that object expected to have. - :param annotate_one_of: Generate one_of index in path - :param schema: jsonschema to get expected paths - :returns list of object property keys paths - """ - paths = [] - if "$ref" in schema: - """ - JsonRef doesnt work correctly with schemas that has refenreces in root e.g. - { - "$ref": "#/definitions/ref" - "definitions": { - "ref": ... - } - } - Considering this schema already processed by resolver so it should - contain only references to definitions section, replace root reference - manually before processing it with JsonRef library. - """ - ref = schema["$ref"].split("/")[-1] - schema.update(schema["definitions"][ref]) - schema.pop("$ref") - # Resolve all references to simplify schema processing. - schema = JsonRef.replace_refs(schema) - - def _scan_schema(subschema, path=""): - if "oneOf" in subschema or "anyOf" in subschema: - if annotate_one_of: - return [ - _scan_schema({"type": "object", **s}, path + f"({num})") - for num, s in enumerate(subschema.get("oneOf") or subschema.get("anyOf")) - ] - return [_scan_schema({"type": "object", **s}, path) for s in subschema.get("oneOf") or subschema.get("anyOf")] - schema_type = subschema.get("type", ["object", "null"]) - if not isinstance(schema_type, list): - schema_type = [schema_type] - if "object" in schema_type: - props = subschema.get("properties") - if not props: - # Handle objects with arbitrary properties: - # {"type": "object", "additionalProperties": {"type": "string"}} - if path: - paths.append(path) - return - return {k: _scan_schema(v, path + "/" + k) for k, v in props.items()} - elif "array" in schema_type: - items = subschema.get("items", {}) - return [_scan_schema(items, path + "/[]")] - paths.append(path) - - _scan_schema(schema) - return paths - - -def flatten_tuples(to_flatten): - """Flatten a tuple of tuples into a single tuple.""" - types = set() - - if not isinstance(to_flatten, tuple): - to_flatten = (to_flatten,) - for thing in to_flatten: - if isinstance(thing, tuple): - types.update(flatten_tuples(thing)) - else: - types.add(thing) - return tuple(types) - - -def get_paths_in_connector_config(schema: dict) -> List[str]: - """ - Traverse through the provided schema's values and extract the path_in_connector_config paths - :param properties: jsonschema containing values which may have path_in_connector_config attributes - :returns list of path_in_connector_config paths - """ - return ["/" + "/".join(value["path_in_connector_config"]) for value in schema.values()] - - -def conforms_to_schema(record: Mapping[str, Any], schema: Mapping[str, Any]) -> bool: - """ - Return true iff the record conforms to the supplied schema. - - The record conforms to the supplied schema iff: - - All columns in the record are in the schema. - - For every column in the record, that column's type is equal to or narrower than the same column's - type in the schema. - """ - schema_columns = set(schema.get("properties", {}).keys()) - record_columns = set(record.keys()) - - if not record_columns.issubset(schema_columns): - return False - - for column, definition in schema.get("properties", {}).items(): - expected_type = definition.get("type") - value = record.get(column) - - if value is not None: - if isinstance(expected_type, list): - return any(_is_equal_or_narrower_type(value, e) for e in expected_type) - elif expected_type == "object": - return isinstance(value, dict) - elif expected_type == "array": - if not isinstance(value, list): - return False - array_type = definition.get("items", {}).get("type") - if not all(_is_equal_or_narrower_type(v, array_type) for v in value): - return False - elif not _is_equal_or_narrower_type(value, expected_type): - return False - - return True - - -def _is_equal_or_narrower_type(value: Any, expected_type: str) -> bool: - if isinstance(value, list): - # We do not compare lists directly; the individual items are compared. - # If we hit this condition, it means that the expected type is not - # compatible with the inferred type. - return False - - inferred_type = ComparableType(_get_inferred_type(value)) - - if inferred_type is None: - return False - - return ComparableType(inferred_type) <= ComparableType(_get_comparable_type(expected_type)) - - -def _get_inferred_type(value: Any) -> Optional[ComparableType]: - if value is None: - return ComparableType.NULL - if isinstance(value, bool): - return ComparableType.BOOLEAN - if isinstance(value, int): - return ComparableType.INTEGER - if isinstance(value, float): - return ComparableType.NUMBER - if isinstance(value, str): - return ComparableType.STRING - if isinstance(value, dict): - return ComparableType.OBJECT - else: - return None - - -def _get_comparable_type(value: Any) -> Optional[ComparableType]: - if value == "null": - return ComparableType.NULL - if value == "boolean": - return ComparableType.BOOLEAN - if value == "integer": - return ComparableType.INTEGER - if value == "number": - return ComparableType.NUMBER - if value == "string": - return ComparableType.STRING - if value == "object": - return ComparableType.OBJECT - else: - return None diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/mitm_addons.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/mitm_addons.py deleted file mode 100644 index 032a80ebee23..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/mitm_addons.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from urllib.parse import parse_qs, urlencode, urlparse - -from mitmproxy import http - - -class SortQueryParams: - """This addon sorts query parameters in the request URL. - It is useful for testing purposes, as it makes it easier to compare requests and get cache hits. - """ - - def request(self, flow: http.HTTPFlow) -> None: - if url := flow.request.url: - parsed_url = urlparse(url) - # Get query parameters as dictionary - query_params = parse_qs(parsed_url.query) - # Sort query parameters alphabetically - sorted_params = {key: query_params[key] for key in sorted(query_params.keys())} - # Reconstruct the URL with sorted query parameters - sorted_url = parsed_url._replace(query=urlencode(sorted_params, doseq=True)).geturl() - - # Update the request URL - flow.request.url = sorted_url - - -# Disabling the addon. -# It can alter the request URL when some connector URL are already encoded. -# See discussion here https://github.com/airbytehq/airbyte-internal-issues/issues/9302#issuecomment-2311854334 - -# addons = [SortQueryParams()] -addons = [] diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/models.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/models.py deleted file mode 100644 index b4bdd87c2c21..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/models.py +++ /dev/null @@ -1,543 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import _collections_abc -import hashlib -import json -import logging -import tempfile -from collections import defaultdict -from collections.abc import Iterable, Iterator, MutableMapping -from dataclasses import dataclass, field -from enum import Enum -from functools import cache -from pathlib import Path -from typing import Any, Dict, List, Optional - -import dagger -import requests -from airbyte_protocol.models import ( - AirbyteCatalog, # type: ignore - AirbyteMessage, # type: ignore - AirbyteStateMessage, # type: ignore - AirbyteStreamStatusTraceMessage, # type: ignore - ConfiguredAirbyteCatalog, # type: ignore - TraceType, # type: ignore -) -from airbyte_protocol.models import Type as AirbyteMessageType -from genson import SchemaBuilder # type: ignore -from mitmproxy import http -from pydantic import ValidationError - -from live_tests.commons.backends import DuckDbBackend, FileBackend -from live_tests.commons.secret_access import get_airbyte_api_key -from live_tests.commons.utils import ( - get_connector_container, - get_http_flows_from_mitm_dump, - mitm_http_stream_to_har, - sanitize_stream_name, - sort_dict_keys, -) - - -class UserDict(_collections_abc.MutableMapping): # type: ignore - # Start by filling-out the abstract methods - def __init__(self, _dict: Optional[MutableMapping] = None, **kwargs: Any): - self.data: MutableMapping = {} - if _dict is not None: - self.update(_dict) - if kwargs: - self.update(kwargs) - - def __len__(self) -> int: - return len(self.data) - - def __getitem__(self, key: Any) -> Any: - if key in self.data: - return self.data[key] - if hasattr(self.__class__, "__missing__"): - return self.__class__.__missing__(self, key) - raise KeyError(key) - - def __setitem__(self, key: Any, item: Any) -> None: - self.data[key] = item - - def __delitem__(self, key: Any) -> None: - del self.data[key] - - def __iter__(self) -> Iterator: - return iter(self.data) - - # Modify __contains__ to work correctly when __missing__ is present - def __contains__(self, key: Any) -> bool: - return key in self.data - - # Now, add the methods in dicts but not in MutableMapping - def __repr__(self) -> str: - return repr(self.data) - - def __or__(self, other: UserDict | dict) -> UserDict: - if isinstance(other, UserDict): - return self.__class__(self.data | other.data) # type: ignore - if isinstance(other, dict): - return self.__class__(self.data | other) # type: ignore - return NotImplemented - - def __ror__(self, other: UserDict | dict) -> UserDict: - if isinstance(other, UserDict): - return self.__class__(other.data | self.data) # type: ignore - if isinstance(other, dict): - return self.__class__(other | self.data) # type: ignore - return NotImplemented - - def __ior__(self, other: UserDict | dict) -> UserDict: - if isinstance(other, UserDict): - self.data |= other.data # type: ignore - else: - self.data |= other # type: ignore - return self - - def __copy__(self) -> UserDict: - inst = self.__class__.__new__(self.__class__) - inst.__dict__.update(self.__dict__) - # Create a copy and avoid triggering descriptors - inst.__dict__["data"] = self.__dict__["data"].copy() - return inst - - def copy(self) -> UserDict: - if self.__class__ is UserDict: - return UserDict(self.data.copy()) # type: ignore - import copy - - data = self.data - try: - self.data = {} - c = copy.copy(self) - finally: - self.data = data - c.update(self) - return c - - @classmethod - def fromkeys(cls, iterable: Iterable, value: Optional[Any] = None) -> UserDict: - d = cls() - for key in iterable: - d[key] = value - return d - - -class SecretDict(UserDict): - def __str__(self) -> str: - return f"{self.__class__.__name__}(******)" - - def __repr__(self) -> str: - return str(self) - - -class Command(Enum): - CHECK = "check" - DISCOVER = "discover" - READ = "read" - READ_WITH_STATE = "read-with-state" - SPEC = "spec" - - def needs_config(self) -> bool: - return self in {Command.CHECK, Command.DISCOVER, Command.READ, Command.READ_WITH_STATE} - - def needs_catalog(self) -> bool: - return self in {Command.READ, Command.READ_WITH_STATE} - - def needs_state(self) -> bool: - return self in {Command.READ_WITH_STATE} - - -class TargetOrControl(Enum): - TARGET = "target" - CONTROL = "control" - - -class ActorType(Enum): - SOURCE = "source" - DESTINATION = "destination" - - -class ConnectionSubset(Enum): - """Signals which connection pool to consider for this live test — just the Airbyte sandboxes, or all possible connctions on Cloud.""" - - SANDBOXES = "sandboxes" - ALL = "all" - - -@dataclass -class ConnectorUnderTest: - """Represents a connector being tested. - In validation tests, there would be one connector under test. - When running regression tests, there would be two connectors under test: the target and the control versions of the same connector. - """ - - # connector image, assuming it's in the format "airbyte/{actor_type}-{connector_name}:{version}" - image_name: str - container: dagger.Container - target_or_control: TargetOrControl - - @property - def name(self) -> str: - return self.image_name.replace("airbyte/", "").split(":")[0] - - @property - def name_without_type_prefix(self) -> str: - return self.name.replace(f"{self.actor_type.value}-", "") - - @property - def version(self) -> str: - return self.image_name.replace("airbyte/", "").split(":")[1] - - @property - def actor_type(self) -> ActorType: - if "airbyte/destination-" in self.image_name: - return ActorType.DESTINATION - elif "airbyte/source-" in self.image_name: - return ActorType.SOURCE - else: - raise ValueError( - f"Can't infer the actor type. Connector image name {self.image_name} does not contain 'airbyte/source' or 'airbyte/destination'" - ) - - @classmethod - async def from_image_name( - cls: type[ConnectorUnderTest], - dagger_client: dagger.Client, - image_name: str, - target_or_control: TargetOrControl, - ) -> ConnectorUnderTest: - container = await get_connector_container(dagger_client, image_name) - return cls(image_name, container, target_or_control) - - -@dataclass -class ExecutionInputs: - hashed_connection_id: str - connector_under_test: ConnectorUnderTest - actor_id: str - global_output_dir: Path - command: Command - config: Optional[SecretDict] = None - configured_catalog: Optional[ConfiguredAirbyteCatalog] = None - state: Optional[dict] = None - environment_variables: Optional[dict] = None - duckdb_path: Optional[Path] = None - - def raise_if_missing_attr_for_command(self, attribute: str) -> None: - if getattr(self, attribute) is None: - raise ValueError(f"We need a {attribute} to run the {self.command.value} command") - - def __post_init__(self) -> None: - if self.command is Command.CHECK: - self.raise_if_missing_attr_for_command("config") - if self.command is Command.DISCOVER: - self.raise_if_missing_attr_for_command("config") - if self.command is Command.READ: - self.raise_if_missing_attr_for_command("config") - self.raise_if_missing_attr_for_command("configured_catalog") - if self.command is Command.READ_WITH_STATE: - self.raise_if_missing_attr_for_command("config") - self.raise_if_missing_attr_for_command("configured_catalog") - self.raise_if_missing_attr_for_command("state") - - @property - def output_dir(self) -> Path: - output_dir = ( - self.global_output_dir - / f"command_execution_artifacts/{self.connector_under_test.name}/{self.command.value}/{self.connector_under_test.version}/{self.hashed_connection_id}" - ) - output_dir.mkdir(parents=True, exist_ok=True) - return output_dir - - -@dataclass -class ExecutionResult: - hashed_connection_id: str - actor_id: str - configured_catalog: ConfiguredAirbyteCatalog - connector_under_test: ConnectorUnderTest - command: Command - stdout_file_path: Path - stderr_file_path: Path - success: bool - executed_container: Optional[dagger.Container] - config: Optional[SecretDict] - http_dump: Optional[dagger.File] = None - http_flows: list[http.HTTPFlow] = field(default_factory=list) - stream_schemas: Optional[dict[str, Any]] = None - backend: Optional[FileBackend] = None - - HTTP_DUMP_FILE_NAME = "http_dump.mitm" - HAR_FILE_NAME = "http_dump.har" - - @property - def logger(self) -> logging.Logger: - return logging.getLogger(f"{self.connector_under_test.target_or_control.value}-{self.command.value}") - - @property - def airbyte_messages(self) -> Iterable[AirbyteMessage]: - return self.parse_airbyte_messages_from_command_output(self.stdout_file_path) - - @property - def duckdb_schema(self) -> Iterable[str]: - return (self.connector_under_test.target_or_control.value, self.command.value, self.hashed_connection_id) - - @property - def configured_streams(self) -> List[str]: - return [stream.stream.name for stream in self.configured_catalog.streams] - - @property - def primary_keys_per_stream(self) -> Dict[str, List[str]]: - return {stream.stream.name: stream.primary_key[0] if stream.primary_key else None for stream in self.configured_catalog.streams} - - @classmethod - async def load( - cls: type[ExecutionResult], - connector_under_test: ConnectorUnderTest, - hashed_connection_id: str, - actor_id: str, - configured_catalog: ConfiguredAirbyteCatalog, - command: Command, - stdout_file_path: Path, - stderr_file_path: Path, - success: bool, - executed_container: Optional[dagger.Container], - config: Optional[SecretDict] = None, - http_dump: Optional[dagger.File] = None, - ) -> ExecutionResult: - execution_result = cls( - hashed_connection_id, - actor_id, - configured_catalog, - connector_under_test, - command, - stdout_file_path, - stderr_file_path, - success, - executed_container, - config, - http_dump, - ) - await execution_result.load_http_flows() - return execution_result - - async def load_http_flows(self) -> None: - if not self.http_dump: - return - with tempfile.NamedTemporaryFile() as temp_file: - await self.http_dump.export(temp_file.name) - self.http_flows = get_http_flows_from_mitm_dump(Path(temp_file.name)) - - def parse_airbyte_messages_from_command_output( - self, command_output_path: Path, log_validation_errors: bool = False - ) -> Iterable[AirbyteMessage]: - with open(command_output_path) as command_output: - for line in command_output: - try: - yield AirbyteMessage.parse_raw(line) - except ValidationError as e: - if log_validation_errors: - self.logger.warn(f"Error parsing AirbyteMessage: {e}") - - def get_records(self) -> Iterable[AirbyteMessage]: - self.logger.info( - f"Reading records all records for command {self.command.value} on {self.connector_under_test.target_or_control.value} version." - ) - for message in self.airbyte_messages: - if message.type is AirbyteMessageType.RECORD: - yield message - - def generate_stream_schemas(self) -> dict[str, Any]: - self.logger.info("Generating stream schemas") - stream_builders: dict[str, SchemaBuilder] = {} - for record in self.get_records(): - stream = record.record.stream - if stream not in stream_builders: - stream_schema_builder = SchemaBuilder() - stream_schema_builder.add_schema({"type": "object", "properties": {}}) - stream_builders[stream] = stream_schema_builder - stream_builders[stream].add_object(self.get_obfuscated_types(record.record.data)) - self.logger.info("Stream schemas generated") - return {stream: sort_dict_keys(stream_builders[stream].to_schema()) for stream in stream_builders} - - @staticmethod - def get_obfuscated_types(data: dict[str, Any]) -> dict[str, Any]: - """ - Convert obfuscated records into a record whose values have the same type as the original values. - """ - types = {} - for k, v in data.items(): - if v.startswith("string_"): - types[k] = "a" - elif v.startswith("integer_"): - types[k] = 0 - elif v.startswith("number_"): - types[k] = 0.1 - elif v.startswith("boolean_"): - types[k] = True - elif v.startswith("null_"): - types[k] = None - elif v.startswith("array_"): - types[k] = [] - elif v.startswith("object_"): - types[k] = {} - else: - types[k] = v - - return types - - def get_records_per_stream(self, stream: str) -> Iterator[AirbyteMessage]: - assert self.backend is not None, "Backend must be set to get records per stream" - self.logger.info(f"Reading records for stream {stream}") - if stream not in self.backend.record_per_stream_paths: - self.logger.warning(f"No records found for stream {stream}") - yield from [] - else: - for message in self.parse_airbyte_messages_from_command_output( - self.backend.record_per_stream_paths[stream], log_validation_errors=True - ): - if message.type is AirbyteMessageType.RECORD: - yield message - - def get_states_per_stream(self, stream: str) -> Dict[str, List[AirbyteStateMessage]]: - self.logger.info(f"Reading state messages for stream {stream}") - states = defaultdict(list) - for message in self.airbyte_messages: - if message.type is AirbyteMessageType.STATE: - states[message.state.stream.stream_descriptor.name].append(message.state) - return states - - def get_status_messages_per_stream(self, stream: str) -> Dict[str, List[AirbyteStreamStatusTraceMessage]]: - self.logger.info(f"Reading state messages for stream {stream}") - statuses = defaultdict(list) - for message in self.airbyte_messages: - if message.type is AirbyteMessageType.TRACE and message.trace.type == TraceType.STREAM_STATUS: - statuses[message.trace.stream_status.stream_descriptor.name].append(message.trace.stream_status) - return statuses - - @cache - def get_message_count_per_type(self) -> dict[AirbyteMessageType, int]: - message_count: dict[AirbyteMessageType, int] = defaultdict(int) - for message in self.airbyte_messages: - message_count[message.type] += 1 - return message_count - - async def save_http_dump(self, output_dir: Path) -> None: - if self.http_dump: - self.logger.info("An http dump was captured during the execution of the command, saving it.") - http_dump_file_path = (output_dir / self.HTTP_DUMP_FILE_NAME).resolve() - await self.http_dump.export(str(http_dump_file_path)) - self.logger.info(f"Http dump saved to {http_dump_file_path}") - - # Define where the har file will be saved - har_file_path = (output_dir / self.HAR_FILE_NAME).resolve() - # Convert the mitmproxy dump file to a har file - mitm_http_stream_to_har(http_dump_file_path, har_file_path) - self.logger.info(f"Har file saved to {har_file_path}") - else: - self.logger.warning("No http dump to save") - - def save_airbyte_messages(self, output_dir: Path, duckdb_path: Optional[Path] = None) -> None: - self.logger.info("Saving Airbyte messages to disk") - airbyte_messages_dir = output_dir / "airbyte_messages" - airbyte_messages_dir.mkdir(parents=True, exist_ok=True) - if duckdb_path: - self.backend = DuckDbBackend(airbyte_messages_dir, duckdb_path, self.duckdb_schema) - else: - self.backend = FileBackend(airbyte_messages_dir) - self.backend.write(self.airbyte_messages) - self.logger.info("Airbyte messages saved") - - def save_stream_schemas(self, output_dir: Path) -> None: - self.stream_schemas = self.generate_stream_schemas() - stream_schemas_dir = output_dir / "stream_schemas" - stream_schemas_dir.mkdir(parents=True, exist_ok=True) - for stream_name, stream_schema in self.stream_schemas.items(): - (stream_schemas_dir / f"{sanitize_stream_name(stream_name)}.json").write_text(json.dumps(stream_schema, sort_keys=True)) - self.logger.info("Stream schemas saved to disk") - - async def save_artifacts(self, output_dir: Path, duckdb_path: Optional[Path] = None) -> None: - self.logger.info("Saving artifacts to disk") - self.save_airbyte_messages(output_dir, duckdb_path) - self.update_configuration() - await self.save_http_dump(output_dir) - self.save_stream_schemas(output_dir) - self.logger.info("All artifacts saved to disk") - - def get_updated_configuration(self, control_message_path: Path) -> Optional[dict[str, Any]]: - """Iterate through the control messages to find CONNECTOR_CONFIG message and return the last updated configuration.""" - if not control_message_path.exists(): - return None - updated_config = None - for line in control_message_path.read_text().splitlines(): - if line.strip(): - connector_config = json.loads(line.strip()).get("connectorConfig", {}) - if connector_config: - updated_config = connector_config - return updated_config - - def update_configuration(self) -> None: - """This function checks if a configuration has to be updated by reading the control messages file. - If a configuration has to be updated, it updates the configuration on the actor using the Airbyte API. - """ - assert self.backend is not None, "Backend must be set to update configuration in order to find the control messages path" - updated_configuration = self.get_updated_configuration(self.backend.jsonl_controls_path) - if updated_configuration is None: - return - - self.logger.warning(f"Updating configuration for {self.connector_under_test.name}, actor {self.actor_id}") - url = f"https://api.airbyte.com/v1/{self.connector_under_test.actor_type.value}s/{self.actor_id}" - - payload = { - "configuration": { - **updated_configuration, - f"{self.connector_under_test.actor_type.value}Type": self.connector_under_test.name_without_type_prefix, - } - } - headers = { - "accept": "application/json", - "content-type": "application/json", - "authorization": f"Bearer {get_airbyte_api_key()}", - } - - response = requests.patch(url, json=payload, headers=headers) - try: - response.raise_for_status() - except requests.HTTPError as e: - self.logger.error(f"Failed to update {self.connector_under_test.name} configuration on actor {self.actor_id}: {e}") - self.logger.error(f"Response: {response.text}") - self.logger.info(f"Updated configuration for {self.connector_under_test.name}, actor {self.actor_id}") - - def __hash__(self): - return hash(self.connector_under_test.version) - - -@dataclass(kw_only=True) -class ConnectionObjects: - source_config: Optional[SecretDict] - destination_config: Optional[SecretDict] - configured_catalog: Optional[ConfiguredAirbyteCatalog] - catalog: Optional[AirbyteCatalog] - state: Optional[dict] - workspace_id: Optional[str] - source_id: Optional[str] - destination_id: Optional[str] - source_docker_image: Optional[str] - connection_id: Optional[str] - - @property - def url(self) -> Optional[str]: - if not self.workspace_id or not self.connection_id: - return None - return f"https://cloud.airbyte.com/workspaces/{self.workspace_id}/connections/{self.connection_id}" - - @property - def hashed_connection_id(self) -> Optional[str]: - if not self.connection_id: - return None - return self.connection_id[:8] diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/proxy.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/proxy.py deleted file mode 100644 index a601ab734fdb..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/proxy.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging -import uuid - -import dagger - -from . import mitm_addons - - -class Proxy: - """ - This class is a wrapper around a mitmproxy container. It allows to declare a mitmproxy container, - bind it as a service to a different container and retrieve the mitmproxy stream file. - """ - - MITMPROXY_IMAGE = "mitmproxy/mitmproxy:10.2.4" - MITM_STREAM_FILE = "stream.mitm" - PROXY_PORT = 8080 - MITM_ADDONS_PATH = mitm_addons.__file__ - - def __init__( - self, - dagger_client: dagger.Client, - hostname: str, - session_id: str, - stream_for_server_replay: dagger.File | None = None, - ) -> None: - self.dagger_client = dagger_client - self.hostname = hostname - self.session_id = session_id - self.stream_for_server_replay = stream_for_server_replay - - @property - def dump_cache_volume(self) -> dagger.CacheVolume: - # We namespace the cache by: - # - Mitmproxy image name to make sure we're not re-using a cached artifact on a different and potentially incompatible mitmproxy version - # - Hostname to avoid sharing the same https dump between different tests - # - Session id to avoid sharing the same https dump between different runs of the same tests - # The session id is set to the Airbyte Connection ID to ensure that no cache is shared between connections - return self.dagger_client.cache_volume(f"{self.MITMPROXY_IMAGE}{self.hostname}{self.session_id}") - - @property - def mitmproxy_dir_cache(self) -> dagger.CacheVolume: - return self.dagger_client.cache_volume(self.MITMPROXY_IMAGE) - - def generate_mitmconfig(self): - return ( - self.dagger_client.container() - .from_(self.MITMPROXY_IMAGE) - # Mitmproxy generates its self signed certs at first run, we need to run it once to generate the certs - # They are stored in /root/.mitmproxy - .with_exec(["timeout", "--preserve-status", "1", "mitmdump"]) - .directory("/root/.mitmproxy") - ) - - async def get_container(self, mitm_config: dagger.Directory) -> dagger.Container: - """Get a container for the mitmproxy service. - If a stream for server replay is provided, it will be used to replay requests to the same URL. - - Returns: - dagger.Container: The container for the mitmproxy service. - mitm_config (dagger.Directory): The directory containing the mitmproxy configuration. - """ - container_addons_path = "/addons.py" - proxy_container = ( - self.dagger_client.container() - .from_(self.MITMPROXY_IMAGE) - .with_exec(["mkdir", "/dumps"]) - # This is caching the mitmproxy stream files, which can contain sensitive information - # We want to nuke this cache after test suite execution. - .with_mounted_cache("/dumps", self.dump_cache_volume) - # This is caching the mitmproxy self-signed certificate, no sensitive information is stored in it - .with_file( - container_addons_path, - self.dagger_client.host().file(self.MITM_ADDONS_PATH), - ) - .with_directory("/root/.mitmproxy", mitm_config) - ) - - # If the proxy was instantiated with a stream for server replay from a previous run, we want to use it. - # Requests to the same URL will be replayed from the stream instead of being sent to the server. - # This is useful to avoid rate limiting issues and limits responses drifts due to time based logics. - if self.stream_for_server_replay is not None and await self.stream_for_server_replay.size() > 0: - proxy_container = proxy_container.with_file("/cache.mitm", self.stream_for_server_replay) - command = [ - "mitmdump", - "-s", - container_addons_path, - "--flow-detail", - "2", - "--server-replay", - "/cache.mitm", - "--save-stream-file", - f"/dumps/{self.MITM_STREAM_FILE}", - ] - else: - command = [ - "mitmdump", - "-s", - container_addons_path, - "--flow-detail", - "2", - "--save-stream-file", - f"/dumps/{self.MITM_STREAM_FILE}", - ] - - return proxy_container.with_exec(command) - - async def get_service(self, mitm_config: dagger.Directory) -> dagger.Service: - return (await self.get_container(mitm_config)).with_exposed_port(self.PROXY_PORT).as_service() - - async def bind_container(self, container: dagger.Container) -> dagger.Container: - """Bind a container to the proxy service and set environment variables to use the proxy for HTTP(S) traffic. - - Args: - container (dagger.Container): The container to bind to the proxy service. - - Returns: - dagger.Container: The container with the proxy service bound and environment variables set. - """ - mitmconfig_dir = self.generate_mitmconfig() - pem = mitmconfig_dir.file("mitmproxy-ca.pem") - - # Find the python version in the container to get the correct path for the requests cert file - # We will overwrite this file with the mitmproxy self-signed certificate - # I could not find a less brutal way to make Requests trust the mitmproxy self-signed certificate - # I tried running update-ca-certificates + setting REQUESTS_CA_BUNDLE in the container but it did not work - python_version_output = (await container.with_exec(["python", "--version"]).stdout()).strip() - python_version = python_version_output.split(" ")[-1] - python_version_minor_only = ".".join(python_version.split(".")[:-1]) - requests_cert_path = f"/usr/local/lib/python{python_version_minor_only}/site-packages/certifi/cacert.pem" - current_user = (await container.with_exec(["whoami"]).stdout()).strip() - try: - return await ( - container.with_user("root") - # Overwrite the requests cert file with the mitmproxy self-signed certificate - .with_file(requests_cert_path, pem, owner=current_user) - .with_env_variable("http_proxy", f"{self.hostname}:{self.PROXY_PORT}") - .with_env_variable("https_proxy", f"{self.hostname}:{self.PROXY_PORT}") - .with_user(current_user) - .with_service_binding(self.hostname, await self.get_service(mitmconfig_dir)) - ) - except dagger.DaggerError as e: - # This is likely hapenning on Java connector images whose certificates location is different - # TODO handle this case - logging.warn(f"Failed to bind container to proxy: {e}") - return container - - async def retrieve_http_dump(self) -> dagger.File | None: - """We mount the cache volume, where the mitmproxy container saves the stream file, to a fresh container. - We then copy the stream file to a new directory and return it as a dagger.File. - The copy operation to /to_export is required as Dagger does not support direct access to files in cache volumes. - - - Returns: - dagger.File | None: The mitmproxy stream file if it exists, None otherwise. - """ - container = ( - self.dagger_client.container() - .from_("alpine:latest") - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - .with_mounted_cache("/dumps", self.dump_cache_volume) - ) - dump_files = (await container.with_exec(["ls", "/dumps"]).stdout()).splitlines() - if self.MITM_STREAM_FILE not in dump_files: - return None - return await ( - container.with_exec(["mkdir", "/to_export"], use_entrypoint=True) - .with_exec(["cp", "-r", f"/dumps/{self.MITM_STREAM_FILE}", f"/to_export/{self.MITM_STREAM_FILE}"], use_entrypoint=True) - .file(f"/to_export/{self.MITM_STREAM_FILE}") - ) - - async def clear_cache_volume(self) -> None: - """Delete all files in the cache volume. This is useful to avoid caching sensitive information between tests.""" - await ( - self.dagger_client.container() - .from_("alpine:latest") - .with_mounted_cache("/to_clear", self.dump_cache_volume) - .with_exec(["rm", "-rf", "/to_clear/*"], use_entrypoint=True) - .sync() - ) - logging.info(f"Cache volume {self.dump_cache_volume} cleared") diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/secret_access.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/secret_access.py deleted file mode 100644 index 05e6591d4995..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/secret_access.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging - -from google.api_core.exceptions import PermissionDenied -from google.cloud import secretmanager - -LIVE_TESTS_AIRBYTE_API_KEY_SECRET_ID = "projects/587336813068/secrets/live_tests_airbyte_api_key" - - -def get_secret_value(secret_manager_client: secretmanager.SecretManagerServiceClient, secret_id: str) -> str: - """Get the value of the enabled version of a secret - - Args: - secret_manager_client (secretmanager.SecretManagerServiceClient): The secret manager client - secret_id (str): The id of the secret - - Returns: - str: The value of the enabled version of the secret - """ - try: - response = secret_manager_client.list_secret_versions(request={"parent": secret_id, "filter": "state:ENABLED"}) - if len(response.versions) == 0: - raise ValueError(f"No enabled version of secret {secret_id} found") - enabled_version = response.versions[0] - response = secret_manager_client.access_secret_version(name=enabled_version.name) - return response.payload.data.decode("UTF-8") - except PermissionDenied as e: - logging.exception( - f"Permission denied while trying to access secret {secret_id}. Please write to #dev-tooling in Airbyte Slack for help.", - exc_info=e, - ) - raise e - - -def get_airbyte_api_key() -> str: - secret_manager_client = secretmanager.SecretManagerServiceClient() - return get_secret_value(secret_manager_client, LIVE_TESTS_AIRBYTE_API_KEY_SECRET_ID) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/segment_tracking.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/segment_tracking.py deleted file mode 100644 index a57a2aa542bb..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/segment_tracking.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging -import os -from importlib.metadata import version -from typing import Any, Optional - -from segment import analytics # type: ignore - -ENABLE_TRACKING = os.getenv("REGRESSION_TEST_DISABLE_TRACKING") is None -DEBUG_SEGMENT = os.getenv("DEBUG_SEGMENT") is not None -EVENT_NAME = "regression_test_start" -CURRENT_VERSION = version(__name__.split(".")[0]) - - -def on_error(error: Exception, items: Any) -> None: - logging.warning("An error occurred in Segment Tracking", exc_info=error) - - -# This is not a secret key, it is a public key that is used to identify the Segment project -analytics.write_key = "hnWfMdEtXNKBjvmJ258F72wShsLmcsZ8" -analytics.send = ENABLE_TRACKING -analytics.debug = DEBUG_SEGMENT -analytics.on_error = on_error - - -def track_usage( - user_id: Optional[str], - pytest_options: dict[str, Any], -) -> None: - if user_id: - analytics.identify(user_id) - else: - user_id = "airbyte-ci" - - # It contains default pytest option and the custom one passed by the user - analytics.track( - user_id, - EVENT_NAME, - { - "pytest_options": pytest_options, - "package_version": CURRENT_VERSION, - }, - ) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/commons/utils.py b/airbyte-ci/connectors/live-tests/src/live_tests/commons/utils.py deleted file mode 100644 index 08aeb1b47de5..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/commons/utils.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import logging -import os -import re -import shutil -from pathlib import Path -from typing import Optional - -import dagger -import docker # type: ignore -import pytest -from mitmproxy import http, io # type: ignore -from mitmproxy.addons.savehar import SaveHar # type: ignore -from slugify import slugify - - -async def get_container_from_id(dagger_client: dagger.Client, container_id: str) -> dagger.Container: - """Get a dagger container from its id. - Please remind that container id are not persistent and can change between Dagger sessions. - - Args: - dagger_client (dagger.Client): The dagger client to use to import the connector image - """ - try: - return await dagger_client.load_container_from_id(dagger.ContainerID(container_id)) - except dagger.DaggerError as e: - pytest.exit(f"Failed to load connector container: {e}") - - -async def get_container_from_tarball_path(dagger_client: dagger.Client, tarball_path: Path) -> dagger.Container: - if not tarball_path.exists(): - pytest.exit(f"Connector image tarball {tarball_path} does not exist") - container_under_test_tar_file = ( - dagger_client.host().directory(str(tarball_path.parent), include=[tarball_path.name]).file(tarball_path.name) - ) - try: - return await dagger_client.container().import_(container_under_test_tar_file) - except dagger.DaggerError as e: - pytest.exit(f"Failed to import connector image from tarball: {e}") - - -async def get_container_from_local_image(dagger_client: dagger.Client, local_image_name: str) -> Optional[dagger.Container]: - """Get a dagger container from a local image. - It will use Docker python client to export the image to a tarball and then import it into dagger. - - Args: - dagger_client (dagger.Client): The dagger client to use to import the connector image - local_image_name (str): The name of the local image to import - - Returns: - Optional[dagger.Container]: The dagger container for the local image or None if the image does not exist - """ - docker_client = docker.from_env() - - try: - image = docker_client.images.get(local_image_name) - except docker.errors.ImageNotFound: - return None - - image_digest = image.id.replace("sha256:", "") - tarball_path = Path(f"/tmp/{image_digest}.tar") - if not tarball_path.exists(): - logging.info(f"Exporting local connector image {local_image_name} to tarball {tarball_path}") - with open(tarball_path, "wb") as f: - for chunk in image.save(named=True): - f.write(chunk) - return await get_container_from_tarball_path(dagger_client, tarball_path) - - -async def get_container_from_dockerhub_image(dagger_client: dagger.Client, dockerhub_image_name: str) -> dagger.Container: - """Get a dagger container from a dockerhub image. - - Args: - dagger_client (dagger.Client): The dagger client to use to import the connector image - dockerhub_image_name (str): The name of the dockerhub image to import - - Returns: - dagger.Container: The dagger container for the dockerhub image - """ - try: - return await dagger_client.container().from_(dockerhub_image_name) - except dagger.DaggerError as e: - pytest.exit(f"Failed to import connector image from DockerHub: {e}") - - -async def get_connector_container(dagger_client: dagger.Client, image_name_with_tag: str) -> dagger.Container: - """Get a dagger container for the connector image to test. - - Args: - dagger_client (dagger.Client): The dagger client to use to import the connector image - image_name_with_tag (str): The docker image name and tag of the connector image to test - - Returns: - dagger.Container: The dagger container for the connector image to test - """ - # If a container_id.txt file is available, we'll use it to load the connector container - # We use a txt file as container ids can be too long to be passed as env vars - # It's used for dagger-in-dagger use case with airbyte-ci, when the connector container is built via an upstream dagger operation - container_id_path = Path(f"/tmp/{slugify(image_name_with_tag)}_container_id.txt") - if container_id_path.exists(): - return await get_container_from_id(dagger_client, container_id_path.read_text()) - - # If the CONNECTOR_UNDER_TEST_IMAGE_TAR_PATH env var is set, we'll use it to import the connector image from the tarball - if connector_image_tarball_path := os.environ.get("CONNECTOR_UNDER_TEST_IMAGE_TAR_PATH"): - tarball_path = Path(connector_image_tarball_path) - return await get_container_from_tarball_path(dagger_client, tarball_path) - - # Let's try to load the connector container from a local image - if connector_container := await get_container_from_local_image(dagger_client, image_name_with_tag): - return connector_container - - # If we get here, we'll try to pull the connector image from DockerHub - return await get_container_from_dockerhub_image(dagger_client, image_name_with_tag) - - -def sh_dash_c(lines: list[str]) -> list[str]: - """Wrap sequence of commands in shell for safe usage of dagger Container's with_exec method.""" - return ["sh", "-c", " && ".join(["set -o xtrace"] + lines)] - - -def clean_up_artifacts(directory: Path, logger: logging.Logger) -> None: - if directory.exists(): - shutil.rmtree(directory) - logger.info(f"🧹 Test artifacts cleaned up from {directory}") - - -def get_http_flows_from_mitm_dump(mitm_dump_path: Path) -> list[http.HTTPFlow]: - """Get http flows from a mitmproxy dump file. - - Args: - mitm_dump_path (Path): Path to the mitmproxy dump file. - - Returns: - List[http.HTTPFlow]: List of http flows. - """ - with open(mitm_dump_path, "rb") as dump_file: - return [f for f in io.FlowReader(dump_file).stream() if isinstance(f, http.HTTPFlow)] - - -def mitm_http_stream_to_har(mitm_http_stream_path: Path, har_file_path: Path) -> Path: - """Converts a mitmproxy http stream file to a har file. - - Args: - mitm_http_stream_path (Path): Path to the mitmproxy http stream file. - har_file_path (Path): Path where the har file will be saved. - - Returns: - Path: Path to the har file. - """ - flows = get_http_flows_from_mitm_dump(mitm_http_stream_path) - SaveHar().export_har(flows, str(har_file_path)) - return har_file_path - - -def extract_connection_id_from_url(url: str) -> str: - pattern = r"/connections/([a-f0-9\-]+)" - match = re.search(pattern, url) - if match: - return match.group(1) - else: - raise ValueError(f"Could not extract connection id from url {url}") - - -def extract_workspace_id_from_url(url: str) -> str: - pattern = r"/workspaces/([a-f0-9\-]+)" - match = re.search(pattern, url) - if match: - return match.group(1) - else: - raise ValueError(f"Could not extract workspace id from url {url}") - - -def build_connection_url(workspace_id: str | None, connection_id: str | None) -> str: - if not workspace_id or not connection_id: - raise ValueError("Both workspace_id and connection_id must be provided") - return f"https://cloud.airbyte.com/workspaces/{workspace_id}/connections/{connection_id}" - - -def sort_dict_keys(d: dict) -> dict: - if isinstance(d, dict): - sorted_dict = {} - for key in sorted(d.keys()): - sorted_dict[key] = sort_dict_keys(d[key]) - return sorted_dict - else: - return d - - -def sanitize_stream_name(stream_name: str) -> str: - return stream_name.replace("/", "_").replace(" ", "_").lower() diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/conftest.py b/airbyte-ci/connectors/live-tests/src/live_tests/conftest.py deleted file mode 100644 index 180fa53a52ae..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/conftest.py +++ /dev/null @@ -1,646 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -from __future__ import annotations - -import hashlib -import logging -import os -import textwrap -import time -import webbrowser -from collections.abc import AsyncIterable, Callable, Generator, Iterable -from itertools import product -from pathlib import Path -from typing import TYPE_CHECKING, List, Optional - -import dagger -import pytest -from airbyte_protocol.models import ConfiguredAirbyteCatalog # type: ignore -from connection_retriever.audit_logging import get_user_email # type: ignore -from connection_retriever.retrieval import ConnectionNotFoundError, get_current_docker_image_tag # type: ignore -from rich.prompt import Confirm, Prompt - -from live_tests import stash_keys -from live_tests.commons.connection_objects_retrieval import ConnectionObject, InvalidConnectionError, get_connection_objects -from live_tests.commons.connector_runner import ConnectorRunner, Proxy -from live_tests.commons.evaluation_modes import TestEvaluationMode -from live_tests.commons.models import ( - ActorType, - Command, - ConnectionObjects, - ConnectionSubset, - ConnectorUnderTest, - ExecutionInputs, - ExecutionResult, - SecretDict, - TargetOrControl, -) -from live_tests.commons.secret_access import get_airbyte_api_key -from live_tests.commons.segment_tracking import track_usage -from live_tests.commons.utils import clean_up_artifacts -from live_tests.report import PrivateDetailsReport, ReportState, TestReport - -if TYPE_CHECKING: - from _pytest.config import Config - from _pytest.config.argparsing import Parser - from _pytest.fixtures import SubRequest - from pytest_sugar import SugarTerminalReporter # type: ignore - -# CONSTS -LOGGER = logging.getLogger("live-tests") -MAIN_OUTPUT_DIRECTORY = Path("/tmp/live_tests_artifacts") - -# It's used by Dagger and its very verbose -logging.getLogger("httpx").setLevel(logging.ERROR) - - -# PYTEST HOOKS -def pytest_addoption(parser: Parser) -> None: - parser.addoption( - "--connector-image", - help="The connector image name on which the tests will run: e.g. airbyte/source-faker", - ) - parser.addoption( - "--control-version", - help="The control version used for regression testing.", - ) - parser.addoption( - "--target-version", - default="dev", - help="The target version used for regression and validation testing. Defaults to dev.", - ) - parser.addoption("--config-path") - parser.addoption("--catalog-path") - parser.addoption("--state-path") - parser.addoption("--connection-id") - parser.addoption("--pr-url", help="The URL of the PR you are testing") - parser.addoption( - "--stream", - help="The stream to run the tests on. (Can be used multiple times)", - action="append", - ) - # Required when running in CI - parser.addoption("--run-id", type=str) - parser.addoption( - "--should-read-with-state", - type=bool, - help="Whether to run the `read` command with state. \n" - "We recommend reading with state to properly test incremental sync. \n" - "But if the target version introduces a breaking change in the state, you might want to run without state. \n", - ) - parser.addoption( - "--test-evaluation-mode", - choices=[e.value for e in TestEvaluationMode], - default=TestEvaluationMode.STRICT.value, - help='If "diagnostic" mode is selected, all tests will pass as long as there is no exception; warnings will be logged. In "strict" mode, tests may fail.', - ) - parser.addoption( - "--connection-subset", - choices=[c.value for c in ConnectionSubset], - default=ConnectionSubset.SANDBOXES.value, - help="Whether to select from sandbox accounts only.", - ) - parser.addoption( - "--max-connections", - default=None, - help="The maximum number of connections to retrieve and use for testing.", - ) - parser.addoption( - "--disable-proxy", - type=bool, - default=False, - help="If a connector uses provider-specific libraries (e.g., facebook-business), it is better to disable the proxy.", - ) - - -def pytest_configure(config: Config) -> None: - user_email = get_user_email() - config.stash[stash_keys.RUN_IN_AIRBYTE_CI] = bool(os.getenv("RUN_IN_AIRBYTE_CI", False)) - config.stash[stash_keys.IS_PRODUCTION_CI] = bool(os.getenv("CI", False)) - - if not config.stash[stash_keys.RUN_IN_AIRBYTE_CI]: - prompt_for_confirmation(user_email) - - track_usage( - "production-ci" - if config.stash[stash_keys.IS_PRODUCTION_CI] - else "local-ci" - if config.stash[stash_keys.RUN_IN_AIRBYTE_CI] - else user_email, - vars(config.option), - ) - config.stash[stash_keys.AIRBYTE_API_KEY] = get_airbyte_api_key() - config.stash[stash_keys.USER] = user_email - config.stash[stash_keys.SESSION_RUN_ID] = config.getoption("--run-id") or str(int(time.time())) - test_artifacts_directory = get_artifacts_directory(config) - duckdb_path = test_artifacts_directory / "duckdb.db" - config.stash[stash_keys.DUCKDB_PATH] = duckdb_path - test_artifacts_directory.mkdir(parents=True, exist_ok=True) - dagger_log_path = test_artifacts_directory / "dagger.log" - config.stash[stash_keys.IS_PERMITTED_BOOL] = False - report_path = test_artifacts_directory / "report.html" - private_details_path = test_artifacts_directory / "private_details.html" - config.stash[stash_keys.TEST_ARTIFACT_DIRECTORY] = test_artifacts_directory - dagger_log_path.touch() - LOGGER.info("Dagger log path: %s", dagger_log_path) - config.stash[stash_keys.DAGGER_LOG_PATH] = dagger_log_path - config.stash[stash_keys.PR_URL] = get_option_or_fail(config, "--pr-url") - _connection_id = config.getoption("--connection-id") - config.stash[stash_keys.AUTO_SELECT_CONNECTION] = _connection_id == "auto" - config.stash[stash_keys.CONNECTOR_IMAGE] = get_option_or_fail(config, "--connector-image") - config.stash[stash_keys.TARGET_VERSION] = get_option_or_fail(config, "--target-version") - config.stash[stash_keys.CONTROL_VERSION] = get_control_version(config) - config.stash[stash_keys.CONNECTION_SUBSET] = ConnectionSubset(get_option_or_fail(config, "--connection-subset")) - custom_source_config_path = config.getoption("--config-path") - custom_configured_catalog_path = config.getoption("--catalog-path") - custom_state_path = config.getoption("--state-path") - config.stash[stash_keys.SELECTED_STREAMS] = set(config.getoption("--stream") or []) - config.stash[stash_keys.TEST_EVALUATION_MODE] = TestEvaluationMode(config.getoption("--test-evaluation-mode", "strict")) - config.stash[stash_keys.MAX_CONNECTIONS] = config.getoption("--max-connections") - config.stash[stash_keys.MAX_CONNECTIONS] = ( - int(config.stash[stash_keys.MAX_CONNECTIONS]) if config.stash[stash_keys.MAX_CONNECTIONS] else None - ) - - config.stash[stash_keys.DISABLE_PROXY] = config.getoption("--disable-proxy") - - if config.stash[stash_keys.RUN_IN_AIRBYTE_CI]: - config.stash[stash_keys.SHOULD_READ_WITH_STATE] = bool(config.getoption("--should-read-with-state")) - elif _should_read_with_state := config.getoption("--should-read-with-state"): - config.stash[stash_keys.SHOULD_READ_WITH_STATE] = _should_read_with_state - else: - config.stash[stash_keys.SHOULD_READ_WITH_STATE] = prompt_for_read_with_or_without_state() - - retrieval_reason = f"Running live tests on connection for connector {config.stash[stash_keys.CONNECTOR_IMAGE]} on target versions ({config.stash[stash_keys.TARGET_VERSION]})." - - try: - config.stash[stash_keys.ALL_CONNECTION_OBJECTS] = get_connection_objects( - { - ConnectionObject.SOURCE_CONFIG, - ConnectionObject.CATALOG, - ConnectionObject.CONFIGURED_CATALOG, - ConnectionObject.STATE, - ConnectionObject.WORKSPACE_ID, - ConnectionObject.SOURCE_DOCKER_IMAGE, - ConnectionObject.SOURCE_ID, - ConnectionObject.DESTINATION_ID, - }, - None if _connection_id == "auto" else _connection_id, - Path(custom_source_config_path) if custom_source_config_path else None, - Path(custom_configured_catalog_path) if custom_configured_catalog_path else None, - Path(custom_state_path) if custom_state_path else None, - retrieval_reason, - connector_image=config.stash[stash_keys.CONNECTOR_IMAGE], - connector_version=config.stash[stash_keys.CONTROL_VERSION], - auto_select_connections=config.stash[stash_keys.AUTO_SELECT_CONNECTION], - selected_streams=config.stash[stash_keys.SELECTED_STREAMS], - connection_subset=config.stash[stash_keys.CONNECTION_SUBSET], - max_connections=config.stash[stash_keys.MAX_CONNECTIONS], - ) - config.stash[stash_keys.IS_PERMITTED_BOOL] = True - except (ConnectionNotFoundError, InvalidConnectionError) as exc: - clean_up_artifacts(MAIN_OUTPUT_DIRECTORY, LOGGER) - LOGGER.error( - f"Failed to retrieve a valid a connection which is using the control version {config.stash[stash_keys.CONTROL_VERSION]}." - ) - pytest.exit(str(exc)) - - if config.stash[stash_keys.CONTROL_VERSION] == config.stash[stash_keys.TARGET_VERSION]: - pytest.exit(f"Control and target versions are the same: {control_version}. Please provide different versions.") - - config.stash[stash_keys.PRIVATE_DETAILS_REPORT] = PrivateDetailsReport( - private_details_path, - config, - ) - - config.stash[stash_keys.TEST_REPORT] = TestReport( - report_path, - config, - private_details_url=config.stash[stash_keys.PRIVATE_DETAILS_REPORT].path.resolve().as_uri(), - ) - - webbrowser.open_new_tab(config.stash[stash_keys.TEST_REPORT].path.resolve().as_uri()) - - -def get_artifacts_directory(config: pytest.Config) -> Path: - run_id = config.stash[stash_keys.SESSION_RUN_ID] - return MAIN_OUTPUT_DIRECTORY / f"session_{run_id}" - - -def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: - for item in items: - if config.stash[stash_keys.SHOULD_READ_WITH_STATE] and "without_state" in item.keywords: - item.add_marker(pytest.mark.skip(reason="Test is marked with without_state marker")) - if not config.stash[stash_keys.SHOULD_READ_WITH_STATE] and "with_state" in item.keywords: - item.add_marker(pytest.mark.skip(reason="Test is marked with with_state marker")) - - -def pytest_terminal_summary(terminalreporter: SugarTerminalReporter, exitstatus: int, config: Config) -> None: - config.stash[stash_keys.TEST_REPORT].update(ReportState.FINISHED) - config.stash[stash_keys.PRIVATE_DETAILS_REPORT].update(ReportState.FINISHED) - if not config.stash.get(stash_keys.IS_PERMITTED_BOOL, False): - # Don't display the prompt if the tests were not run due to inability to fetch config - clean_up_artifacts(MAIN_OUTPUT_DIRECTORY, LOGGER) - pytest.exit(str(NotPermittedError)) - - terminalreporter.ensure_newline() - terminalreporter.section("Test artifacts", sep="=", bold=True, blue=True) - terminalreporter.line( - f"All tests artifacts for this sessions should be available in {config.stash[stash_keys.TEST_ARTIFACT_DIRECTORY].resolve()}" - ) - - if not config.stash[stash_keys.RUN_IN_AIRBYTE_CI]: - try: - Prompt.ask( - textwrap.dedent( - """ - Test artifacts will be destroyed after this prompt. - Press enter when you're done reading them. - 🚨 Do not copy them elsewhere on your disk!!! 🚨 - """ - ) - ) - finally: - clean_up_artifacts(MAIN_OUTPUT_DIRECTORY, LOGGER) - - -def pytest_keyboard_interrupt(excinfo: Exception) -> None: - LOGGER.error("Test execution was interrupted by the user. Cleaning up test artifacts.") - clean_up_artifacts(MAIN_OUTPUT_DIRECTORY, LOGGER) - - -@pytest.hookimpl(tryfirst=True, hookwrapper=True) -def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> Generator: - outcome = yield - report = outcome.get_result() - - # Overwrite test failures with passes for tests being run in diagnostic mode - if ( - item.config.stash.get(stash_keys.TEST_EVALUATION_MODE, TestEvaluationMode.STRICT) == TestEvaluationMode.DIAGNOSTIC - and "allow_diagnostic_mode" in item.keywords - ): - if call.when == "call": - if call.excinfo: - if report.outcome == "failed": - report.outcome = "passed" - - # This is to add skipped or failed tests due to upstream fixture failures on setup - if report.outcome in ["failed", "skipped"] or report.when == "call": - item.config.stash[stash_keys.TEST_REPORT].add_test_result( - report, - item.function.__doc__, # type: ignore - ) - - -# HELPERS - - -def get_option_or_fail(config: pytest.Config, option: str) -> str: - if option_value := config.getoption(option): - return option_value - pytest.fail(f"Missing required option: {option}") - - -def get_control_version(config: pytest.Config) -> str: - if control_version := config.getoption("--control-version"): - return control_version - if connector_docker_repository := config.getoption("--connector-image"): - return get_current_docker_image_tag(connector_docker_repository) - raise ValueError("The control version can't be determined, please pass a --control-version or a --connector-image") - - -def prompt_for_confirmation(user_email: str) -> None: - message = textwrap.dedent( - f""" - 👮 This program is running on live Airbyte Cloud connection. - It means that it might induce costs or rate limits on the source. - This program is storing tests artifacts in {MAIN_OUTPUT_DIRECTORY.resolve()} that you can use for debugging. They will get destroyed after the program execution. - - By approving this prompt, you ({user_email}) confirm that: - 1. You understand the implications of running this test suite. - 2. You have selected the correct target and control versions. - 3. You have selected the right tests according to your testing needs. - 4. You will not copy the test artifacts content. - 5. You want to run the program on the passed connection ID. - - Usage of this tool is tracked and logged. - - Do you want to continue? - """ - ) - if not os.environ.get("CI") and not Confirm.ask(message): - pytest.exit("Test execution was interrupted by the user.") - - -def prompt_for_read_with_or_without_state() -> bool: - message = textwrap.dedent( - """ - 📖 Do you want to run the read command with or without state? - 1. Run the read command with state - 2. Run the read command without state - - We recommend reading with state to properly test incremental sync. - But if the target version introduces a breaking change in the state, you might want to run without state. - """ - ) - return Prompt.ask(message) == "1" - - -# FIXTURES - - -@pytest.fixture(scope="session") -def anyio_backend() -> str: - return "asyncio" - - -@pytest.fixture(scope="session") -def test_artifacts_directory(request: SubRequest) -> Path: - return request.config.stash[stash_keys.TEST_ARTIFACT_DIRECTORY] - - -@pytest.fixture(scope="session") -def connector_image(request: SubRequest) -> str: - return request.config.stash[stash_keys.CONNECTOR_IMAGE] - - -@pytest.fixture(scope="session") -def control_version(request: SubRequest) -> str: - return request.config.stash[stash_keys.CONTROL_VERSION] - - -@pytest.fixture(scope="session") -def target_version(request: SubRequest) -> str: - return request.config.stash[stash_keys.TARGET_VERSION] - - -@pytest.fixture(scope="session") -def all_connection_objects(request: SubRequest) -> List[ConnectionObjects]: - return request.config.stash[stash_keys.ALL_CONNECTION_OBJECTS] - - -def get_connector_config(connection_objects: ConnectionObjects, control_connector: ConnectorUnderTest) -> Optional[SecretDict]: - if control_connector.actor_type is ActorType.SOURCE: - return connection_objects.source_config - elif control_connector.actor_type is ActorType.DESTINATION: - return connection_objects.destination_config - else: - raise ValueError(f"Actor type {control_connector.actor_type} is not supported") - - -@pytest.fixture(scope="session") -def actor_id(connection_objects: ConnectionObjects, control_connector: ConnectorUnderTest) -> str | None: - if control_connector.actor_type is ActorType.SOURCE: - return connection_objects.source_id - elif control_connector.actor_type is ActorType.DESTINATION: - return connection_objects.destination_id - else: - raise ValueError(f"Actor type {control_connector.actor_type} is not supported") - - -def get_actor_id(connection_objects: ConnectionObjects, control_connector: ConnectorUnderTest) -> str | None: - if control_connector.actor_type is ActorType.SOURCE: - return connection_objects.source_id - elif control_connector.actor_type is ActorType.DESTINATION: - return connection_objects.destination_id - else: - raise ValueError(f"Actor type {control_connector.actor_type} is not supported") - - -@pytest.fixture(scope="session") -def configured_streams( - configured_catalog: ConfiguredAirbyteCatalog, -) -> Iterable[str]: - return {stream.stream.name for stream in configured_catalog.streams} - - -@pytest.fixture(scope="session") -def state(connection_objects: ConnectionObjects) -> Optional[dict]: - return connection_objects.state - - -@pytest.fixture(scope="session") -def dagger_connection(request: SubRequest) -> dagger.Connection: - return dagger.Connection(dagger.Config(log_output=request.config.stash[stash_keys.DAGGER_LOG_PATH].open("w"))) - - -@pytest.fixture(scope="session", autouse=True) -async def dagger_client( - dagger_connection: dagger.Connection, -) -> AsyncIterable[dagger.Client]: - async with dagger_connection as client: - yield client - - -@pytest.fixture(scope="session") -async def control_connector(dagger_client: dagger.Client, connector_image: str, control_version: str) -> ConnectorUnderTest: - return await ConnectorUnderTest.from_image_name(dagger_client, f"{connector_image}:{control_version}", TargetOrControl.CONTROL) - - -@pytest.fixture(scope="session") -async def target_connector(dagger_client: dagger.Client, connector_image: str, target_version: str) -> ConnectorUnderTest: - return await ConnectorUnderTest.from_image_name(dagger_client, f"{connector_image}:{target_version}", TargetOrControl.TARGET) - - -@pytest.fixture(scope="session") -def duckdb_path(request: SubRequest) -> Path: - return request.config.stash[stash_keys.DUCKDB_PATH] - - -def get_execution_inputs_for_command( - command: Command, - connection_objects: ConnectionObjects, - control_connector: ConnectorUnderTest, - test_artifacts_directory: Path, - duckdb_path: Path, -) -> ExecutionInputs: - """Get the execution inputs for the given command and connection objects.""" - actor_id = get_actor_id(connection_objects, control_connector) - - inputs_arguments = { - "hashed_connection_id": connection_objects.hashed_connection_id, - "connector_under_test": control_connector, - "actor_id": actor_id, - "global_output_dir": test_artifacts_directory, - "command": command, - "duckdb_path": duckdb_path, - } - - if command.needs_config: - connector_config = get_connector_config(connection_objects, control_connector) - if not connector_config: - pytest.skip("Config is not provided. The config fixture can't be used.") - inputs_arguments["config"] = connector_config - if command.needs_catalog: - configured_catalog = connection_objects.configured_catalog - if not configured_catalog: - pytest.skip("Catalog is not provided. The catalog fixture can't be used.") - inputs_arguments["configured_catalog"] = connection_objects.configured_catalog - if command.needs_state: - state = connection_objects.state - if not state: - pytest.skip("State is not provided. The state fixture can't be used.") - inputs_arguments["state"] = state - - return ExecutionInputs(**inputs_arguments) - - -async def run_command( - dagger_client: dagger.Client, - command: Command, - connection_objects: ConnectionObjects, - connector: ConnectorUnderTest, - test_artifacts_directory: Path, - duckdb_path: Path, - runs_in_ci, - disable_proxy: bool = False, -) -> ExecutionResult: - """Run the given command for the given connector and connection objects.""" - execution_inputs = get_execution_inputs_for_command(command, connection_objects, connector, test_artifacts_directory, duckdb_path) - logging.info(f"Running {command} for {connector.target_or_control.value} connector {execution_inputs.connector_under_test.name}") - proxy = None - - if not disable_proxy: - proxy_hostname = f"proxy_server_{command.value}_{execution_inputs.connector_under_test.version.replace('.', '_')}" - proxy = Proxy(dagger_client, proxy_hostname, connection_objects.connection_id) - - runner = ConnectorRunner(dagger_client, execution_inputs, runs_in_ci, http_proxy=proxy) - execution_result = await runner.run() - return execution_result, proxy - - -async def run_command_and_add_to_report( - dagger_client: dagger.Client, - command: Command, - connection_objects: ConnectionObjects, - connector: ConnectorUnderTest, - test_artifacts_directory: Path, - duckdb_path: Path, - runs_in_ci, - test_report: TestReport, - private_details_report: PrivateDetailsReport, - disable_proxy: bool = False, -) -> ExecutionResult: - """Run the given command for the given connector and connection objects and add the results to the test report.""" - execution_result, proxy = await run_command( - dagger_client, - command, - connection_objects, - connector, - test_artifacts_directory, - duckdb_path, - runs_in_ci, - disable_proxy=disable_proxy, - ) - if connector.target_or_control is TargetOrControl.CONTROL: - test_report.add_control_execution_result(execution_result) - private_details_report.add_control_execution_result(execution_result) - if connector.target_or_control is TargetOrControl.TARGET: - test_report.add_target_execution_result(execution_result) - private_details_report.add_target_execution_result(execution_result) - return execution_result, proxy - - -def generate_execution_results_fixture(command: Command, control_or_target: str) -> Callable: - """Dynamically generate the fixture for the given command and control/target. - This is mainly to avoid code duplication and to make the code more maintainable. - Declaring this explicitly for each command and control/target combination would be cumbersome. - """ - - if control_or_target not in ["control", "target"]: - raise ValueError("control_or_target should be either 'control' or 'target'") - if command not in [Command.SPEC, Command.CHECK, Command.DISCOVER, Command.READ, Command.READ_WITH_STATE]: - raise ValueError("command should be either 'spec', 'check', 'discover', 'read' or 'read_with_state'") - - if control_or_target == "control": - - @pytest.fixture(scope="session") - async def generated_fixture( - request: SubRequest, dagger_client: dagger.Client, control_connector: ConnectorUnderTest, test_artifacts_directory: Path - ) -> ExecutionResult: - connection_objects = request.param - disable_proxy = request.config.stash[stash_keys.DISABLE_PROXY] - - execution_results, proxy = await run_command_and_add_to_report( - dagger_client, - command, - connection_objects, - control_connector, - test_artifacts_directory, - request.config.stash[stash_keys.DUCKDB_PATH], - request.config.stash[stash_keys.RUN_IN_AIRBYTE_CI], - request.config.stash[stash_keys.TEST_REPORT], - request.config.stash[stash_keys.PRIVATE_DETAILS_REPORT], - disable_proxy=disable_proxy, - ) - - yield execution_results - - if not disable_proxy: - await proxy.clear_cache_volume() - - else: - - @pytest.fixture(scope="session") - async def generated_fixture( - request: SubRequest, dagger_client: dagger.Client, target_connector: ConnectorUnderTest, test_artifacts_directory: Path - ) -> ExecutionResult: - connection_objects = request.param - disable_proxy = request.config.stash[stash_keys.DISABLE_PROXY] - - execution_results, proxy = await run_command_and_add_to_report( - dagger_client, - command, - connection_objects, - target_connector, - test_artifacts_directory, - request.config.stash[stash_keys.DUCKDB_PATH], - request.config.stash[stash_keys.RUN_IN_AIRBYTE_CI], - request.config.stash[stash_keys.TEST_REPORT], - request.config.stash[stash_keys.PRIVATE_DETAILS_REPORT], - disable_proxy=disable_proxy, - ) - - yield execution_results - - if not disable_proxy: - await proxy.clear_cache_volume() - - return generated_fixture - - -def inject_fixtures() -> set[str]: - """Dynamically generate th execution result fixtures for all the combinations of commands and control/target. - The fixtures will be named as __execution_result - Add the generated fixtures to the global namespace. - """ - execution_result_fixture_names = [] - for command, control_or_target in product([command for command in Command], ["control", "target"]): - fixture_name = f"{command.name.lower()}_{control_or_target}_execution_result" - globals()[fixture_name] = generate_execution_results_fixture(command, control_or_target) - execution_result_fixture_names.append(fixture_name) - return set(execution_result_fixture_names) - - -EXECUTION_RESULT_FIXTURES = inject_fixtures() - - -def pytest_generate_tests(metafunc): - """This function is called for each test function. - It helps in parameterizing the test functions with the connection objects. - It will provide the connection objects to the "*_execution_result" fixtures as parameters. - This will make sure that the tests are run for all the connection objects available in the configuration. - """ - all_connection_objects = metafunc.config.stash[stash_keys.ALL_CONNECTION_OBJECTS] - requested_fixtures = [fixture_name for fixture_name in metafunc.fixturenames if fixture_name in EXECUTION_RESULT_FIXTURES] - assert isinstance(all_connection_objects, list), "all_connection_objects should be a list" - - if not requested_fixtures: - return - metafunc.parametrize( - requested_fixtures, - [[c] * len(requested_fixtures) for c in all_connection_objects], - indirect=requested_fixtures, - ids=[f"CONNECTION {c.connection_id[:8]}" for c in all_connection_objects], - ) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/consts.py b/airbyte-ci/connectors/live-tests/src/live_tests/consts.py deleted file mode 100644 index 16bfc69e55bd..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/consts.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -MAX_LINES_IN_REPORT = 1000 diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/pytest.ini b/airbyte-ci/connectors/live-tests/src/live_tests/pytest.ini deleted file mode 100644 index bca444d47897..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/pytest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[pytest] -addopts = --capture=no -console_output_style = progress -log_cli = True -log_cli_level= INFO -markers = - allow_diagnostic_mode: mark a test as eligible for diagnostic mode. - with_state: mark test as running a read command with state. - without_state: mark test as running a read command without state. \ No newline at end of file diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/README.md b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/README.md deleted file mode 100644 index 93f84175f1ad..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# LLM-Based Regression Test Evaluation - -Automated evaluation of connector regression test reports using LLM models. - -## How It Works - -After regression tests complete, this evaluates the HTML report and writes a pass/fail judgment to `GITHUB_STEP_SUMMARY`. - -## Configuration - -**Environment Variables:** -- `OPENAI_API_KEY` - API key (use `ollama` for Ollama) -- `OPENAI_BASE_URL` - Base URL for OpenAI-compatible API (e.g., `http://127.0.0.1:11434/v1` for Ollama) -- `EVAL_MODEL` - Model name (defaults to `gpt-4o`) - -**Evaluation Prompt:** -Stored in `.github/prompts/regression-evaluation.prompt.yaml` following GitHub's prompt format. Uses `{{report_text}}` placeholder for dynamic content injection. - -## Local Testing - -```bash -# Install Ollama -curl -fsSL https://ollama.com/install.sh | sh -ollama serve & -ollama pull llama3.2:3b - -# Set environment -export OPENAI_API_KEY=ollama -export OPENAI_BASE_URL=http://127.0.0.1:11434/v1 -export EVAL_MODEL=llama3.2:3b - -# Run evaluation -cd airbyte-ci/connectors/live-tests -poetry install -poetry run python src/live_tests/regression_tests/llm_evaluation/evaluate_report.py \ - --report-path /path/to/report.html -``` diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/__init__.py deleted file mode 100644 index 7f66676b8716..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/evaluate_report.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/evaluate_report.py deleted file mode 100644 index ac9f33c8016e..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/llm_evaluation/evaluate_report.py +++ /dev/null @@ -1,299 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -""" -LLM-based evaluation of regression test reports. - -This script reads a regression test report (HTML format) and uses OpenAI's LLM -to evaluate the results, make a pass/fail judgment, and generate a summary. -The summary is written to GITHUB_STEP_SUMMARY for display in GitHub Actions. -""" - -import argparse -import json -import os -import sys -from pathlib import Path -from typing import Any - -import yaml -from bs4 import BeautifulSoup -from openai import OpenAI - -MAX_REPORT_CHARS = 200000 - -# Default evaluation prompt -EVAL_PROMPT = """You are an expert at evaluating connector regression test results. -Your task is to analyze the test report and determine if the regression tests should PASS or FAIL. - -Consider the following criteria: -1. All test cases should pass (no failed tests) -2. Record count differences between control and target versions should be minimal or explainable -3. Message count differences should not indicate data loss or corruption -4. Stream coverage should be reasonable -5. Any warnings or errors in test outputs should be evaluated for severity - -Provide your evaluation in the following JSON format: -{ - "pass": true/false, - "summary": "A concise 2-3 sentence summary of the evaluation", - "reasoning": "Detailed reasoning for your pass/fail decision, including specific issues found", - "severity": "critical/major/minor/none", - "recommendations": "Any recommendations for addressing issues" -} - -Be strict but fair in your evaluation. Minor differences are acceptable, but data loss, -corruption, or test failures should result in a FAIL.""" - - -def load_prompt_from_yaml(yaml_path: Path | None = None) -> tuple[list[dict[str, str]] | None, str | None, dict[str, Any] | None]: - """ - Load prompt from GitHub-format YAML file. - - Args: - yaml_path: Path to the .prompt.yaml file. If None, uses default location. - - Returns: - Tuple of (messages, model, modelParameters) or (None, None, None) if file not found or invalid - """ - if yaml_path is None: - # Default location: .github/prompts/regression-evaluation.prompt.yaml - github_workspace = os.environ.get("GITHUB_WORKSPACE") - if github_workspace: - yaml_path = Path(github_workspace) / ".github" / "prompts" / "regression-evaluation.prompt.yaml" - else: - script_dir = Path(__file__).parent - repo_root = script_dir.parent.parent.parent.parent.parent.parent - yaml_path = repo_root / ".github" / "prompts" / "regression-evaluation.prompt.yaml" - - if not yaml_path.exists(): - print(f"Prompt file not found at {yaml_path}, using default hardcoded prompt") - return None, None, None - - try: - with open(yaml_path, "r", encoding="utf-8") as f: - prompt_data = yaml.safe_load(f) - - messages = prompt_data.get("messages", []) - model = prompt_data.get("model") - model_params = prompt_data.get("modelParameters", {}) - - if not messages: - print(f"Warning: No messages found in {yaml_path}, using default hardcoded prompt") - return None, None, None - - print(f"Loaded prompt from {yaml_path}") - return messages, model, model_params - - except Exception as e: - print(f"Warning: Failed to load prompt from {yaml_path}: {e}") - print("Using default hardcoded prompt") - return None, None, None - - -def load_report_text(html_path: Path) -> str: - """ - Load and convert HTML report to clean text. - - Args: - html_path: Path to the report.html file - - Returns: - Clean text representation of the report - """ - with open(html_path, "r", encoding="utf-8") as f: - html_content = f.read() - - soup = BeautifulSoup(html_content, "html.parser") - - for element in soup(["script", "style"]): - element.decompose() - - report_text = soup.get_text("\n", strip=True) - - report_text = "\n".join(line.strip() for line in report_text.splitlines() if line.strip()) - - if len(report_text) > MAX_REPORT_CHARS: - original_length = len(report_text) - report_text = report_text[:MAX_REPORT_CHARS] - truncation_note = f"\n\n[Report truncated from {original_length} to {MAX_REPORT_CHARS} characters for evaluation]" - report_text += truncation_note - print(f"Warning: Report truncated from {original_length} to {MAX_REPORT_CHARS} characters") - - return report_text - - -def evaluate_with_llm(report_text: str, prompt: str | None = None, prompt_yaml_path: Path | None = None) -> dict[str, Any]: - """ - Use OpenAI LLM to evaluate the regression test report. - - Supports both OpenAI API and Ollama (OpenAI-compatible). - Configure via environment variables: - - OPENAI_API_KEY: API key (use 'ollama' for Ollama) - - OPENAI_BASE_URL: Optional base URL (e.g., http://127.0.0.1:11434/v1 for Ollama) - - EVAL_MODEL: Model name (defaults to gpt-4o, use llama3.2:3b for Ollama) - - EVAL_PROMPT_PATH: Optional path to custom .prompt.yaml file - - Args: - report_text: Full text of the report - prompt: Optional custom evaluation prompt string (legacy, overrides YAML) - prompt_yaml_path: Optional path to .prompt.yaml file - - Returns: - Dictionary containing evaluation results with 'pass', 'summary', 'reasoning', 'severity', and 'recommendations' keys - - Raises: - Exception: If LLM evaluation fails after retry - """ - api_key = os.environ.get("OPENAI_API_KEY") - base_url = os.environ.get("OPENAI_BASE_URL") - model = os.environ.get("EVAL_MODEL", "gpt-4o") - - if base_url: - client = OpenAI(api_key=api_key, base_url=base_url) - print(f"Using custom base URL: {base_url}") - else: - client = OpenAI(api_key=api_key) - - yaml_messages, yaml_model, yaml_params = load_prompt_from_yaml(prompt_yaml_path) - - if yaml_model and not os.environ.get("EVAL_MODEL"): - model = yaml_model - - temperature = 0.3 - if yaml_params and "temperature" in yaml_params: - temperature = yaml_params["temperature"] - - print(f"Using model: {model}") - - if prompt is not None: - messages = [ - {"role": "system", "content": prompt}, - {"role": "user", "content": f"Report:\n\n{report_text}"}, - ] - elif yaml_messages: - messages = [] - for msg in yaml_messages: - content = msg.get("content", "") - content = content.replace("{{report_text}}", report_text) - messages.append({"role": msg["role"], "content": content}) - else: - # Fallback to hardcoded EVAL_PROMPT - messages = [ - {"role": "system", "content": EVAL_PROMPT}, - {"role": "user", "content": f"Report:\n\n{report_text}"}, - ] - - try: - response = client.chat.completions.create( - model=model, - messages=messages, - temperature=temperature, - response_format={"type": "json_object"}, - ) - - evaluation = json.loads(response.choices[0].message.content) - return evaluation - except Exception as e: - error_msg = str(e).lower() - if "response_format" in error_msg or "json_object" in error_msg: - print(f"Warning: JSON response format not supported, retrying without it: {e}") - response = client.chat.completions.create( - model=model, - messages=messages, - temperature=temperature, - ) - content = response.choices[0].message.content - evaluation = json.loads(content) - return evaluation - raise - - -def write_github_summary(evaluation: dict[str, Any], model: str | None = None) -> None: - """ - Write the evaluation summary to GITHUB_STEP_SUMMARY. - - Args: - evaluation: LLM evaluation results - model: Model name used for evaluation (optional) - """ - summary_file = os.environ.get("GITHUB_STEP_SUMMARY") - if not summary_file: - print("Warning: GITHUB_STEP_SUMMARY environment variable not set. Writing to stdout instead.") - summary_file = "/dev/stdout" - - status_emoji = "✅" if evaluation["pass"] else "❌" - - model_info = f"model: {model}" if model else "OpenAI-compatible API" - - markdown = f"""# {status_emoji} Regression Test Evaluation: {"PASS" if evaluation['pass'] else "FAIL"} - -{evaluation['summary']} - - -{evaluation['reasoning']} - -{evaluation.get('recommendations', 'No specific recommendations.')} - ---- -*This evaluation was generated using {model_info}* -""" - - with open(summary_file, "a", encoding="utf-8") as f: - f.write(markdown) - - print(f"Summary written to {summary_file}") - - -def main(): - """Main entry point for the LLM evaluation script.""" - parser = argparse.ArgumentParser(description="Evaluate regression test reports using OpenAI LLM") - parser.add_argument("--report-path", type=Path, required=True, help="Path to the report.html file") - parser.add_argument("--prompt-file", type=Path, help="Optional path to a custom evaluation prompt file") - parser.add_argument("--output-json", type=Path, help="Optional path to write evaluation results as JSON") - - args = parser.parse_args() - - if not os.environ.get("OPENAI_API_KEY"): - print("Error: OPENAI_API_KEY environment variable not set", file=sys.stderr) - sys.exit(1) - - if not args.report_path.exists(): - print(f"Error: Report file not found: {args.report_path}", file=sys.stderr) - sys.exit(1) - - print(f"Loading report from: {args.report_path}") - report_text = load_report_text(args.report_path) - print(f"Report loaded: {len(report_text)} characters") - - custom_prompt = None - if args.prompt_file and args.prompt_file.exists(): - with open(args.prompt_file, "r", encoding="utf-8") as f: - custom_prompt = f.read() - print(f"Using custom prompt from: {args.prompt_file}") - - prompt_yaml_path = None - eval_prompt_path = os.environ.get("EVAL_PROMPT_PATH") - if eval_prompt_path: - prompt_yaml_path = Path(eval_prompt_path) - - print("Evaluating report with LLM...") - evaluation = evaluate_with_llm(report_text, custom_prompt, prompt_yaml_path) - - print(f"\nEvaluation Result: {'PASS' if evaluation['pass'] else 'FAIL'}") - print(f"Summary: {evaluation['summary']}") - - model = os.environ.get("EVAL_MODEL", "gpt-4o") - write_github_summary(evaluation, model) - - if args.output_json: - output_data = {"evaluation": evaluation} - with open(args.output_json, "w", encoding="utf-8") as f: - json.dump(output_data, f, indent=2) - print(f"Evaluation results written to: {args.output_json}") - - sys.exit(0 if evaluation["pass"] else 1) - - -if __name__ == "__main__": - main() diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_check.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_check.py deleted file mode 100644 index b18298472aef..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_check.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from collections.abc import Callable - -import pytest - -from live_tests.commons.models import ExecutionResult -from live_tests.consts import MAX_LINES_IN_REPORT -from live_tests.utils import fail_test_on_failing_execution_results, is_successful_check, tail_file - -pytestmark = [ - pytest.mark.anyio, -] - - -async def test_check_passes_on_both_versions( - record_property: Callable, - check_control_execution_result: ExecutionResult, - check_target_execution_result: ExecutionResult, -) -> None: - """This test runs the check command on both the control and target connectors. - It makes sure that the check command succeeds on both connectors. - Success is determined by the presence of a connection status message with a status of SUCCEEDED. - """ - fail_test_on_failing_execution_results( - record_property, - [ - check_control_execution_result, - check_target_execution_result, - ], - ) - - successful_control_check: bool = is_successful_check(check_control_execution_result) - successful_target_check: bool = is_successful_check(check_target_execution_result) - error_messages = [] - if not successful_control_check: - record_property( - f"Control CHECK standard output [Last {MAX_LINES_IN_REPORT} lines]", - tail_file(check_control_execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT), - ) - error_messages.append("The control check did not succeed, we cannot compare the results.") - if not successful_target_check: - record_property( - f"Target CHECK standard output [Last {MAX_LINES_IN_REPORT} lines]", - tail_file(check_target_execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT), - ) - error_messages.append("The target check did not succeed. Check the test artifacts for more information.") - if error_messages: - pytest.fail("\n".join(error_messages)) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_discover.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_discover.py deleted file mode 100644 index f4cd47d4b8e8..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_discover.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import json -from collections.abc import Callable, Iterable - -import pytest -from _pytest.fixtures import SubRequest -from airbyte_protocol.models import AirbyteStream # type: ignore - -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results, get_and_write_diff, get_catalog - -pytestmark = [ - pytest.mark.anyio, -] - - -async def test_catalog_are_the_same( - record_property: Callable, - request: SubRequest, - discover_control_execution_result: ExecutionResult, - discover_target_execution_result: ExecutionResult, -) -> None: - """This test runs the discover command on both the control and target connectors. - It makes sure that the discover command returns the same catalog for both connectors. - A catalog diff is generated and stored in the test artifacts if the catalogs are not the same. - """ - fail_test_on_failing_execution_results( - record_property, - [ - discover_control_execution_result, - discover_target_execution_result, - ], - ) - - control_catalog = get_catalog(discover_control_execution_result) - target_catalog = get_catalog(discover_target_execution_result) - - if control_catalog is None: - pytest.skip("The control discover did not return a catalog, we cannot compare the results.") - - if target_catalog is None: - pytest.fail("The target discover did not return a catalog. Check the test artifacts for more information.") - - control_streams = {c.name: c for c in control_catalog.streams} - target_streams = {t.name: t for t in target_catalog.streams} - - catalog_diff_path_prefix = "catalog_diff" - catalog_diff = get_and_write_diff( - request, - _get_filtered_sorted_streams(control_streams, target_streams.keys(), True), - _get_filtered_sorted_streams(target_streams, control_streams.keys(), True), - catalog_diff_path_prefix, - True, - None, - ) - - control_streams_diff_path_prefix = "control_streams_diff" - control_streams_diff = get_and_write_diff( - request, - _get_filtered_sorted_streams(control_streams, target_streams.keys(), False), - [], - control_streams_diff_path_prefix, - True, - None, - ) - - target_streams_diff_path_prefix = "target_streams_diff" - target_streams_diff = get_and_write_diff( - request, - [], - _get_filtered_sorted_streams(target_streams, control_streams.keys(), False), - target_streams_diff_path_prefix, - True, - None, - ) - - has_diff = catalog_diff or control_streams_diff or target_streams_diff - - if has_diff: - record_property("Catalog diff", catalog_diff) - record_property("Control streams diff", control_streams_diff) - record_property("Target streams diff", target_streams_diff) - - if control_streams.keys() != target_streams.keys(): - pytest.fail( - f"The set of streams in the control and target catalogs do not match. control_streams={', '.join(control_streams.keys())} target_streams={', '.join(target_streams.keys())}. Detailed diff is stored in Diff is stored at {catalog_diff}, {control_streams_diff}, and {target_streams_diff}." - ) - - else: - pytest.fail( - f"The control and target output are not the same. Diff is stored at {catalog_diff}, {control_streams_diff}, and {target_streams_diff}." - ) - - -def _get_filtered_sorted_streams(streams: dict[str, AirbyteStream], stream_set: Iterable[str], include_target: bool) -> list[dict]: - return sorted( - filter( - lambda x: (x["name"] in stream_set if include_target else x["name"] not in stream_set), - [json.loads(s.json(sort_keys=True)) for s in streams.values()], - ), - key=lambda x: x["name"], - ) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_read.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_read.py deleted file mode 100644 index c55dae963289..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_read.py +++ /dev/null @@ -1,551 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import json -from collections.abc import Callable, Generator, Iterable -from typing import TYPE_CHECKING, Any, Optional - -import pytest -from airbyte_protocol.models import AirbyteMessage # type: ignore -from deepdiff import DeepDiff # type: ignore - -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results, get_and_write_diff, get_test_logger, write_string_to_test_artifact - -if TYPE_CHECKING: - from _pytest.fixtures import SubRequest - -pytestmark = [ - pytest.mark.anyio, -] - - -EXCLUDE_PATHS = ["emitted_at"] - - -class TestDataIntegrity: - """This class contains tests that check if the data integrity is preserved between the control and target versions. - The tests have some overlap but they are meant to be gradually stricter in terms of integrity checks. - - 1. test_record_count: On each stream, check if the target version produces at least the same number of records as the control version. - 2. test_all_pks_are_produced_in_target_version: On each stream, check if all primary key values produced by the control version are present in the target version. - 3. test_all_records_are_the_same: On each stream, check if all records produced by the control version are the same as in the target version. This will write a diff of the records to the test artifacts. - - All these test have a full refresh and incremental variant. - """ - - async def _check_all_pks_are_produced_in_target_version( - self, - request: SubRequest, - record_property: Callable, - read_with_state_control_execution_result: ExecutionResult, - read_with_state_target_execution_result: ExecutionResult, - ) -> None: - """This test gathers all primary key values from the control version and checks if they are present in the target version for each stream. - If there are missing primary keys, the test fails and the missing records are stored in the test artifacts. - Args: - request (SubRequest): The test request. - record_property (Callable): A callable for stashing information on the report. - streams: (Iterable[str]): The list of streams configured for the connection. - primary_keys_per_stream (Dict[str, Optional[List[str]]]): The primary keys for each stream. - read_with_state_control_execution_result (ExecutionResult): The control version execution result. - read_with_state_target_execution_result (ExecutionResult): The target version execution result. - """ - if not read_with_state_control_execution_result.primary_keys_per_stream: - pytest.skip("No primary keys provided on any stream. Skipping the test.") - - logger = get_test_logger(request) - streams_with_missing_records = set() - for stream_name in read_with_state_control_execution_result.configured_streams: - _primary_key = read_with_state_control_execution_result.primary_keys_per_stream[stream_name] - if not _primary_key: - # TODO: report skipped PK test per individual stream - logger.warning(f"No primary keys provided on stream {stream_name}.") - continue - - primary_key = _primary_key[0] if isinstance(_primary_key, list) else _primary_key - - control_pks = set() - target_pks = set() - logger.info(f"Retrieving primary keys for stream {stream_name} on control version.") - for control_record in read_with_state_control_execution_result.get_records_per_stream(stream_name): - control_pks.add(control_record.record.data[primary_key]) - - logger.info(f"Retrieving primary keys for stream {stream_name} on target version.") - for target_record in read_with_state_target_execution_result.get_records_per_stream(stream_name): - target_pks.add(target_record.record.data[primary_key]) - - if missing_pks := control_pks - target_pks: - logger.warning(f"Found {len(missing_pks)} primary keys for stream {stream_name}. Retrieving missing records.") - streams_with_missing_records.add(stream_name) - missing_records = [ - r - for r in read_with_state_control_execution_result.get_records_per_stream(stream_name) - if r.record.data[primary_key] in missing_pks - ] - record_property( - f"Missing records on stream {stream_name}", - json.dumps(missing_records), - ) - artifact_path = write_string_to_test_artifact( - request, - json.dumps(missing_records), - f"missing_records_{stream_name}.json", - subdir=request.node.name, - ) - logger.info(f"Missing records for stream {stream_name} are stored in {artifact_path}.") - if streams_with_missing_records: - pytest.fail(f"Missing records for streams: {', '.join(streams_with_missing_records)}.") - - async def _check_record_counts( - self, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - record_count_difference_per_stream: dict[str, dict[str, int]] = {} - for stream_name in read_control_execution_result.configured_streams: - control_records_count = sum(1 for _ in read_control_execution_result.get_records_per_stream(stream_name)) - target_records_count = sum(1 for _ in read_target_execution_result.get_records_per_stream(stream_name)) - - difference = { - "delta": target_records_count - control_records_count, - "control": control_records_count, - "target": target_records_count, - } - - if difference["delta"] != 0: - record_count_difference_per_stream[stream_name] = difference - error_messages = [] - for stream, difference in record_count_difference_per_stream.items(): - if difference["delta"] > 0: - error_messages.append( - f"Stream {stream} has {difference['delta']} more records in the target version ({difference['target']} vs. {difference['control']})." - ) - if difference["delta"] < 0: - error_messages.append( - f"Stream {stream} has {-difference['delta']} fewer records in the target version({difference['target']} vs. {difference['control']})." - ) - if error_messages: - record_property("Record count differences", "\n".join(error_messages)) - pytest.fail("Record counts are different.") - - async def _check_all_records_are_the_same( - self, - request: SubRequest, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - """This test checks if all records in the control version are present in the target version for each stream. - If there are mismatches, the test fails and the missing records are stored in the test artifacts. - It will catch differences in record schemas, missing records, and extra records. - - Args: - request (SubRequest): The test request. - read_control_execution_result (ExecutionResult): The control version execution result. - read_target_execution_result (ExecutionResult): The target version execution result. - """ - streams_with_diff = set() - for stream in read_control_execution_result.configured_streams: - control_records = list(read_control_execution_result.get_records_per_stream(stream)) - target_records = list(read_target_execution_result.get_records_per_stream(stream)) - - if control_records and not target_records: - pytest.fail(f"Stream {stream} is missing in the target version.") - - if primary_key := read_control_execution_result.primary_keys_per_stream.get(stream): - diffs = self._get_diff_on_stream_with_pk( - request, - record_property, - stream, - control_records, - target_records, - primary_key, - ) - else: - diffs = self._get_diff_on_stream_without_pk( - request, - record_property, - stream, - control_records, - target_records, - ) - - if diffs: - streams_with_diff.add(stream) - - if streams_with_diff: - messages = [ - f"Records for stream {stream} are different. Please check the diff in the test artifacts for debugging." - for stream in sorted(streams_with_diff) - ] - pytest.fail("/n".join(messages)) - - def _check_record_schema_match( - self, - request: SubRequest, - record_property: Callable, - control_execution_result: ExecutionResult, - target_execution_result: ExecutionResult, - ) -> None: - """This test checks if the schema of the records in the control and target versions match. - It compares the meta schema inferred for each streams on the control and target versions. - It also fetches an example record for each stream from the DuckDB instance and compares the schema of the records. - - Args: - record_property (Callable): The record property to store the mismatching fields. - control_execution_result (ExecutionResult): The control version execution result. - target_execution_result (ExecutionResult): The target version execution result. - """ - logger = get_test_logger(request) - - assert control_execution_result.stream_schemas is not None, "Control schemas were not inferred." - assert target_execution_result.stream_schemas is not None, "Target schemas were not inferred." - - mismatches_count = 0 - for stream in control_execution_result.stream_schemas: - control_schema = control_execution_result.stream_schemas.get(stream, {}) - if not control_schema: - logger.warning(f"Stream {stream} was not found in the control results.") - - target_schema = target_execution_result.stream_schemas.get(stream, {}) - if control_schema and not target_schema: - logger.warning(f"Stream {stream} was present in the control results but not in the target results.") - - diff = DeepDiff(control_schema, target_schema, ignore_order=True) - if diff: - record_property(f"{stream} diff between control and target version", diff.pretty()) - try: - control_record = next(control_execution_result.get_records_per_stream(stream)) - control_example = json.dumps(control_record.record.data, indent=2) - record_property(f"{stream} example record for control version", control_example) - except StopIteration: - logger.warning(f"Stream {stream} has no record in the control version.") - try: - target_record = next(target_execution_result.get_records_per_stream(stream)) - target_example = json.dumps(target_record.record.data, indent=2) - record_property(f"{stream} example record for target version", target_example) - except StopIteration: - logger.warning(f"Stream {stream} has no record in the target version.") - mismatches_count += 1 - - if mismatches_count > 0: - pytest.fail(f"{mismatches_count} streams have mismatching schemas between control and target versions.") - - @pytest.mark.with_state() - async def test_record_count_with_state( - self, - record_property: Callable, - read_with_state_control_execution_result: ExecutionResult, - read_with_state_target_execution_result: ExecutionResult, - ) -> None: - """This test compares the record counts between the control and target versions on each stream. - Records are pulled from the output of the read command to which the connection state is passed. - It fails if there are any differences in the record counts. - It is not bulletproof, if the upstream source supports insertion or deletion it may lead to false positives. - The HTTP cache used between the control and target versions command execution might limit this problem. - Extra records in the target version might mean that a bug was fixed, but it could also mean that the target version produces duplicates. - We should add a new test for duplicates and not fail this one if extra records are found. - More advanced checks are done in the other tests. - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ], - ) - await self._check_record_counts( - record_property, - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ) - - @pytest.mark.without_state() - async def test_record_count_without_state( - self, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - """This test compares the record counts between the control and target versions on each stream. - Records are pulled from the output of the read command to which no connection state is passed (leading to a full-refresh like sync). - It fails if there are any differences in the record counts. - It is not bulletproof, if the upstream source supports insertion or deletion it may lead to false positives. - The HTTP cache used between the control and target versions command execution might limit this problem. - Extra records in the target version might mean that a bug was fixed, but it could also mean that the target version produces duplicates. - We should add a new test for duplicates and not fail this one if extra records are found. - More advanced checks are done in the other tests. - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_control_execution_result, - read_target_execution_result, - ], - ) - await self._check_record_counts( - record_property, - read_control_execution_result, - read_target_execution_result, - ) - - @pytest.mark.with_state() - async def test_all_pks_are_produced_in_target_version_with_state( - self, - request: SubRequest, - record_property: Callable, - read_with_state_control_execution_result: ExecutionResult, - read_with_state_target_execution_result: ExecutionResult, - ) -> None: - """This test checks if all primary key values produced by the control version are present in the target version for each stream. - It is reading the records from the output of the read command to which the connection state is passed. - A failing test means that the target version is missing some records. - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ], - ) - await self._check_all_pks_are_produced_in_target_version( - request, - record_property, - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ) - - @pytest.mark.without_state() - async def test_all_pks_are_produced_in_target_version_without_state( - self, - request: SubRequest, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - """This test checks if all primary key values produced by the control version are present in the target version for each stream. - Records are pulled from the output of the read command to which no connection state is passed (leading to a full-refresh like sync). - A failing test means that the target version is missing some records. - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_control_execution_result, - read_target_execution_result, - ], - ) - await self._check_all_pks_are_produced_in_target_version( - request, - record_property, - read_control_execution_result, - read_target_execution_result, - ) - - @pytest.mark.with_state() - async def test_record_schema_match_with_state( - self, - request: SubRequest, - record_property: Callable, - read_with_state_control_execution_result: ExecutionResult, - read_with_state_target_execution_result: ExecutionResult, - ) -> None: - """This test checks if the schema of the streams in the control and target versions match. - It produces a meta schema for each stream on control and target version and compares them. - It is not using the catalog schema, but inferring schemas from the actual records produced by the read command. - Records are pulled from the output of the read command to which the connection state is passed. - """ - self._check_record_schema_match( - request, - record_property, - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ) - - @pytest.mark.without_state() - async def test_record_schema_match_without_state( - self, - request: SubRequest, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - """This test checks if the schema of the streams in the control and target versions match. - It produces a meta schema for each stream on control and target version and compares them. - It is not using the catalog schema, but inferring schemas from the actual records produced by the read command. - Records are pulled from the output of the read command to which the connection state is passed. - """ - self._check_record_schema_match( - request, - record_property, - read_control_execution_result, - read_target_execution_result, - ) - - @pytest.mark.allow_diagnostic_mode - @pytest.mark.with_state() - async def test_all_records_are_the_same_with_state( - self, - request: SubRequest, - record_property: Callable, - read_with_state_control_execution_result: ExecutionResult, - read_with_state_target_execution_result: ExecutionResult, - ) -> None: - """This test compares all records between the control and target versions on each stream. - It is very sensitive to record schema and order changes. - It fails if there are any differences in the records. - It is reading the records from the output of the read command to which the connection state is passed. - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ], - ) - await self._check_all_records_are_the_same( - request, - record_property, - read_with_state_control_execution_result, - read_with_state_target_execution_result, - ) - - @pytest.mark.allow_diagnostic_mode - @pytest.mark.without_state() - async def test_all_records_are_the_same_without_state( - self, - request: SubRequest, - record_property: Callable, - read_control_execution_result: ExecutionResult, - read_target_execution_result: ExecutionResult, - ) -> None: - """This test compares all records between the control and target versions on each stream. - It is very sensitive to record schema and order changes. - It fails if there are any differences in the records. - It is reading the records from the output of the read command to which no connection state is passed (leading to a full-refresh like sync). - """ - fail_test_on_failing_execution_results( - record_property, - [ - read_control_execution_result, - read_target_execution_result, - ], - ) - await self._check_all_records_are_the_same( - request, - record_property, - read_control_execution_result, - read_target_execution_result, - ) - - def _get_diff_on_stream_with_pk( - self, - request: SubRequest, - record_property: Callable, - stream: str, - control_records: list[AirbyteMessage], - target_records: list[AirbyteMessage], - primary_key: list[str], - ) -> Optional[Iterable[str]]: - control_pks = {r.record.data[primary_key[0]] for r in control_records} - target_pks = {r.record.data[primary_key[0]] for r in target_records} - - # Compare the diff for all records whose primary key is in - record_diff_path_prefix = f"{stream}_record_diff" - record_diff = get_and_write_diff( - request, - _get_filtered_sorted_records(control_records, target_pks, True, primary_key), - _get_filtered_sorted_records(target_records, control_pks, True, primary_key), - record_diff_path_prefix, - ignore_order=False, - exclude_paths=EXCLUDE_PATHS, - ) - - control_records_diff_path_prefix = f"{stream}_control_records_diff" - control_records_diff = get_and_write_diff( - request, - _get_filtered_sorted_records(control_records, target_pks, False, primary_key), - [], - control_records_diff_path_prefix, - ignore_order=False, - exclude_paths=EXCLUDE_PATHS, - ) - - target_records_diff_path_prefix = f"{stream}_target_records_diff" - target_records_diff = get_and_write_diff( - request, - [], - _get_filtered_sorted_records(target_records, control_pks, False, primary_key), - target_records_diff_path_prefix, - ignore_order=False, - exclude_paths=EXCLUDE_PATHS, - ) - - has_diff = record_diff or control_records_diff or target_records_diff - - if has_diff: - record_property( - f"{stream} stream: records with primary key in target & control whose values differ", - record_diff, - ) - record_property( - f"{stream} stream: records in control but not target", - control_records_diff, - ) - record_property( - f"{stream} stream: records in target but not control", - target_records_diff, - ) - - return (record_diff, control_records_diff, target_records_diff) - return None - - def _get_diff_on_stream_without_pk( - self, - request: SubRequest, - record_property: Callable, - stream: str, - control_records: list[AirbyteMessage], - target_records: list[AirbyteMessage], - ) -> Optional[Iterable[str]]: - diff = get_and_write_diff( - request, - [json.loads(r.record.json(sort_keys=True)) for r in control_records], - [json.loads(r.record.json(sort_keys=True)) for r in target_records], - f"{stream}_diff", - ignore_order=True, - exclude_paths=EXCLUDE_PATHS, - ) - if diff: - record_property(f"Diff for stream {stream}", diff) - return (diff,) - return None - - -def _get_filtered_sorted_records( - records: list[AirbyteMessage], - primary_key_set: set[Generator[Any, Any, None]], - include_target: bool, - primary_key: list[str], -) -> list[dict]: - """ - Get a list of records sorted by primary key, and filtered as specified. - - For example, if `include_target` is true, we filter the records such that - only those whose primary key is in `primary_key_set` are returned. - If `include_target` is false, we only return records whose primary key - is not in `primary_key_set`. - """ - if include_target: - _filter = lambda x: x["data"].get(primary_key[0]) in primary_key_set - else: - _filter = lambda x: x["data"].get(primary_key[0]) not in primary_key_set - - return sorted( - filter( - _filter, - [json.loads(s.record.json(sort_keys=True)) for s in records], - ), - key=lambda x: x["data"][primary_key[0]], - ) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_spec.py b/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_spec.py deleted file mode 100644 index 7a1985fd3e40..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/regression_tests/test_spec.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from collections.abc import Callable - -import pytest -from airbyte_protocol.models import Type # type: ignore - -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results - -pytestmark = [ - pytest.mark.anyio, -] - - -async def test_spec_passes_on_both_versions( - record_property: Callable, - spec_control_execution_result: ExecutionResult, - spec_target_execution_result: ExecutionResult, -) -> None: - """This test runs the spec command on both the control and target connectors. - It makes sure that the spec command succeeds on both connectors by checking the presence of a SPEC message. - """ - fail_test_on_failing_execution_results( - record_property, - [ - spec_control_execution_result, - spec_target_execution_result, - ], - ) - - def has_spec(execution_result: ExecutionResult) -> bool: - for message in execution_result.airbyte_messages: - if message.type is Type.SPEC and message.spec: - return True - return False - - if not has_spec(spec_control_execution_result): - pytest.skip("The control spec did not succeed, we cannot compare the results.") - if not has_spec(spec_target_execution_result): - pytest.fail("The target spec did not succeed. Check the test artifacts for more information.") diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/report.py b/airbyte-ci/connectors/live-tests/src/live_tests/report.py deleted file mode 100644 index 78bb06720c72..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/report.py +++ /dev/null @@ -1,411 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import datetime -import json -from abc import ABC, abstractmethod -from collections import defaultdict -from collections.abc import Iterable, MutableMapping -from copy import deepcopy -from enum import Enum -from functools import cache -from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional - -import requests -import yaml -from jinja2 import Environment, PackageLoader, select_autoescape - -from live_tests import stash_keys -from live_tests.commons.models import Command, ConnectionObjects -from live_tests.consts import MAX_LINES_IN_REPORT - -if TYPE_CHECKING: - from typing import List - - import pytest - from _pytest.config import Config - from airbyte_protocol.models import AirbyteStream, ConfiguredAirbyteStream, SyncMode, Type # type: ignore - - from live_tests.commons.models import ExecutionResult - - -class ReportState(Enum): - INITIALIZING = "initializing" - RUNNING = "running" - FINISHED = "finished" - - -class BaseReport(ABC): - TEMPLATE_NAME: str - - def __init__(self, path: Path, pytest_config: Config) -> None: - self.path = path - self.pytest_config = pytest_config - self.created_at = datetime.datetime.utcnow() - self.updated_at = self.created_at - - self.control_execution_results_per_command: dict[Command, List[ExecutionResult]] = {command: [] for command in Command} - self.target_execution_results_per_command: dict[Command, List[ExecutionResult]] = {command: [] for command in Command} - self.update(ReportState.INITIALIZING) - - @abstractmethod - def render(self) -> None: - pass - - @property - def all_connection_objects(self) -> List[ConnectionObjects]: - return self.pytest_config.stash[stash_keys.ALL_CONNECTION_OBJECTS] - - def add_control_execution_result(self, control_execution_result: ExecutionResult) -> None: - self.control_execution_results_per_command[control_execution_result.command].append(control_execution_result) - self.update() - - def add_target_execution_result(self, target_execution_result: ExecutionResult) -> None: - self.target_execution_results_per_command[target_execution_result.command].append(target_execution_result) - self.update() - - def update(self, state: ReportState = ReportState.RUNNING) -> None: - self._state = state - self.updated_at = datetime.datetime.utcnow() - self.render() - - -class PrivateDetailsReport(BaseReport): - TEMPLATE_NAME = "private_details.html.j2" - SPEC_SECRET_MASK_URL = "https://connectors.airbyte.com/files/registries/v0/specs_secrets_mask.yaml" - - def __init__(self, path: Path, pytest_config: Config) -> None: - self.secret_properties = self.get_secret_properties() - super().__init__(path, pytest_config) - - def get_secret_properties(self) -> list: - response = requests.get(self.SPEC_SECRET_MASK_URL) - response.raise_for_status() - return yaml.safe_load(response.text)["properties"] - - def scrub_secrets_from_config(self, to_scrub: MutableMapping) -> MutableMapping: - if isinstance(to_scrub, dict): - for key, value in to_scrub.items(): - if key in self.secret_properties: - to_scrub[key] = "********" - elif isinstance(value, dict): - to_scrub[key] = self.scrub_secrets_from_config(value) - return to_scrub - - @property - def renderable_connection_objects(self) -> list[dict[str, Any]]: - return [ - { - "workspace_id": connection_objects.workspace_id, - "connection_id": connection_objects.connection_id, - "hashed_connection_id": connection_objects.hashed_connection_id, - "source_config": json.dumps( - self.scrub_secrets_from_config( - deepcopy(connection_objects.source_config.data) if connection_objects.source_config else {} - ), - indent=2, - ), - "url": connection_objects.url, - } - for connection_objects in self.all_connection_objects - ] - - def render(self) -> None: - jinja_env = Environment( - loader=PackageLoader(__package__, "templates"), - autoescape=select_autoescape(), - trim_blocks=False, - lstrip_blocks=True, - ) - template = jinja_env.get_template(self.TEMPLATE_NAME) - rendered = template.render( - user=self.pytest_config.stash[stash_keys.USER], - test_date=self.created_at, - all_connection_objects=self.renderable_connection_objects, - connector_image=self.pytest_config.stash[stash_keys.CONNECTOR_IMAGE], - control_version=self.pytest_config.stash[stash_keys.CONTROL_VERSION], - target_version=self.pytest_config.stash[stash_keys.TARGET_VERSION], - requested_urls_per_command=self.get_requested_urls_per_command(), - fully_generated=self._state is ReportState.FINISHED, - ) - self.path.write_text(rendered) - - def get_requested_urls_per_command( - self, - ) -> dict[Command, list[tuple[int, str, str]]]: - requested_urls_per_command = {} - all_commands = sorted( - list(set(self.control_execution_results_per_command.keys()).union(set(self.target_execution_results_per_command.keys()))), - key=lambda command: command.value, - ) - for command in all_commands: - if command in self.control_execution_results_per_command: - control_flows = [ - flow for exec_result in self.control_execution_results_per_command[command] for flow in exec_result.http_flows - ] - else: - control_flows = [] - if command in self.target_execution_results_per_command: - target_flows = [ - flow for exec_result in self.target_execution_results_per_command[command] for flow in exec_result.http_flows - ] - else: - target_flows = [] - all_flows = [] - max_flows = max(len(control_flows), len(target_flows)) - for i in range(max_flows): - control_url = control_flows[i].request.url if i < len(control_flows) else "" - target_url = target_flows[i].request.url if i < len(target_flows) else "" - all_flows.append((i, control_url, target_url)) - requested_urls_per_command[command] = all_flows - return requested_urls_per_command - - -class TestReport(BaseReport): - TEMPLATE_NAME = "report.html.j2" - - def __init__(self, path: Path, pytest_config: Config, private_details_url: Optional[str] = None) -> None: - self.private_details_url = private_details_url - self.test_results: list[dict[str, Any]] = [] - super().__init__(path, pytest_config) - - def add_test_result(self, test_report: pytest.TestReport, test_documentation: Optional[str] = None) -> None: - cut_properties: list[tuple[str, str]] = [] - for property_name, property_value in test_report.user_properties: - if len(str(property_value).splitlines()) > MAX_LINES_IN_REPORT: - cut_property_name = f"{property_name} (truncated)" - cut_property_value = "\n".join(str(property_value).splitlines()[:MAX_LINES_IN_REPORT]) - cut_property_value += f"\n... and {len(str(property_value).splitlines()) - MAX_LINES_IN_REPORT} more lines.\nPlease check the artifacts files for the full output." - cut_properties.append((cut_property_name, cut_property_value)) - else: - cut_properties.append((property_name, str(property_value))) - self.test_results.append( - { - "name": test_report.head_line, - "result": test_report.outcome, - "output": test_report.longreprtext if test_report.longrepr else "", - "properties": cut_properties, - "documentation": test_documentation, - } - ) - self.update() - - def render(self) -> None: - jinja_env = Environment( - loader=PackageLoader(__package__, "templates"), - autoescape=select_autoescape(), - trim_blocks=False, - lstrip_blocks=True, - ) - template = jinja_env.get_template(self.TEMPLATE_NAME) - rendered = template.render( - fully_generated=self._state is ReportState.FINISHED, - user=self.pytest_config.stash[stash_keys.USER], - test_date=self.updated_at, - connector_image=self.pytest_config.stash[stash_keys.CONNECTOR_IMAGE], - control_version=self.pytest_config.stash[stash_keys.CONTROL_VERSION], - target_version=self.pytest_config.stash[stash_keys.TARGET_VERSION], - all_connection_objects=self.renderable_connection_objects, - message_count_per_type=self.get_message_count_per_type(), - stream_coverage_metrics=self.get_stream_coverage_metrics(), - untested_streams=self.get_untested_streams(), - selected_streams=self.get_configured_streams(), - sync_mode_coverage=self.get_sync_mode_coverage(), - http_metrics_per_command=self.get_http_metrics_per_command(), - record_count_per_command_and_stream=self.get_record_count_per_stream(), - test_results=self.test_results, - max_lines=MAX_LINES_IN_REPORT, - private_details_url=self.private_details_url, - ) - self.path.write_text(rendered) - - @property - def renderable_connection_objects(self) -> list[dict[str, Any]]: - return [ - { - "hashed_connection_id": connection_objects.hashed_connection_id, - "catalog": connection_objects.catalog.json(indent=2) if connection_objects.catalog else {}, - "configured_catalog": connection_objects.configured_catalog.json(indent=2), - "state": json.dumps(connection_objects.state if connection_objects.state else {}, indent=2), - } - for connection_objects in self.all_connection_objects - ] - - def get_stream_coverage_metrics(self) -> dict[str, str]: - configured_catalog_stream_count = len(self.get_configured_streams()) - catalog_stream_count = len(self.all_streams) - coverage = configured_catalog_stream_count / catalog_stream_count if catalog_stream_count > 0 else 0 - return { - "Available in catalog": str(catalog_stream_count), - "In use (in configured catalog)": str(configured_catalog_stream_count), - "Coverage": f"{coverage * 100:.2f}%", - } - - def get_record_count_per_stream( - self, - ) -> dict[Command, dict[str, dict[str, int] | int]]: - record_count_per_command_and_stream: dict[Command, dict[str, dict[str, int] | int]] = {} - for control_results, target_results in zip( - self.control_execution_results_per_command.values(), - self.target_execution_results_per_command.values(), - strict=False, - ): - per_stream_count = defaultdict(lambda: {"control": 0, "target": 0}) # type: ignore - for results, source in [ - (control_results, "control"), - (target_results, "target"), - ]: - stream_schemas: Iterable = [stream_schema for result in results for stream_schema in result.stream_schemas] - - for stream in stream_schemas: - per_stream_count[stream][source] = sum([self._get_record_count_for_stream(result, stream) for result in results]) - for stream in per_stream_count: - per_stream_count[stream]["difference"] = per_stream_count[stream]["target"] - per_stream_count[stream]["control"] - if control_results: - record_count_per_command_and_stream[control_results[0].command] = per_stream_count # type: ignore - - return record_count_per_command_and_stream - - @cache - def _get_record_count_for_stream(self, result: ExecutionResult, stream: str) -> int: - return sum(1 for _ in result.get_records_per_stream(stream)) # type: ignore - - def get_untested_streams(self) -> list[str]: - streams_with_data: set[str] = set() - for stream_count in self.get_record_count_per_stream().values(): - streams_with_data.update(stream_count.keys()) - - return [stream.name for stream in self.all_streams if stream.name not in streams_with_data] - - @property - def all_streams(self) -> List[AirbyteStream]: - # A set would be better but AirbyteStream is not hashable - all_streams = dict() - for connection_objects in self.all_connection_objects: - if connection_objects.catalog: - for stream in connection_objects.catalog.streams: - all_streams[stream.name] = stream - return list(all_streams.values()) - - @property - def all_configured_streams(self) -> List[ConfiguredAirbyteStream]: - all_configured_streams = dict() - for connection_objects in self.all_connection_objects: - if connection_objects.configured_catalog: - for configured_airbyte_stream in connection_objects.configured_catalog.streams: - all_configured_streams[configured_airbyte_stream.stream.name] = configured_airbyte_stream - return list(all_configured_streams.values()) - - def get_configured_streams(self) -> dict[str, dict[str, SyncMode | bool]]: - untested_streams = self.get_untested_streams() - return ( - { - configured_stream.stream.name: { - "sync_mode": configured_stream.sync_mode, - "has_data": configured_stream.stream.name not in untested_streams, - } - for configured_stream in sorted( - self.all_configured_streams, - key=lambda x: x.stream.name, - ) - } - if self.all_configured_streams - else {} - ) - - def get_sync_mode_coverage(self) -> dict[SyncMode, int]: - count_per_sync_mode: dict[SyncMode, int] = defaultdict(int) - for s in self.get_configured_streams().values(): - count_per_sync_mode[s["sync_mode"]] += 1 - return count_per_sync_mode - - def get_message_count_per_type( - self, - ) -> tuple[list[Command], dict[Type, dict[Command, dict[str, int]]]]: - message_count_per_type_and_command: dict[Type, dict[Command, dict[str, int]]] = {} - all_message_types = set() - all_commands = set() - # Gather all message types from both control and target execution reports - for execution_results_per_command in [ - self.control_execution_results_per_command, - self.target_execution_results_per_command, - ]: - for command, execution_results in execution_results_per_command.items(): - all_commands.add(command) - for execution_result in execution_results: - for message_type in execution_result.get_message_count_per_type().keys(): - all_message_types.add(message_type) - - all_commands_sorted = sorted(all_commands, key=lambda command: command.value) - all_message_types_sorted = sorted(all_message_types, key=lambda message_type: message_type.value) - - # Iterate over all message types and commands to count messages - for message_type in all_message_types_sorted: - message_count_per_type_and_command[message_type] = {} - for command in all_commands_sorted: - message_count_per_type_and_command[message_type][command] = { - "control": 0, - "target": 0, - } - if command in self.control_execution_results_per_command: - for control_result in self.control_execution_results_per_command[command]: - message_count_per_type_and_command[message_type][command]["control"] += ( - control_result.get_message_count_per_type().get(message_type, 0) - ) - if command in self.target_execution_results_per_command: - for target_result in self.target_execution_results_per_command[command]: - message_count_per_type_and_command[message_type][command]["target"] += ( - target_result.get_message_count_per_type().get(message_type, 0) - ) - - message_count_per_type_and_command[message_type][command]["difference"] = ( - message_count_per_type_and_command[message_type][command]["target"] - - message_count_per_type_and_command[message_type][command]["control"] - ) - return all_commands_sorted, message_count_per_type_and_command - - def get_http_metrics_per_command( - self, - ) -> dict[Command, dict[str, dict[str, int | str] | int]]: - metrics_per_command: dict[Command, dict[str, dict[str, int | str] | int]] = {} - - for control_results, target_results in zip( - self.control_execution_results_per_command.values(), - self.target_execution_results_per_command.values(), - strict=False, - ): - # TODO - # Duplicate flow counts may be wrong when we gather results from multiple connections - control_flow_count = sum([len(control_result.http_flows) for control_result in control_results]) - control_all_urls = [f.request.url for control_result in control_results for f in control_result.http_flows] - control_duplicate_flow_count = len(control_all_urls) - len(set(control_all_urls)) - control_cache_hits_count = sum(1 for control_result in control_results for f in control_result.http_flows if f.is_replay) - control_cache_hit_ratio = f"{(control_cache_hits_count / control_flow_count) * 100:.2f}%" if control_flow_count != 0 else "N/A" - - target_flow_count = sum([len(target_result.http_flows) for target_result in target_results]) - target_all_urls = [f.request.url for target_result in target_results for f in target_result.http_flows] - target_duplicate_flow_count = len(target_all_urls) - len(set(target_all_urls)) - - target_cache_hits_count = sum(1 for target_result in target_results for f in target_result.http_flows if f.is_replay) - target_cache_hit_ratio = f"{(target_cache_hits_count / target_flow_count) * 100:.2f}%" if target_flow_count != 0 else "N/A" - - flow_count_difference = target_flow_count - control_flow_count - if control_results: - metrics_per_command[control_results[0].command] = { - "control": { - "flow_count": control_flow_count, - "duplicate_flow_count": control_duplicate_flow_count, - "cache_hits_count": control_cache_hits_count, - "cache_hit_ratio": control_cache_hit_ratio, - }, - "target": { - "flow_count": target_flow_count, - "duplicate_flow_count": target_duplicate_flow_count, - "cache_hits_count": target_cache_hits_count, - "cache_hit_ratio": target_cache_hit_ratio, - }, - "difference": flow_count_difference, - } - - return metrics_per_command diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/stash_keys.py b/airbyte-ci/connectors/live-tests/src/live_tests/stash_keys.py deleted file mode 100644 index 4d05f4e93974..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/stash_keys.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -from pathlib import Path -from typing import List - -import pytest - -from live_tests.commons.evaluation_modes import TestEvaluationMode -from live_tests.commons.models import ConnectionObjects, ConnectionSubset -from live_tests.report import PrivateDetailsReport, TestReport - -AIRBYTE_API_KEY = pytest.StashKey[str]() -AUTO_SELECT_CONNECTION = pytest.StashKey[bool]() -ALL_CONNECTION_OBJECTS = pytest.StashKey[List[ConnectionObjects]]() -CONNECTION_URL = pytest.StashKey[str | None]() -CONNECTOR_IMAGE = pytest.StashKey[str]() -CONTROL_VERSION = pytest.StashKey[str]() -CONNECTION_SUBSET = pytest.StashKey[ConnectionSubset]() -DAGGER_LOG_PATH = pytest.StashKey[Path]() -DUCKDB_PATH = pytest.StashKey[Path]() -HTTP_DUMP_CACHE_VOLUMES = pytest.StashKey[list]() -RUN_IN_AIRBYTE_CI = pytest.StashKey[bool]() # Running in airbyte-ci, locally or in GhA -IS_PRODUCTION_CI = pytest.StashKey[bool]() # Running in airbyte-ci in GhA -IS_PERMITTED_BOOL = pytest.StashKey[bool]() -PR_URL = pytest.StashKey[str]() -TEST_REPORT = pytest.StashKey[TestReport]() -PRIVATE_DETAILS_REPORT = pytest.StashKey[PrivateDetailsReport]() -RETRIEVAL_REASONS = pytest.StashKey[str]() -SELECTED_STREAMS = pytest.StashKey[set[str]]() -SESSION_RUN_ID = pytest.StashKey[str]() -SHOULD_READ_WITH_STATE = pytest.StashKey[bool]() -DISABLE_PROXY = pytest.StashKey[bool]() -TARGET_VERSION = pytest.StashKey[str]() -TEST_ARTIFACT_DIRECTORY = pytest.StashKey[Path]() -USER = pytest.StashKey[str]() -WORKSPACE_ID = pytest.StashKey[str]() -TEST_EVALUATION_MODE = pytest.StashKey[TestEvaluationMode] -MAX_CONNECTIONS = pytest.StashKey[int | None]() diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/templates/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/templates/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/templates/private_details.html.j2 b/airbyte-ci/connectors/live-tests/src/live_tests/templates/private_details.html.j2 deleted file mode 100644 index 2505bb3d3cea..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/templates/private_details.html.j2 +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - Test Report - - - - - - - -

-

Live test private details

-
-

Context

-
-
    -
  • Tester: {{ user }}
  • -
  • Test date: {{ test_date }}
  • -
  • Connector image: {{ connector_image }}
  • -
  • Control version: {{ control_version }}
  • -
  • Target version: {{ target_version }}
  • -
-
-
-
-

Connections used for testing

- {% for connection_objects in all_connection_objects %} -
-

Details for {{ connection_objects['hashed_connection_id'] }}

-
-

Source configuration

-

The configuration object taken from the given connection that was passed to each version of the connector during the test.

-
{{ connection_objects['source_config'] }}
-
- {% endfor %} -
-
- {% if not fully_generated %} -

Requested URLs

- {% else%} -

Requested URLs

- {% endif %} -
- {% for command, flows in requested_urls_per_command.items() %} -

{{ command.value.upper() }}

- {% if flows %} -
- - - - - - - - - - {% for index, control_url, target_url in flows %} - - - - - - {% endfor %} - -
Control URLTarget URL
{{ index }}{{ control_url }}{{ target_url }}
-
- {% else %} -

No URLs requested

- {% endif %} - {% endfor%} -
-
-
- - diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/templates/report.html.j2 b/airbyte-ci/connectors/live-tests/src/live_tests/templates/report.html.j2 deleted file mode 100644 index 4ec1519a305a..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/templates/report.html.j2 +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - Test Report - - - - - - - -
-

Regression test report

-
-

Context

-
-
    -
  • Tester: {{ user }}
  • -
  • Test date: {{ test_date }}
  • -
  • Connector image: {{ connector_image }}
  • -
  • Control version: {{ control_version }}
  • -
  • Target version: {{ target_version }}
  • -
  • Private details
  • -
-
-
-
-

Coverage metadata

-
-

Stream coverage

- - - - {% for metric_name, metric_value in stream_coverage_metrics.items() %} - - - {% endfor %} - - -
{{ metric_name}}{{ metric_value }}
-

Sync mode coverage

- - - - {% for sync_mode, count in sync_mode_coverage.items() %} - - - {% endfor %} - - -
{{ sync_mode.value }}{{ count }}
- -

Selected stream

- - - - - - - - - {% for stream_name in selected_streams %} - - - - {% if selected_streams[stream_name]['has_data'] %} - - {% else %} - - {% endif %} - - {% endfor %} - -
StreamSync modeHas data
{{ stream_name }}{{ selected_streams[stream_name]['sync_mode'].value }}{{ selected_streams[stream_name]['has_data'] }}{{ selected_streams[stream_name]['has_data'] }}
- {% if untested_streams %} -

Untested streams (not in configured catalog or without data)

-
    - {% for stream_name in untested_streams %} -
  • {{ stream_name }}
  • - {% endfor %} -
- {% endif %} -
-
- {% for connection_objects in all_connection_objects %} - -
-

Connection objects - Connection {{ connection_objects.hashed_connection_id }}

- -
- {% endfor %} - -
- {% if not fully_generated %} -

Command execution metrics

- {% else%} -

Command execution metrics

- {% endif %} -
- {% if message_count_per_type[0] %} -

Message types

- - - - - {% for command in message_count_per_type[0] %} - - {% endfor %} - - - - {% for command in message_count_per_type[0] %} - - - - {% endfor %} - - {% for message_type in message_count_per_type[1] %} - - - {% for command in message_count_per_type[1][message_type] %} - - - {% if message_count_per_type[1][message_type][command]["difference"] != 0 %} - - {% else %} - - {% endif %} - {% endfor %} - - {% endfor %} -
{{ command.value.upper() }}
controltargetΔ
{{ message_type.value }}{{ message_count_per_type[1][message_type][command]["control"] }}{{ message_count_per_type[1][message_type][command]["target"] }}{{ message_count_per_type[1][message_type][command]["difference"] }}{{ message_count_per_type[1][message_type][command]["difference"] }}
- {% endif %} - {% if record_count_per_command_and_stream %} -

Record count per stream

- {% for command, record_count_per_stream in record_count_per_command_and_stream.items() %} -

{{ command.value.upper() }}

- - - - - - - - - - - {% for stream, record_count in record_count_per_stream.items() %} - - - - - {% if record_count.get("difference", 0) != 0 %} - - {% else %} - - {% endif %} - - {% endfor %} - -
streamcontrol record counttarget record countΔ
{{ stream }}{{ record_count.get("control", 0) }}{{ record_count.get("target", 0) }}{{ record_count.get("difference", 0) }}{{ record_count.get("difference", 0) }}
- {% endfor %} - {% endif %} - {% if http_metrics_per_command %} -

HTTP traffic

- - - - - - - - - - - - - - - - - - - {% for command in http_metrics_per_command %} - - - - - - - - {% if http_metrics_per_command[command].get("difference", 0) != 0 %} - - {% else %} - - {% endif %} - - {% endfor %} - -
controltargetΔ
commandrequest countduplicate request countrequest countduplicate request countcache hit ratiorequest count
{{ command.value.upper() }}{{ http_metrics_per_command[command].get("control", {}).get("flow_count", "0")}}{{ http_metrics_per_command[command].get("control", {}).get("duplicate_flow_count", "0")}}{{ http_metrics_per_command[command].get("target", {}).get("flow_count", "0")}}{{ http_metrics_per_command[command].get("target", {}).get("duplicate_flow_count", "0")}}{{ http_metrics_per_command[command].get("target", {}).get("cache_hit_ratio", "0%")}}{{ http_metrics_per_command[command].get("difference", 0)}}{{ http_metrics_per_command[command].get("difference", 0)}}
- {% endif %} -
-
- -
- {% if not fully_generated %} -

Test results

- {% else%} -

Test results

- {% endif %} -
- {% for test in test_results %} -
- {% if test["result"] == "passed" %} - {% if test["output"] %} -

{{ test["name"] }} [{{ test["result"] + " with errors" }}]

- {% else %} -

{{ test["name"] }} [{{ test["result"] }}]

- {% endif %} - {% elif test["result"] == "failed" %} -

{{ test["name"] }} [{{ test["result"] }}]

- {% else %} -

{{ test["name"] }} [{{ test["result"] }}]

- {% endif %} - {% if test["documentation"] %} -

{{ test["documentation"] }}

- {% endif %} - {% if test["output"] %} -
-                    {{ test["output"] }}
-                    
- {% endif %} - {% if test["properties"]%} - {% for property_name, property_value in test["properties"] %} - {% if property_value %} -

{{ property_name }}

-
-                        {{ property_value }}
-                        
- {% else%} - - {% endif %} - {% endfor%} - {% endif %} -
- {% endfor%} -
-
-
- - diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/utils.py b/airbyte-ci/connectors/live-tests/src/live_tests/utils.py deleted file mode 100644 index 4deafdbd8b97..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/utils.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -from __future__ import annotations - -import json -import logging -from collections.abc import Callable, Iterable -from pathlib import Path -from typing import TYPE_CHECKING, Optional, Union - -import pytest -from airbyte_protocol.models import AirbyteCatalog, AirbyteMessage, ConnectorSpecification, Status, Type # type: ignore -from deepdiff import DeepDiff # type: ignore - -from live_tests import stash_keys -from live_tests.commons.models import ExecutionResult -from live_tests.consts import MAX_LINES_IN_REPORT - -if TYPE_CHECKING: - from _pytest.fixtures import SubRequest - -MAX_DIFF_SIZE_FOR_LOGGING = 500 - - -def get_test_logger(request: SubRequest) -> logging.Logger: - return logging.getLogger(request.node.name) - - -def filter_records(messages: Iterable[AirbyteMessage]) -> Iterable[AirbyteMessage]: - for message in messages: - if message.type is Type.RECORD: - yield message - - -def write_string_to_test_artifact(request: SubRequest, content: str, filename: str, subdir: Optional[Path] = None) -> Path: - # StashKey (in this case TEST_ARTIFACT_DIRECTORY) defines the output class of this, - # so this is already a Path. - test_artifact_directory = request.config.stash[stash_keys.TEST_ARTIFACT_DIRECTORY] - if subdir: - test_artifact_directory = test_artifact_directory / subdir - test_artifact_directory.mkdir(parents=True, exist_ok=True) - artifact_path = test_artifact_directory / filename - artifact_path.write_text(content) - return artifact_path - - -def get_and_write_diff( - request: SubRequest, - control_data: Union[list, dict], - target_data: Union[list, dict], - filepath: str, - ignore_order: bool, - exclude_paths: Optional[list[str]], -) -> str: - logger = get_test_logger(request) - diff = DeepDiff( - control_data, - target_data, - ignore_order=ignore_order, - report_repetition=True, - exclude_regex_paths=exclude_paths, - ) - if diff: - diff_json = diff.to_json() - parsed_diff = json.loads(diff_json) - formatted_diff_json = json.dumps(parsed_diff, indent=2) - - diff_path_tree = write_string_to_test_artifact(request, str(diff.tree), f"{filepath}_tree.txt", subdir=request.node.name) - diff_path_text = write_string_to_test_artifact( - request, - formatted_diff_json, - f"{filepath}_text.txt", - subdir=Path(request.node.name), - ) - diff_path_pretty = write_string_to_test_artifact( - request, - str(diff.pretty()), - f"{filepath}_pretty.txt", - subdir=Path(request.node.name), - ) - - logger.info(f"Diff file are stored in {diff_path_tree}, {diff_path_text}, and {diff_path_pretty}.") - if len(diff_json.encode("utf-8")) < MAX_DIFF_SIZE_FOR_LOGGING: - logger.error(formatted_diff_json) - - return formatted_diff_json - return "" - - -def fail_test_on_failing_execution_results(record_property: Callable, execution_results: list[ExecutionResult]) -> None: - error_messages = [] - for execution_result in execution_results: - if not execution_result.success: - property_suffix = f"of failing execution {execution_result.command.value} on {execution_result.connector_under_test.name}:{execution_result.connector_under_test.version} [{MAX_LINES_IN_REPORT} last lines]" - record_property( - f"Stdout {property_suffix}", - tail_file(execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT), - ) - record_property( - f"Stderr of {property_suffix}", - tail_file(execution_result.stderr_file_path, n=MAX_LINES_IN_REPORT), - ) - error_messages.append( - f"Failed executing command {execution_result.command} on {execution_result.connector_under_test.name}:{execution_result.connector_under_test.version}" - ) - if error_messages: - pytest.fail("\n".join(error_messages)) - - -def tail_file(file_path: Path, n: int = MAX_LINES_IN_REPORT) -> list[str]: - with open(file_path) as f: - # Move the cursor to the end of the file - f.seek(0, 2) - file_size = f.tell() - lines: list[str] = [] - read_size = min(4096, file_size) - cursor = file_size - read_size - - # Read chunks of the file until we've found n lines - while len(lines) < n and cursor >= 0: - f.seek(cursor) - chunk = f.read(read_size) - lines.extend(chunk.splitlines(True)[-n:]) - cursor -= read_size - - # Return the last n lines - return lines[-n:] - - -def is_successful_check(execution_result: ExecutionResult) -> bool: - for message in execution_result.airbyte_messages: - if message.type is Type.CONNECTION_STATUS and message.connectionStatus and message.connectionStatus.status is Status.SUCCEEDED: - return True - return False - - -def get_catalog(execution_result: ExecutionResult) -> AirbyteCatalog: - catalog = [m.catalog for m in execution_result.airbyte_messages if m.type is Type.CATALOG and m.catalog] - try: - return catalog[0] - except ValueError: - raise ValueError(f"Expected exactly one catalog in the execution result, but got {len(catalog)}.") - - -def get_spec(execution_result: ExecutionResult) -> ConnectorSpecification: - spec = [m.spec for m in execution_result.airbyte_messages if m.type is Type.SPEC] - try: - return spec[0] - except ValueError: - raise ValueError(f"Expected exactly one spec in the execution result, but got {len(spec)}.") - - -def find_all_values_for_key_in_schema(schema: dict, searched_key: str): - """Retrieve all (nested) values in a schema for a specific searched key""" - if isinstance(schema, list): - for schema_item in schema: - yield from find_all_values_for_key_in_schema(schema_item, searched_key) - if isinstance(schema, dict): - for key, value in schema.items(): - if key == searched_key: - yield value - if isinstance(value, dict) or isinstance(value, list): - yield from find_all_values_for_key_in_schema(value, searched_key) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/__init__.py b/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_check.py b/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_check.py deleted file mode 100644 index ac945b830ffb..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_check.py +++ /dev/null @@ -1,45 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from typing import Callable - -import pytest -from airbyte_protocol.models import Type - -from live_tests.commons.models import ExecutionResult -from live_tests.consts import MAX_LINES_IN_REPORT -from live_tests.utils import fail_test_on_failing_execution_results, is_successful_check, tail_file - -pytestmark = [ - pytest.mark.anyio, -] - - -@pytest.mark.allow_diagnostic_mode -async def test_check_succeeds( - record_property: Callable, - check_target_execution_result: ExecutionResult, -) -> None: - """ - Verify that the check command succeeds on the target connection. - - Success is determined by the presence of a connection status message with a status of SUCCEEDED. - """ - fail_test_on_failing_execution_results( - record_property, - [check_target_execution_result], - ) - assert len([msg for msg in check_target_execution_result.airbyte_messages if msg.type == Type.CONNECTION_STATUS]) == 1 - - successful_target_check: bool = is_successful_check(check_target_execution_result) - error_messages = [] - if not successful_target_check: - record_property( - f"Target CHECK standard output [Last {MAX_LINES_IN_REPORT} lines]", - tail_file(check_target_execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT), - ) - error_messages.append("The target check did not succeed. Check the test artifacts for more information.") - if error_messages: - pytest.fail("\n".join(error_messages)) diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_discover.py b/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_discover.py deleted file mode 100644 index 0ac95f3fc82e..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_discover.py +++ /dev/null @@ -1,172 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from typing import Callable, List, Union - -import dpath.util -import jsonschema -import pytest -from airbyte_protocol.models import AirbyteCatalog - -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results, find_all_values_for_key_in_schema, get_catalog - -pytestmark = [ - pytest.mark.anyio, -] - - -@pytest.fixture(scope="session") -def target_discovered_catalog(discover_target_execution_result: ExecutionResult) -> AirbyteCatalog: - return get_catalog(discover_target_execution_result) - - -@pytest.mark.allow_diagnostic_mode -async def test_discover( - record_property: Callable, - discover_target_execution_result: ExecutionResult, - target_discovered_catalog: AirbyteCatalog, -): - """ - Verify that the discover command succeeds on the target connection. - - Success is determined by the presence of a catalog with one or more streams, all with unique names. - """ - fail_test_on_failing_execution_results( - record_property, - [discover_target_execution_result], - ) - duplicated_stream_names = _duplicated_stream_names(target_discovered_catalog.streams) - - assert target_discovered_catalog is not None, "Message should have catalog" - assert hasattr(target_discovered_catalog, "streams") and target_discovered_catalog.streams, "Catalog should contain streams" - assert len(duplicated_stream_names) == 0, f"Catalog should have uniquely named streams, duplicates are: {duplicated_stream_names}" - - -def _duplicated_stream_names(streams) -> List[str]: - """Counts number of times a stream appears in the catalog""" - name_counts = dict() - for stream in streams: - count = name_counts.get(stream.name, 0) - name_counts[stream.name] = count + 1 - return [k for k, v in name_counts.items() if v > 1] - - -@pytest.mark.allow_diagnostic_mode -async def test_streams_have_valid_json_schemas(target_discovered_catalog: AirbyteCatalog): - """Check if all stream schemas are valid json schemas.""" - for stream in target_discovered_catalog.streams: - jsonschema.Draft7Validator.check_schema(stream.json_schema) - - -@pytest.mark.allow_diagnostic_mode -async def test_defined_cursors_exist_in_schema(target_discovered_catalog: AirbyteCatalog): - """Check if all of the source defined cursor fields exist on stream's json schema.""" - for stream in target_discovered_catalog.streams: - if not stream.default_cursor_field: - continue - schema = stream.json_schema - assert "properties" in schema, f"Top level item should have an 'object' type for {stream.name} stream schema" - cursor_path = "/properties/".join(stream.default_cursor_field) - cursor_field_location = dpath.util.search(schema["properties"], cursor_path) - assert cursor_field_location, ( - f"Some of defined cursor fields {stream.default_cursor_field} are not specified in discover schema " - f"properties for {stream.name} stream" - ) - - -@pytest.mark.allow_diagnostic_mode -async def test_defined_refs_exist_in_schema(target_discovered_catalog: AirbyteCatalog): - """Check the presence of unresolved `$ref`s values within each json schema.""" - schemas_errors = [] - for stream in target_discovered_catalog.streams: - check_result = list(find_all_values_for_key_in_schema(stream.json_schema, "$ref")) - if check_result: - schemas_errors.append({stream.name: check_result}) - - assert not schemas_errors, f"Found unresolved `$refs` values for selected streams: {tuple(schemas_errors)}." - - -@pytest.mark.allow_diagnostic_mode -@pytest.mark.parametrize("keyword", ["allOf", "not"]) -async def test_defined_keyword_exist_in_schema(keyword, target_discovered_catalog: AirbyteCatalog): - """Check for the presence of not allowed keywords within each json schema""" - schemas_errors = [] - for stream in target_discovered_catalog.streams: - check_result = _find_keyword_schema(stream.json_schema, key=keyword) - if check_result: - schemas_errors.append(stream.name) - - assert not schemas_errors, f"Found not allowed `{keyword}` keyword for selected streams: {schemas_errors}." - - -def _find_keyword_schema(schema: Union[dict, list, str], key: str) -> bool: - """Find at least one keyword in a schema, skip object properties""" - - def _find_keyword(schema, key, _skip=False): - if isinstance(schema, list): - for v in schema: - _find_keyword(v, key) - elif isinstance(schema, dict): - for k, v in schema.items(): - if k == key and not _skip: - raise StopIteration - rec_skip = k == "properties" and schema.get("type") == "object" - _find_keyword(v, key, rec_skip) - - try: - _find_keyword(schema, key) - except StopIteration: - return True - return False - - -@pytest.mark.allow_diagnostic_mode -async def test_primary_keys_exist_in_schema(target_discovered_catalog: AirbyteCatalog): - """Check that all primary keys are present in catalog.""" - for stream in target_discovered_catalog.streams: - for pk in stream.source_defined_primary_key or []: - schema = stream.json_schema - pk_path = "/properties/".join(pk) - pk_field_location = dpath.util.search(schema["properties"], pk_path) - assert pk_field_location, f"One of the PKs ({pk}) is not specified in discover schema for {stream.name} stream" - - -@pytest.mark.allow_diagnostic_mode -async def test_streams_has_sync_modes(target_discovered_catalog: AirbyteCatalog): - """Check that the supported_sync_modes is a not empty field in streams of the catalog.""" - for stream in target_discovered_catalog.streams: - assert stream.supported_sync_modes is not None, f"The stream {stream.name} is missing supported_sync_modes field declaration." - assert len(stream.supported_sync_modes) > 0, f"supported_sync_modes list on stream {stream.name} should not be empty." - - -@pytest.mark.allow_diagnostic_mode -async def test_additional_properties_is_true(target_discovered_catalog: AirbyteCatalog): - """ - Check that value of the "additionalProperties" field is always true. - - A stream schema declaring "additionalProperties": false introduces the risk of accidental breaking changes. - Specifically, when removing a property from the stream schema, existing connector catalog will no longer be valid. - False value introduces the risk of accidental breaking changes. - - Read https://github.com/airbytehq/airbyte/issues/14196 for more details. - """ - for stream in target_discovered_catalog.streams: - additional_properties_values = list(find_all_values_for_key_in_schema(stream.json_schema, "additionalProperties")) - if additional_properties_values: - assert all( - [additional_properties_value is True for additional_properties_value in additional_properties_values] - ), "When set, additionalProperties field value must be true for backward compatibility." - - -@pytest.mark.allow_diagnostic_mode -@pytest.mark.skip("This a placeholder for a CAT which has too many failures. We need to fix the connectors at scale first.") -async def test_catalog_has_supported_data_types(target_discovered_catalog: AirbyteCatalog): - """ - Check that all streams have supported data types, format and airbyte_types. - - Supported data types are listed there: https://docs.airbyte.com/understanding-airbyte/supported-data-types/ - """ - pass diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_read.py b/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_read.py deleted file mode 100644 index 76b6063b82d2..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_read.py +++ /dev/null @@ -1,143 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from collections import defaultdict -from functools import reduce -from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Tuple - -import pytest -from airbyte_protocol.models import ( - AirbyteStateMessage, - AirbyteStateStats, - AirbyteStateType, - AirbyteStreamStatus, - AirbyteStreamStatusTraceMessage, - ConfiguredAirbyteCatalog, -) - -from live_tests.commons.json_schema_helper import conforms_to_schema -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results, get_test_logger - -if TYPE_CHECKING: - from _pytest.fixtures import SubRequest - -pytestmark = [ - pytest.mark.anyio, -] - - -@pytest.mark.allow_diagnostic_mode -async def test_read( - request: "SubRequest", - record_property: Callable, - read_target_execution_result: ExecutionResult, -): - """ - Verify that the read command succeeds on the target connection. - - Also makes assertions about the validity of the read command output: - - At least one state message is emitted per stream - - Appropriate stream status messages are emitted for each stream - - If a primary key exists for the stream, it is present in the records emitted - """ - has_records = False - errors = [] - warnings = [] - fail_test_on_failing_execution_results( - record_property, - [read_target_execution_result], - ) - for stream in read_target_execution_result.configured_catalog.streams: - records = read_target_execution_result.get_records_per_stream(stream.stream.name) - state_messages = read_target_execution_result.get_states_per_stream(stream.stream.name) - statuses = read_target_execution_result.get_status_messages_per_stream(stream.stream.name) - primary_key = read_target_execution_result.primary_keys_per_stream.get(stream.stream.name) - - for record in records: - has_records = True - if not conforms_to_schema(read_target_execution_result.get_obfuscated_types(record.record.data), stream.schema()): - errors.append(f"A record was encountered that does not conform to the schema. stream={stream.stream.name} record={record}") - if primary_key: - if _extract_primary_key_value(record.dict(), primary_key) is None: - errors.append( - f"Primary key subkeys {repr(primary_key)} have null values or not present in {stream.stream.name} stream records." - ) - if stream.stream.name not in state_messages: - errors.append( - f"At least one state message should be emitted per stream, but no state messages were emitted for {stream.stream.name}." - ) - try: - _validate_state_messages( - state_messages=state_messages[stream.stream.name], configured_catalog=read_target_execution_result.configured_catalog - ) - except AssertionError as exc: - warnings.append( - f"Invalid state message for stream {stream.stream.name}. exc={exc} state_messages={state_messages[stream.stream.name]}" - ) - if stream.stream.name not in statuses: - warnings.append(f"No stream statuses were emitted for stream {stream.stream.name}.") - if not _validate_stream_statuses( - configured_catalog=read_target_execution_result.configured_catalog, statuses=statuses[stream.stream.name] - ): - errors.append(f"Invalid statuses for stream {stream.stream.name}. statuses={statuses[stream.stream.name]}") - if not has_records: - errors.append("At least one record should be read using provided catalog.") - - if errors: - logger = get_test_logger(request) - for error in errors: - logger.info(error) - - -def _extract_primary_key_value(record: Mapping[str, Any], primary_key: List[List[str]]) -> dict[Tuple[str], Any]: - pk_values = {} - for pk_path in primary_key: - pk_value: Any = reduce(lambda data, key: data.get(key) if isinstance(data, dict) else None, pk_path, record) - pk_values[tuple(pk_path)] = pk_value - return pk_values - - -def _validate_stream_statuses(configured_catalog: ConfiguredAirbyteCatalog, statuses: List[AirbyteStreamStatusTraceMessage]): - """Validate all statuses for all streams in the catalogs were emitted in correct order: - 1. STARTED - 2. RUNNING (can be >1) - 3. COMPLETE - """ - stream_statuses = defaultdict(list) - for status in statuses: - stream_statuses[f"{status.stream_descriptor.namespace}-{status.stream_descriptor.name}"].append(status.status) - - assert set(f"{x.stream.namespace}-{x.stream.name}" for x in configured_catalog.streams) == set( - stream_statuses - ), "All stream must emit status" - - for stream_name, status_list in stream_statuses.items(): - assert ( - len(status_list) >= 3 - ), f"Stream `{stream_name}` statuses should be emitted in the next order: `STARTED`, `RUNNING`,... `COMPLETE`" - assert status_list[0] == AirbyteStreamStatus.STARTED - assert status_list[-1] == AirbyteStreamStatus.COMPLETE - assert all(x == AirbyteStreamStatus.RUNNING for x in status_list[1:-1]) - - -def _validate_state_messages(state_messages: List[AirbyteStateMessage], configured_catalog: ConfiguredAirbyteCatalog): - # Ensure that at least one state message is emitted for each stream - assert len(state_messages) >= len( - configured_catalog.streams - ), "At least one state message should be emitted for each configured stream." - - for state_message in state_messages: - stream_name = state_message.stream.stream_descriptor.name - state_type = state_message.type - - # Ensure legacy state type is not emitted anymore - assert state_type != AirbyteStateType.LEGACY, ( - f"Ensure that statuses from the {stream_name} stream are emitted using either " - "`STREAM` or `GLOBAL` state types, as the `LEGACY` state type is now deprecated." - ) - - # Check if stats are of the correct type and present in state message - assert isinstance(state_message.sourceStats, AirbyteStateStats), "Source stats should be in state message." diff --git a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_spec.py b/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_spec.py deleted file mode 100644 index f5d3c2238d41..000000000000 --- a/airbyte-ci/connectors/live-tests/src/live_tests/validation_tests/test_spec.py +++ /dev/null @@ -1,514 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Set, Tuple - -import dpath.util -import jsonschema -import pytest -from airbyte_protocol.models import ConnectorSpecification - -from live_tests.commons.json_schema_helper import JsonSchemaHelper, get_expected_schema_structure, get_paths_in_connector_config -from live_tests.commons.models import ExecutionResult -from live_tests.utils import fail_test_on_failing_execution_results, find_all_values_for_key_in_schema, get_spec, get_test_logger - -pytestmark = [ - pytest.mark.anyio, -] - -if TYPE_CHECKING: - from _pytest.fixtures import SubRequest - - -@pytest.fixture(name="secret_property_names") -def secret_property_names_fixture(): - return ( - "client_token", - "access_token", - "api_token", - "token", - "secret", - "client_secret", - "password", - "key", - "service_account_info", - "service_account", - "tenant_id", - "certificate", - "jwt", - "credentials", - "app_id", - "appid", - "refresh_token", - ) - - -DATE_PATTERN = "^[0-9]{2}-[0-9]{2}-[0-9]{4}$" -DATETIME_PATTERN = "^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2}:[0-9]{2}:[0-9]{2})?$" - - -@pytest.fixture -def target_spec(spec_target_execution_result: ExecutionResult) -> ConnectorSpecification: - return get_spec(spec_target_execution_result) - - -@pytest.fixture -def connector_config(spec_target_execution_result: ExecutionResult) -> Dict[str, any]: - return spec_target_execution_result.config - - -async def test_spec( - record_property: Callable, - spec_target_execution_result: ExecutionResult, -): - """Check that the spec call succeeds""" - fail_test_on_failing_execution_results(record_property, [spec_target_execution_result]) - - -@pytest.mark.allow_diagnostic_mode -async def test_config_match_spec( - target_spec: ConnectorSpecification, - connector_config: Dict[str, any], -): - """Check that config matches the actual schema from the spec call""" - # Getting rid of technical variables that start with an underscore - config = {key: value for key, value in connector_config.data.items() if not key.startswith("_")} - try: - jsonschema.validate(instance=config, schema=target_spec.connectionSpecification) - except jsonschema.exceptions.ValidationError as err: - pytest.fail(f"Config invalid: {err}") - except jsonschema.exceptions.SchemaError as err: - pytest.fail(f"Spec is invalid: {err}") - - -async def test_enum_usage(target_spec: ConnectorSpecification): - """Check that enum lists in specs contain distinct values.""" - docs_url = "https://docs.airbyte.io/connector-development/connector-specification-reference" - docs_msg = f"See specification reference at {docs_url}." - - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - enum_paths = schema_helper.find_nodes(keys=["enum"]) - - for path in enum_paths: - enum_list = schema_helper.get_node(path) - assert len(set(enum_list)) == len( - enum_list - ), f"Enum lists should not contain duplicate values. Misconfigured enum array: {enum_list}. {docs_msg}" - - -async def test_oneof_usage(target_spec: ConnectorSpecification): - """Check that if spec contains oneOf it follows the rules according to reference - https://docs.airbyte.io/connector-development/connector-specification-reference - """ - docs_url = "https://docs.airbyte.io/connector-development/connector-specification-reference" - docs_msg = f"See specification reference at {docs_url}." - - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - variant_paths = schema_helper.find_nodes(keys=["oneOf", "anyOf"]) - - for variant_path in variant_paths: - top_level_obj = schema_helper.get_node(variant_path[:-1]) - assert ( - top_level_obj.get("type") == "object" - ), f"The top-level definition in a `oneOf` block should have type: object. misconfigured object: {top_level_obj}. {docs_msg}" - - variants = schema_helper.get_node(variant_path) - for variant in variants: - assert "properties" in variant, f"Each item in the oneOf array should be a property with type object. {docs_msg}" - - oneof_path = ".".join(map(str, variant_path)) - variant_props = [set(v["properties"].keys()) for v in variants] - common_props = set.intersection(*variant_props) - assert common_props, f"There should be at least one common property for {oneof_path} subobjects. {docs_msg}" - - const_common_props = set() - enum_common_props = set() - for common_prop in common_props: - if all(["const" in variant["properties"][common_prop] for variant in variants]): - const_common_props.add(common_prop) - if all(["enum" in variant["properties"][common_prop] for variant in variants]): - enum_common_props.add(common_prop) - assert len(const_common_props) == 1 or ( - len(const_common_props) == 0 and len(enum_common_props) == 1 - ), f"There should be exactly one common property with 'const' keyword (or equivalent) for {oneof_path} subobjects. {docs_msg}" - - const_common_prop = const_common_props.pop() if const_common_props else enum_common_props.pop() - for n, variant in enumerate(variants): - prop_obj = variant["properties"][const_common_prop] - prop_info = f"common property {oneof_path}[{n}].{const_common_prop}. It's recommended to just use `const`." - if "const" in prop_obj: - const_value = prop_obj["const"] - assert ( - "default" not in prop_obj or prop_obj["default"] == const_value - ), f"'default' needs to be identical to 'const' in {prop_info}. {docs_msg}" - assert "enum" not in prop_obj or prop_obj["enum"] == [ - const_value - ], f"'enum' needs to be an array with a single item identical to 'const' in {prop_info}. {docs_msg}" - else: - assert ( - "enum" in prop_obj and "default" in prop_obj and prop_obj["enum"] == [prop_obj["default"]] - ), f"'enum' needs to be an array with a single item identical to 'default' in {prop_info}. {docs_msg}" - - -def _is_spec_property_name_secret(path: str, secret_property_names) -> Tuple[Optional[str], bool]: - """ - Given a path to a type field, extract a field name and decide whether it is a name of secret or not - based on a provided list of secret names. - Split the path by `/`, drop the last item and make list reversed. - Then iterate over it and find the first item that's not a reserved keyword or an index. - Example: - properties/credentials/oneOf/1/properties/api_key/type -> [api_key, properties, 1, oneOf, credentials, properties] -> api_key - """ - reserved_keywords = ("anyOf", "oneOf", "allOf", "not", "properties", "items", "type", "prefixItems") - for part in reversed(path.split("/")[:-1]): - if part.isdigit() or part in reserved_keywords: - continue - return part, part.lower() in secret_property_names - return None, False - - -def _property_can_store_secret(prop: dict) -> bool: - """ - Some fields can not hold a secret by design, others can. - Null type as well as boolean can not hold a secret value. - A string, a number or an integer type can always store secrets. - Secret objects and arrays can not be rendered correctly in the UI: - A field with a constant value can not hold a secret as well. - """ - unsecure_types = {"string", "integer", "number"} - type_ = prop["type"] - is_property_constant_value = bool(prop.get("const")) - can_store_secret = any( - [ - isinstance(type_, str) and type_ in unsecure_types, - isinstance(type_, list) and (set(type_) & unsecure_types), - ] - ) - if not can_store_secret: - return False - # if a property can store a secret, additional check should be done if it's a constant value - return not is_property_constant_value - - -async def test_secret_is_properly_marked(target_spec: ConnectorSpecification, secret_property_names): - """ - Each field has a type, therefore we can make a flat list of fields from the returned specification. - Iterate over the list, check if a field name is a secret name, can potentially hold a secret value - and make sure it is marked as `airbyte_secret`. - """ - secrets_exposed = [] - non_secrets_hidden = [] - spec_properties = target_spec.connectionSpecification["properties"] - for type_path, type_value in dpath.util.search(spec_properties, "**/type", yielded=True): - _, is_property_name_secret = _is_spec_property_name_secret(type_path, secret_property_names) - if not is_property_name_secret: - continue - absolute_path = f"/{type_path}" - property_path, _ = absolute_path.rsplit(sep="/", maxsplit=1) - property_definition = dpath.util.get(spec_properties, property_path) - marked_as_secret = property_definition.get("airbyte_secret", False) - possibly_a_secret = _property_can_store_secret(property_definition) - if marked_as_secret and not possibly_a_secret: - non_secrets_hidden.append(property_path) - if not marked_as_secret and possibly_a_secret: - secrets_exposed.append(property_path) - - if non_secrets_hidden: - properties = "\n".join(non_secrets_hidden) - pytest.fail( - f"""Some properties are marked with `airbyte_secret` although they probably should not be. - Please double check them. If they're okay, please fix this test. - {properties}""" - ) - if secrets_exposed: - properties = "\n".join(secrets_exposed) - pytest.fail( - f"""The following properties should be marked with `airbyte_secret!` - {properties}""" - ) - - -def _fail_on_errors(errors: List[str]): - if len(errors) > 0: - pytest.fail("\n".join(errors)) - - -def test_property_type_is_not_array(target_spec: ConnectorSpecification): - """ - Each field has one or multiple types, but the UI only supports a single type and optionally "null" as a second type. - """ - errors = [] - for type_path, type_value in dpath.util.search(target_spec.connectionSpecification, "**/properties/*/type", yielded=True): - if isinstance(type_value, List): - number_of_types = len(type_value) - if number_of_types != 2 and number_of_types != 1: - errors.append( - f"{type_path} is not either a simple type or an array of a simple type plus null: {type_value} (for example: type: [string, null])" - ) - if number_of_types == 2 and type_value[1] != "null": - errors.append( - f"Second type of {type_path} is not null: {type_value}. Type can either be a simple type or an array of a simple type plus null (for example: type: [string, null])" - ) - _fail_on_errors(errors) - - -def test_object_not_empty(target_spec: ConnectorSpecification): - """ - Each object field needs to have at least one property as the UI won't be able to show them otherwise. - If the whole spec is empty, it's allowed to have a single empty object at the top level - """ - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - errors = [] - for type_path, type_value in dpath.util.search(target_spec.connectionSpecification, "**/type", yielded=True): - if type_path == "type": - # allow empty root object - continue - if type_value == "object": - property = schema_helper.get_parent(type_path) - if "oneOf" not in property and ("properties" not in property or len(property["properties"]) == 0): - errors.append( - f"{type_path} is an empty object which will not be represented correctly in the UI. Either remove or add specific properties" - ) - _fail_on_errors(errors) - - -async def test_array_type(target_spec: ConnectorSpecification): - """ - Each array has one or multiple types for its items, but the UI only supports a single type which can either be object, string or an enum - """ - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - errors = [] - for type_path, type_type in dpath.util.search(target_spec.connectionSpecification, "**/type", yielded=True): - property_definition = schema_helper.get_parent(type_path) - if type_type != "array": - # unrelated "items", not an array definition - continue - items_value = property_definition.get("items", None) - if items_value is None: - continue - elif isinstance(items_value, List): - errors.append(f"{type_path} is not just a single item type: {items_value}") - elif items_value.get("type") not in ["object", "string", "number", "integer"] and "enum" not in items_value: - errors.append(f"Items of {type_path} has to be either object or string or define an enum") - _fail_on_errors(errors) - - -async def test_forbidden_complex_types(target_spec: ConnectorSpecification): - """ - not, anyOf, patternProperties, prefixItems, allOf, if, then, else, dependentSchemas and dependentRequired are not allowed - """ - forbidden_keys = [ - "not", - "anyOf", - "patternProperties", - "prefixItems", - "allOf", - "if", - "then", - "else", - "dependentSchemas", - "dependentRequired", - ] - found_keys = set() - for forbidden_key in forbidden_keys: - for path, value in dpath.util.search(target_spec.connectionSpecification, f"**/{forbidden_key}", yielded=True): - found_keys.add(path) - - for forbidden_key in forbidden_keys: - # remove forbidden keys if they are used as properties directly - for path, _value in dpath.util.search(target_spec.connectionSpecification, f"**/properties/{forbidden_key}", yielded=True): - found_keys.remove(path) - - if len(found_keys) > 0: - key_list = ", ".join(found_keys) - pytest.fail(f"Found the following disallowed JSON schema features: {key_list}") - - -async def test_date_pattern(request: "SubRequest", target_spec: ConnectorSpecification): - """ - Properties with format date or date-time should always have a pattern defined how the date/date-time should be formatted - that corresponds with the format the datepicker component is creating. - """ - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - for format_path, format in dpath.util.search(target_spec.connectionSpecification, "**/format", yielded=True): - if not isinstance(format, str): - # format is not a format definition here but a property named format - continue - property_definition = schema_helper.get_parent(format_path) - pattern = property_definition.get("pattern") - logger = get_test_logger(request) - if format == "date" and not pattern == DATE_PATTERN: - logger.warning( - f"{format_path} is defining a date format without the corresponding pattern. Consider setting the pattern to {DATE_PATTERN} to make it easier for users to edit this field in the UI." - ) - if format == "date-time" and not pattern == DATETIME_PATTERN: - logger.warning( - f"{format_path} is defining a date-time format without the corresponding pattern Consider setting the pattern to {DATETIME_PATTERN} to make it easier for users to edit this field in the UI." - ) - - -async def test_date_format(request: "SubRequest", target_spec: ConnectorSpecification): - """ - Properties with a pattern that looks like a date should have their format set to date or date-time. - """ - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - for pattern_path, pattern in dpath.util.search(target_spec.connectionSpecification, "**/pattern", yielded=True): - if not isinstance(pattern, str): - # pattern is not a pattern definition here but a property named pattern - continue - if pattern == DATE_PATTERN or pattern == DATETIME_PATTERN: - property_definition = schema_helper.get_parent(pattern_path) - format = property_definition.get("format") - logger = get_test_logger(request) - if not format == "date" and pattern == DATE_PATTERN: - logger.warning( - f"{pattern_path} is defining a pattern that looks like a date without setting the format to `date`. Consider specifying the format to make it easier for users to edit this field in the UI." - ) - if not format == "date-time" and pattern == DATETIME_PATTERN: - logger.warning( - f"{pattern_path} is defining a pattern that looks like a date-time without setting the format to `date-time`. Consider specifying the format to make it easier for users to edit this field in the UI." - ) - - -async def test_duplicate_order(target_spec: ConnectorSpecification): - """ - Custom ordering of field (via the "order" property defined in the field) is not allowed to have duplicates within the same group. - `{ "a": { "order": 1 }, "b": { "order": 1 } }` is invalid because there are two fields with order 1 - `{ "a": { "order": 1 }, "b": { "order": 1, "group": "x" } }` is valid because the fields with the same order are in different groups - """ - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - errors = [] - for properties_path, properties in dpath.util.search(target_spec.connectionSpecification, "**/properties", yielded=True): - definition = schema_helper.get_parent(properties_path) - if definition.get("type") != "object": - # unrelated "properties", not an actual object definition - continue - used_orders: Dict[str, Set[int]] = {} - for property in properties.values(): - if "order" not in property: - continue - order = property.get("order") - group = property.get("group", "") - if group not in used_orders: - used_orders[group] = set() - orders_for_group = used_orders[group] - if order in orders_for_group: - errors.append(f"{properties_path} has duplicate order: {order}") - orders_for_group.add(order) - _fail_on_errors(errors) - - -async def test_nested_group(target_spec: ConnectorSpecification): - """ - Groups can only be defined on the top level properties - `{ "a": { "group": "x" }}` is valid because field "a" is a top level field - `{ "a": { "oneOf": [{ "type": "object", "properties": { "b": { "group": "x" } } }] }}` is invalid because field "b" is nested in a oneOf - """ - errors = [] - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - for result in dpath.util.search(target_spec.connectionSpecification, "/properties/**/group", yielded=True): - group_path = result[0] - parent_path = schema_helper.get_parent_path(group_path) - is_property_named_group = parent_path.endswith("properties") - grandparent_path = schema_helper.get_parent_path(parent_path) - if grandparent_path != "/properties" and not is_property_named_group: - errors.append(f"Groups can only be defined on top level, is defined at {group_path}") - _fail_on_errors(errors) - - -async def test_display_type(target_spec: ConnectorSpecification): - """ - The display_type property can only be set on fields which have a oneOf property, and must be either "dropdown" or "radio" - """ - errors = [] - schema_helper = JsonSchemaHelper(target_spec.connectionSpecification) - for result in dpath.util.search(target_spec.connectionSpecification, "/properties/**/display_type", yielded=True): - display_type_path = result[0] - parent_path = schema_helper.get_parent_path(display_type_path) - is_property_named_display_type = parent_path.endswith("properties") - if is_property_named_display_type: - continue - parent_object = schema_helper.get_parent(display_type_path) - if "oneOf" not in parent_object: - errors.append(f"display_type is only allowed on fields which have a oneOf property, but is set on {parent_path}") - display_type_value = parent_object.get("display_type") - if display_type_value != "dropdown" and display_type_value != "radio": - errors.append(f"display_type must be either 'dropdown' or 'radio', but is set to '{display_type_value}' at {display_type_path}") - _fail_on_errors(errors) - - -async def test_defined_refs_exist_in_json_spec_file(target_spec: ConnectorSpecification): - """Checking for the presence of unresolved `$ref`s values within each json spec file""" - check_result = list(find_all_values_for_key_in_schema(target_spec.connectionSpecification["properties"], "$ref")) - assert not check_result, "Found unresolved `$refs` value in spec.json file" - - -async def test_oauth_flow_parameters(target_spec: ConnectorSpecification): - """Check if connector has correct oauth flow parameters according to - https://docs.airbyte.io/connector-development/connector-specification-reference - """ - advanced_auth = target_spec.advanced_auth - if not advanced_auth: - return - spec_schema = target_spec.connectionSpecification - paths_to_validate = set() - if advanced_auth.predicate_key: - paths_to_validate.add("/" + "/".join(advanced_auth.predicate_key)) - oauth_config_specification = advanced_auth.oauth_config_specification - if oauth_config_specification: - if oauth_config_specification.oauth_user_input_from_connector_config_specification: - paths_to_validate.update( - get_paths_in_connector_config(oauth_config_specification.oauth_user_input_from_connector_config_specification["properties"]) - ) - if oauth_config_specification.complete_oauth_output_specification: - paths_to_validate.update( - get_paths_in_connector_config(oauth_config_specification.complete_oauth_output_specification["properties"]) - ) - if oauth_config_specification.complete_oauth_server_output_specification: - paths_to_validate.update( - get_paths_in_connector_config(oauth_config_specification.complete_oauth_server_output_specification["properties"]) - ) - - diff = paths_to_validate - set(get_expected_schema_structure(spec_schema)) - assert diff == set(), f"Specified oauth fields are missed from spec schema: {diff}" - - -async def test_oauth_is_default_method(target_spec: ConnectorSpecification): - """ - OAuth is default check. - If credentials do have oneOf: we check that the OAuth is listed at first. - If there is no oneOf and Oauth: OAuth is only option to authenticate the source and no check is needed. - """ - advanced_auth = target_spec.advanced_auth - if not advanced_auth: - pytest.skip("Source does not have OAuth method.") - if not advanced_auth.predicate_key: - pytest.skip("Advanced Auth object does not have predicate_key, only one option to authenticate.") - - spec_schema = target_spec.connectionSpecification - credentials = advanced_auth.predicate_key[0] - try: - one_of_default_method = dpath.util.get(spec_schema, f"/**/{credentials}/oneOf/0") - except KeyError: # Key Error when oneOf is not in credentials object - pytest.skip("Credentials object does not have oneOf option.") - - path_in_credentials = "/".join(advanced_auth.predicate_key[1:]) - auth_method_predicate_const = dpath.util.get(one_of_default_method, f"/**/{path_in_credentials}/const") - assert ( - auth_method_predicate_const == advanced_auth.predicate_value - ), f"Oauth method should be a default option. Current default method is {auth_method_predicate_const}." - - -async def test_additional_properties_is_true(target_spec: ConnectorSpecification): - """Check that value of the "additionalProperties" field is always true. - A spec declaring "additionalProperties": false introduces the risk of accidental breaking changes. - Specifically, when removing a property from the spec, existing connector configs will no longer be valid. - False value introduces the risk of accidental breaking changes. - Read https://github.com/airbytehq/airbyte/issues/14196 for more details""" - additional_properties_values = find_all_values_for_key_in_schema(target_spec.connectionSpecification, "additionalProperties") - if additional_properties_values: - assert all( - [additional_properties_value is True for additional_properties_value in additional_properties_values] - ), "When set, additionalProperties field value must be true for backward compatibility." diff --git a/airbyte-ci/connectors/live-tests/tests/__init__.py b/airbyte-ci/connectors/live-tests/tests/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/live-tests/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/live-tests/tests/backends/__init__.py b/airbyte-ci/connectors/live-tests/tests/backends/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/live-tests/tests/backends/test_file_backend.py b/airbyte-ci/connectors/live-tests/tests/backends/test_file_backend.py deleted file mode 100644 index fbd8f03bc4f7..000000000000 --- a/airbyte-ci/connectors/live-tests/tests/backends/test_file_backend.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from pathlib import Path - -import pytest -from airbyte_protocol.models import ( - AirbyteCatalog, - AirbyteConnectionStatus, - AirbyteMessage, - AirbyteRecordMessage, - AirbyteStateMessage, - ConnectorSpecification, - Status, -) -from airbyte_protocol.models import Type as AirbyteMessageType - -from live_tests.commons.backends import FileBackend - - -@pytest.mark.parametrize( - "messages, expected_writes", - [ - ( - [ - AirbyteMessage(type=AirbyteMessageType.CATALOG, catalog=AirbyteCatalog(streams=[])), - AirbyteMessage( - type=AirbyteMessageType.CONNECTION_STATUS, - connectionStatus=AirbyteConnectionStatus(status=Status.SUCCEEDED), - ), - AirbyteMessage( - type=AirbyteMessageType.RECORD, - record=AirbyteRecordMessage(stream="test_stream", data={}, emitted_at=123456789), - ), - AirbyteMessage( - type=AirbyteMessageType.SPEC, - spec=ConnectorSpecification(connectionSpecification={}), - ), - AirbyteMessage( - type=AirbyteMessageType.STATE, - state=AirbyteStateMessage(data={"test": "value"}), - ), - ], - [ - ("catalog.jsonl", '{"streams": []}\n'), - ( - "connection_status.jsonl", - '{"status": "SUCCEEDED", "message": null}\n', - ), - ( - "records.jsonl", - '{"namespace": null, "stream": "test_stream", "data": {}, "meta": null}\n', - ), - ( - "spec.jsonl", - '{"documentationUrl": null, "changelogUrl": null, "connectionSpecification": {}, "supportsIncremental": null, "supportsNormalization": false, "supportsDBT": false, "supported_destination_sync_modes": null, "advanced_auth": null, "protocol_version": null}\n', - ), - ( - "states.jsonl", - '{"type": null, "stream": null, "global_": null, "data": {"test": "value"}, "sourceStats": null, "destinationStats": null}\n', - ), - ], - ), - ], -) -def test_write(tmp_path, messages, expected_writes): - backend = FileBackend(tmp_path) - backend.write(messages) - for expected_file, expected_content in expected_writes: - expected_path = Path(tmp_path / expected_file) - assert expected_path.exists() - content = expected_path.read_text() diff --git a/airbyte-ci/connectors/live-tests/tests/test_get_connection_objects_retrieval.py b/airbyte-ci/connectors/live-tests/tests/test_get_connection_objects_retrieval.py deleted file mode 100644 index e38bba4fcc41..000000000000 --- a/airbyte-ci/connectors/live-tests/tests/test_get_connection_objects_retrieval.py +++ /dev/null @@ -1,702 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import uuid -from unittest.mock import patch -from uuid import UUID - -from connection_retriever import ConnectionObject -from connection_retriever.retrieval import TestingCandidate -from connection_retriever.utils import ConnectionSubset - -from live_tests.commons.connection_objects_retrieval import _get_connection_objects_from_retrieved_objects - -mocking_return_of_retrieve_testing_candidates = [ - TestingCandidate( - connection_id="0b11bcb3-7726-4d1a-bcb4-d68fb579f7a8", - connection_url=f"https://cloud.airbyte.com/workspaces/{str(uuid.uuid4())}/connections/{str(uuid.uuid4())}", - catalog=None, - configured_catalog=None, - state=None, - workspace_id=None, - destination_docker_image=None, - source_docker_image=None, - last_attempt_duration_in_microseconds=44902474342, - streams_with_data=[ - "sponsored_products_report_stream", - "profiles", - "sponsored_display_report_stream", - "sponsored_brands_v3_report_stream", - ], - ), - TestingCandidate( - connection_id="8775fd75-e510-4f7f-98dc-0128ea997133", - connection_url=f"https://cloud.airbyte.com/workspaces/{str(uuid.uuid4())}/connections/{str(uuid.uuid4())}", - catalog=None, - configured_catalog=None, - state=None, - workspace_id=None, - destination_docker_image=None, - source_docker_image=None, - last_attempt_duration_in_microseconds=44902474342, - streams_with_data=[ - "sponsored_products_report_stream", - "sponsored_display_report_stream", - "profiles", - "sponsored_brands_v3_report_stream", - ], - ), -] - -mocking_return_of_retrieve_objects = [ - TestingCandidate( - connection_id="0b11bcb3-7726-4d1a-bcb4-d68fb579f7a8", - connection_url=f"https://cloud.airbyte.com/workspaces/{str(uuid.uuid4())}/connections/{str(uuid.uuid4())}", - catalog={ - "streams": [ - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "countryCode": {"type": [...]}, - }, - "title": "profiles", - "type": ["null", "object"], - }, - "name": "profiles", - "source_defined_primary_key": [["profileId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "state": {"type": [...]}, - }, - "title": "portfolios", - "type": ["null", "object"], - }, - "name": "portfolios", - "source_defined_primary_key": [["portfolioId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "tactic": {"type": [...]}, - }, - "title": "sponsored_display_campaigns", - "type": ["null", "object"], - }, - "name": "sponsored_display_campaigns", - "source_defined_primary_key": [["campaignId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "tactic": {"type": [...]}, - }, - "title": "sponsored_display_ad_groups", - "type": ["null", "object"], - }, - "name": "sponsored_display_ad_groups", - "source_defined_primary_key": [["adGroupId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "state": {"type": [...]}, - }, - "title": "sponsored_display_product_ads", - "type": ["null", "object"], - }, - "name": "sponsored_display_product_ads", - "source_defined_primary_key": [["adId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "targetId": {"type": [...]}, - }, - "title": "sponsored_display_targetings", - "type": ["null", "object"], - }, - "name": "sponsored_display_targetings", - "source_defined_primary_key": [["targetId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "properties": {"type": [...]}, - }, - "title": "sponsored_display_creatives", - "type": ["null", "object"], - }, - "name": "sponsored_display_creatives", - "source_defined_primary_key": [["creativeId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "createdDate": {"type": [...]}, - }, - "title": "sponsored_display_budget_rules", - "type": ["null", "object"], - }, - "name": "sponsored_display_budget_rules", - "source_defined_primary_key": [["ruleId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_brands_keywords", - "type": ["null", "object"], - }, - "name": "sponsored_brands_keywords", - "source_defined_primary_key": [["adGroupId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "bidding": {"type": [...]}, - }, - "title": "sponsored_brands_campaigns", - "type": ["null", "object"], - }, - "name": "sponsored_brands_campaigns", - "source_defined_primary_key": [["campaignId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_brands_ad_groups", - "type": ["null", "object"], - }, - "name": "sponsored_brands_ad_groups", - "source_defined_primary_key": [["adGroupId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "budget": {"type": [...]}, - }, - "title": "sponsored_product_campaigns", - "type": ["null", "object"], - }, - "name": "sponsored_product_campaigns", - "source_defined_primary_key": [["campaignId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_ad_groups", - "type": ["null", "object"], - }, - "name": "sponsored_product_ad_groups", - "source_defined_primary_key": [["adGroupId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_keywords", - "type": ["null", "object"], - }, - "name": "sponsored_product_keywords", - "source_defined_primary_key": [["keywordId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_negative_keywords", - "type": ["null", "object"], - }, - "name": "sponsored_product_negative_keywords", - "source_defined_primary_key": [["keywordId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_campaign_negative_keywords", - "type": ["null", "object"], - }, - "name": "sponsored_product_campaign_negative_keywords", - "source_defined_primary_key": [["keywordId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_ads", - "type": ["null", "object"], - }, - "name": "sponsored_product_ads", - "source_defined_primary_key": [["adId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "sponsored_product_targetings", - "type": ["null", "object"], - }, - "name": "sponsored_product_targetings", - "source_defined_primary_key": [["targetId"]], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - "bidRecommendationsForTargetingExpressions": {"items": {...}, "type": "array"}, - "campaignId": {"type": [...]}, - "theme": {"type": [...]}, - }, - "title": "sponsored_product_ad_group_bid_recommendations", - "type": ["null", "object"], - }, - "name": "sponsored_product_ad_group_bid_recommendations", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": {"adGroupId": {"type": [...]}, "suggestedKeywords": {"items": {...}, "type": [...]}}, - "title": "sponsored_product_ad_group_suggested_keywords", - "type": ["null", "object"], - }, - "name": "sponsored_product_ad_group_suggested_keywords", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "attribution_report_products", - "type": ["null", "object"], - }, - "name": "attribution_report_products", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "attribution_report_performance_adgroup", - "type": ["null", "object"], - }, - "name": "attribution_report_performance_adgroup", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "attribution_report_performance_campaign", - "type": ["null", "object"], - }, - "name": "attribution_report_performance_campaign", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "adGroupId": {"type": [...]}, - }, - "title": "attribution_report_performance_creative", - "type": ["null", "object"], - }, - "name": "attribution_report_performance_creative", - "source_defined_primary_key": [], - "supported_sync_modes": ["full_refresh"], - }, - { - "default_cursor_field": ["reportDate"], - "is_resumable": True, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "profileId": {"type": [...]}, - }, - "title": "sponsored_display_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_display_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - { - "default_cursor_field": ["reportDate"], - "is_resumable": True, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "profileId": {"type": [...]}, - }, - "title": "sponsored_brands_v3_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_brands_v3_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - { - "default_cursor_field": ["reportDate"], - "is_resumable": True, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "profileId": {"type": [...]}, - }, - "title": "sponsored_products_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_products_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - ] - }, - configured_catalog={ - "streams": [ - { - "cursor_field": ["reportDate"], - "destination_sync_mode": "append_dedup", - "fields": [ - {"name": "metric", "type": "OBJECT"}, - {"name": "recordId", "type": "STRING"}, - {"name": "profileId", "type": "INTEGER"}, - {"name": "recordType", "type": "STRING"}, - {"name": "reportDate", "type": "STRING"}, - ], - "mappers": [], - "primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "stream": { - "default_cursor_field": ["reportDate"], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "metric": {...}, - "profileId": {...}, - "recordId": {...}, - "recordType": {...}, - "reportDate": {...}, - }, - "title": "sponsored_display_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_display_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - "sync_mode": "incremental", - }, - { - "cursor_field": [], - "destination_sync_mode": "overwrite_dedup", - "fields": [ - {"name": "timezone", "type": "STRING"}, - {"name": "profileId", "type": "INTEGER"}, - {"name": "accountInfo", "type": "OBJECT"}, - {"name": "countryCode", "type": "STRING"}, - {"name": "dailyBudget", "type": "NUMBER"}, - {"name": "currencyCode", "type": "STRING"}, - ], - "mappers": [], - "primary_key": [["profileId"]], - "stream": { - "default_cursor_field": [], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "accountInfo": {...}, - "countryCode": {...}, - "currencyCode": {...}, - "dailyBudget": {...}, - "profileId": {...}, - "timezone": {...}, - }, - "title": "profiles", - "type": ["null", "object"], - }, - "name": "profiles", - "source_defined_cursor": False, - "source_defined_primary_key": [["profileId"]], - "supported_sync_modes": ["full_refresh"], - }, - "sync_mode": "full_refresh", - }, - { - "cursor_field": ["reportDate"], - "destination_sync_mode": "append_dedup", - "fields": [ - {"name": "metric", "type": "OBJECT"}, - {"name": "recordId", "type": "STRING"}, - {"name": "profileId", "type": "INTEGER"}, - {"name": "recordType", "type": "STRING"}, - {"name": "reportDate", "type": "STRING"}, - ], - "mappers": [], - "primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "stream": { - "default_cursor_field": ["reportDate"], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "metric": {...}, - "profileId": {...}, - "recordId": {...}, - "recordType": {...}, - "reportDate": {...}, - }, - "title": "sponsored_brands_v3_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_brands_v3_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - "sync_mode": "incremental", - }, - { - "cursor_field": ["reportDate"], - "destination_sync_mode": "append_dedup", - "fields": [ - {"name": "metric", "type": "OBJECT"}, - {"name": "recordId", "type": "STRING"}, - {"name": "profileId", "type": "INTEGER"}, - {"name": "recordType", "type": "STRING"}, - {"name": "reportDate", "type": "STRING"}, - ], - "mappers": [], - "primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "stream": { - "default_cursor_field": ["reportDate"], - "is_resumable": False, - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "metric": {...}, - "profileId": {...}, - "recordId": {...}, - "recordType": {...}, - "reportDate": {...}, - }, - "title": "sponsored_products_report_stream", - "type": ["null", "object"], - }, - "name": "sponsored_products_report_stream", - "source_defined_cursor": True, - "source_defined_primary_key": [["profileId"], ["recordType"], ["reportDate"], ["recordId"]], - "supported_sync_modes": ["full_refresh", "incremental"], - }, - "sync_mode": "incremental", - }, - ] - }, - state=[ - { - "stream": { - "stream_descriptor": {"name": "profiles", "namespace": None}, - "stream_state": {"__ab_no_cursor_state_message": True}, - }, - "type": "STREAM", - }, - { - "stream": { - "stream_descriptor": {"name": "sponsored_brands_v3_report_stream", "namespace": None}, - "stream_state": { - "2575400145671382": {"reportDate": "2025-02-04"}, - }, - }, - "type": "STREAM", - }, - { - "stream": { - "stream_descriptor": {"name": "sponsored_products_report_stream", "namespace": None}, - "stream_state": { - "2575400145671382": {"reportDate": "2025-02-04"}, - }, - }, - "type": "STREAM", - }, - { - "stream": { - "stream_descriptor": {"name": "sponsored_display_report_stream", "namespace": None}, - "stream_state": { - "2575400145671382": {"reportDate": "2025-02-04"}, - }, - }, - "type": "STREAM", - }, - ], - workspace_id=UUID("90336fed-1595-492e-a938-eaf4b058fb25"), - destination_docker_image=None, - destination_id=UUID("97dfd7b1-8908-4f76-af4f-38351fb11fd3"), - source_config={"key": "value"}, - source_docker_image="airbyte/source-amazon-ads:6.2.7", - source_id=UUID("93ad83fd-796a-4b5c-bc63-54b0266d28dd"), - last_attempt_duration_in_microseconds=44902474342, - streams_with_data=[ - "sponsored_products_report_stream", - "profiles", - "sponsored_display_report_stream", - "sponsored_brands_v3_report_stream", - ], - ), -] - - -def test_get_connection_objects_from_retrieved_objects(): - with ( - patch( - "live_tests.commons.connection_objects_retrieval.retrieve_testing_candidates", - return_value=mocking_return_of_retrieve_testing_candidates, - ), - patch("live_tests.commons.connection_objects_retrieval.retrieve_objects", return_value=mocking_return_of_retrieve_objects), - ): - requested_objects = { - ConnectionObject.DESTINATION_ID, - ConnectionObject.SOURCE_ID, - ConnectionObject.CONFIGURED_CATALOG, - ConnectionObject.STATE, - ConnectionObject.CATALOG, - ConnectionObject.SOURCE_CONFIG, - ConnectionObject.WORKSPACE_ID, - ConnectionObject.SOURCE_DOCKER_IMAGE, - } - - retrieval_reason = "Running live tests on connection for connector airbyte/source-amazon-ads on target versions (dev)." - selected_streams = {"sponsored_brands_v3_report_stream"} - connection_objects = _get_connection_objects_from_retrieved_objects( - requested_objects=requested_objects, - retrieval_reason=retrieval_reason, - source_docker_repository="airbyte/source-amazon-ads", - source_docker_image_tag="6.2.7", - selected_streams=selected_streams, - connection_id=None, - custom_config=None, - custom_configured_catalog=None, - custom_state=None, - connection_subset=ConnectionSubset.ALL, - max_connections=None, - ) - # it is expected to get 1 connection only from _find_best_candidates_subset, because selected stream is presented in catalog and has data - assert len(connection_objects) == 1 - connection_objects_to_check = connection_objects[0] - assert len(connection_objects_to_check.configured_catalog.streams) == len( - selected_streams - ), f"Number of streams in catatalog should match number of selected streams: {len(selected_streams)}" - assert connection_objects_to_check.configured_catalog.streams[0].stream.name == "sponsored_brands_v3_report_stream" diff --git a/airbyte-ci/connectors/live-tests/tests/test_json_schema_helper.py b/airbyte-ci/connectors/live-tests/tests/test_json_schema_helper.py deleted file mode 100644 index 6b73fbfa3ceb..000000000000 --- a/airbyte-ci/connectors/live-tests/tests/test_json_schema_helper.py +++ /dev/null @@ -1,480 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from enum import Enum -from typing import Any, Iterable, List, Mapping, Text, Tuple, Union - -import pendulum -import pytest -from airbyte_protocol.models import ( - AirbyteMessage, - AirbyteRecordMessage, - AirbyteStream, - ConfiguredAirbyteStream, - DestinationSyncMode, - SyncMode, - Type, -) -from pydantic import BaseModel - -from live_tests.commons.json_schema_helper import ( - ComparableType, - JsonSchemaHelper, - conforms_to_schema, - get_expected_schema_structure, - get_object_structure, -) - - -def records_with_state(records, state, stream_mapping, state_cursor_paths) -> Iterable[Tuple[Any, Any, Any]]: - """Iterate over records and return cursor value with corresponding cursor value from state""" - - for record in records: - stream_name = record.record.stream - stream = stream_mapping[stream_name] - helper = JsonSchemaHelper(schema=stream.stream.json_schema) - cursor_field = helper.field(stream.cursor_field) - record_value = cursor_field.parse(record=record.record.data) - try: - if state[stream_name] is None: - continue - - # first attempt to parse the state value assuming the state object is namespaced on stream names - state_value = cursor_field.parse(record=state[stream_name], path=state_cursor_paths[stream_name]) - except KeyError: - try: - # try second time as an absolute path in state file (i.e. bookmarks -> stream_name -> column -> value) - state_value = cursor_field.parse(record=state, path=state_cursor_paths[stream_name]) - except KeyError: - continue - yield record_value, state_value, stream_name - - -@pytest.fixture(name="simple_state") -def simple_state_fixture(): - return { - "my_stream": { - "id": 11, - "ts_created": "2014-01-01T22:03:11", - "ts_updated": "2015-01-01T22:03:11", - } - } - - -@pytest.fixture(name="none_state") -def none_state_fixture(): - return {"my_stream": None} - - -@pytest.fixture(name="nested_state") -def nested_state_fixture(simple_state): - return {"my_stream": {"some_account_id": simple_state["my_stream"]}} - - -@pytest.fixture(name="singer_state") -def singer_state_fixture(simple_state): - return {"bookmarks": simple_state} - - -@pytest.fixture(name="stream_schema") -def stream_schema_fixture(): - return { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id": {"type": "integer"}, - "ts_created": {"type": "string", "format": "datetime"}, - "nested": {"type": "object", "properties": {"ts_updated": {"type": "string", "format": "date"}}}, - }, - } - - -@pytest.fixture(name="stream_mapping") -def stream_mapping_fixture(stream_schema): - return { - "my_stream": ConfiguredAirbyteStream( - stream=AirbyteStream(name="my_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.full_refresh]), - sync_mode=SyncMode.full_refresh, - destination_sync_mode=DestinationSyncMode.append, - ) - } - - -@pytest.fixture(name="records") -def records_fixture(): - return [ - AirbyteMessage( - type=Type.RECORD, - record=AirbyteRecordMessage( - stream="my_stream", - data={"id": 1, "ts_created": "2015-11-01T22:03:11", "nested": {"ts_updated": "2015-05-01"}}, - emitted_at=0, - ), - ) - ] - - -def test_simple_path(records, stream_mapping, simple_state): - stream_mapping["my_stream"].cursor_field = ["id"] - paths = {"my_stream": ["id"]} - - result = records_with_state(records=records, state=simple_state, stream_mapping=stream_mapping, state_cursor_paths=paths) - record_value, state_value, stream_name = next(result) - - assert record_value == 1, "record value must be correctly found" - assert state_value == 11, "state value must be correctly found" - - -def test_nested_path(records, stream_mapping, nested_state): - stream_mapping["my_stream"].cursor_field = ["nested", "ts_updated"] - paths = {"my_stream": ["some_account_id", "ts_updated"]} - - result = records_with_state(records=records, state=nested_state, stream_mapping=stream_mapping, state_cursor_paths=paths) - record_value, state_value, stream_name = next(result) - - assert record_value == pendulum.datetime(2015, 5, 1), "record value must be correctly found" - assert state_value == pendulum.datetime(2015, 1, 1, 22, 3, 11), "state value must be correctly found" - - -def test_absolute_path(records, stream_mapping, singer_state): - stream_mapping["my_stream"].cursor_field = ["ts_created"] - paths = {"my_stream": ["bookmarks", "my_stream", "ts_created"]} - - result = records_with_state(records=records, state=singer_state, stream_mapping=stream_mapping, state_cursor_paths=paths) - record_value, state_value, stream_name = next(result) - - assert record_value == pendulum.datetime(2015, 11, 1, 22, 3, 11), "record value must be correctly found" - assert state_value == pendulum.datetime(2014, 1, 1, 22, 3, 11), "state value must be correctly found" - - -def test_none_state(records, stream_mapping, none_state): - stream_mapping["my_stream"].cursor_field = ["ts_created"] - paths = {"my_stream": ["unknown", "ts_created"]} - - result = records_with_state(records=records, state=none_state, stream_mapping=stream_mapping, state_cursor_paths=paths) - assert next(result, None) is None - - -def test_json_schema_helper_pydantic_generated(): - class E(str, Enum): - A = "dda" - B = "dds" - C = "ddf" - - class E2(BaseModel): - e2: str - - class C(BaseModel): - aaa: int - e: Union[E, E2] - - class A(BaseModel): - sdf: str - sss: str - c: C - - class B(BaseModel): - name: str - surname: str - - class Root(BaseModel): - f: Union[A, B] - - js_helper = JsonSchemaHelper(Root.schema()) - variant_paths = js_helper.find_nodes(keys=["anyOf", "oneOf"]) - assert len(variant_paths) == 2 - assert variant_paths == [["properties", "f", "anyOf"], ["definitions", "C", "properties", "e", "anyOf"]] - # TODO: implement validation for pydantic generated objects as well - # js_helper.validate_variant_paths(variant_paths) - - -@pytest.mark.parametrize( - "object, paths", - [ - ({}, []), - ({"a": 12}, ["/a"]), - ({"a": {"b": 12}}, ["/a", "/a/b"]), - ({"a": {"b": 12}, "c": 45}, ["/a", "/a/b", "/c"]), - ( - {"a": [{"b": 12}]}, - ["/a", "/a/[]", "/a/[]/b"], - ), - ({"a": [{"b": 12}, {"b": 15}]}, ["/a", "/a/[]", "/a/[]/b"]), - ({"a": [[[{"b": 12}, {"b": 15}]]]}, ["/a", "/a/[]", "/a/[]/[]", "/a/[]/[]/[]", "/a/[]/[]/[]/b"]), - ], -) -def test_get_object_strucutre(object, paths): - assert get_object_structure(object) == paths - - -@pytest.mark.parametrize( - "schema, paths", - [ - ({"type": "object", "properties": {"a": {"type": "string"}}}, ["/a"]), - ({"properties": {"a": {"type": "string"}}}, ["/a"]), - ({"type": "object", "properties": {"a": {"type": "string"}, "b": {"type": "number"}}}, ["/a", "/b"]), - ( - { - "type": "object", - "properties": {"a": {"type": "string"}, "b": {"$ref": "#definitions/b_type"}}, - "definitions": {"b_type": {"type": "number"}}, - }, - ["/a", "/b"], - ), - ({"type": "object", "oneOf": [{"properties": {"a": {"type": "string"}}}, {"properties": {"b": {"type": "string"}}}]}, ["/a", "/b"]), - # Some of pydantic generatec schemas have anyOf keyword - ({"type": "object", "anyOf": [{"properties": {"a": {"type": "string"}}}, {"properties": {"b": {"type": "string"}}}]}, ["/a", "/b"]), - ( - {"type": "array", "items": {"oneOf": [{"properties": {"a": {"type": "string"}}}, {"properties": {"b": {"type": "string"}}}]}}, - ["/[]/a", "/[]/b"], - ), - # There could be an object with any properties with specific type - ({"type": "object", "properties": {"a": {"type": "object", "additionalProperties": {"type": "string"}}}}, ["/a"]), - # Array with no item type specified - ({"type": "array"}, ["/[]"]), - ({"type": "array", "items": {"type": "object", "additionalProperties": {"type": "string"}}}, ["/[]"]), - ], -) -def test_get_expected_schema_structure(schema, paths): - assert paths == get_expected_schema_structure(schema) - - -@pytest.mark.parametrize( - "keys, num_paths, last_value", - [ - (["description"], 1, "Tests that keys can be found inside lists of dicts"), - (["option1"], 2, {"a_key": "a_value"}), - (["option2"], 1, ["value1", "value2"]), - (["nonexistent_key"], 0, None), - (["option1", "option2"], 3, ["value1", "value2"]), - ], -) -def test_find_and_get_nodes(keys: List[Text], num_paths: int, last_value: Any): - schema = { - "title": "Key_inside_oneOf", - "description": "Tests that keys can be found inside lists of dicts", - "type": "object", - "properties": { - "credentials": { - "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "common": {"type": "string", "const": "option1", "default": "option1"}, - "option1": {"type": "string"}, - }, - }, - { - "type": "object", - "properties": { - "common": {"type": "string", "const": "option2", "default": "option2"}, - "option1": {"a_key": "a_value"}, - "option2": ["value1", "value2"], - }, - }, - ], - } - }, - } - schema_helper = JsonSchemaHelper(schema) - variant_paths = schema_helper.find_nodes(keys=keys) - assert len(variant_paths) == num_paths - - if variant_paths: - values_at_nodes = [] - for path in variant_paths: - values_at_nodes.append(schema_helper.get_node(path)) - assert last_value in values_at_nodes - - -COMPLETE_CONFORMING_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - - -NONCONFORMING_EXTRA_COLUMN_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, - "column_x": "extra", -} - -CONFORMING_WITH_MISSING_COLUMN_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], -} - -CONFORMING_WITH_NARROWER_TYPE_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": True, - "number_field": True, - "string_field": True, - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -NONCONFORMING_WIDER_TYPE_RECORD = { - "null_field": "not None", - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -NONCONFORMING_NON_OBJECT_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": "not an object", -} - -NONCONFORMING_NON_ARRAY_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": "not an array", - "object_field": {"col": "val"}, -} - -CONFORMING_MIXED_TYPE_NARROWER_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -NONCONFORMING_MIXED_TYPE_WIDER_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -CONFORMING_MIXED_TYPE_WITHIN_TYPE_RANGE_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "val1", - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -NONCONFORMING_INVALID_ARRAY_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": ["this should not be an array"], - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - -NONCONFORMING_TOO_WIDE_ARRAY_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "okay", - "array_field": ["val1", "val2"], - "object_field": {"col": "val"}, -} - - -CONFORMING_NARROWER_ARRAY_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": "okay", - "array_field": [1, 2], - "object_field": {"col": "val"}, -} - - -NONCONFORMING_INVALID_OBJECT_RECORD = { - "null_field": None, - "boolean_field": True, - "integer_field": 1, - "number_field": 1.5, - "string_field": {"this": "should not be an object"}, - "array_field": [1.1, 2.2], - "object_field": {"col": "val"}, -} - - -SCHEMA = { - "type": "object", - "properties": { - "null_field": {"type": "null"}, - "boolean_field": {"type": "boolean"}, - "integer_field": {"type": "integer"}, - "number_field": {"type": "number"}, - "string_field": {"type": "string"}, - "array_field": { - "type": "array", - "items": { - "type": "number", - }, - }, - "object_field": {"type": "object"}, - }, -} - - -@pytest.mark.parametrize( - "record,schema,expected_result", - [ - pytest.param(COMPLETE_CONFORMING_RECORD, SCHEMA, True, id="record-conforms"), - pytest.param(NONCONFORMING_EXTRA_COLUMN_RECORD, SCHEMA, False, id="nonconforming-extra-column"), - pytest.param(CONFORMING_WITH_MISSING_COLUMN_RECORD, SCHEMA, True, id="record-conforms-with-missing-column"), - pytest.param(CONFORMING_WITH_NARROWER_TYPE_RECORD, SCHEMA, True, id="record-conforms-with-narrower-type"), - pytest.param(NONCONFORMING_WIDER_TYPE_RECORD, SCHEMA, False, id="nonconforming-wider-type"), - pytest.param(NONCONFORMING_NON_OBJECT_RECORD, SCHEMA, False, id="nonconforming-string-is-not-an-object"), - pytest.param(NONCONFORMING_NON_ARRAY_RECORD, SCHEMA, False, id="nonconforming-string-is-not-an-array"), - pytest.param(NONCONFORMING_TOO_WIDE_ARRAY_RECORD, SCHEMA, False, id="nonconforming-array-values-too-wide"), - pytest.param(CONFORMING_NARROWER_ARRAY_RECORD, SCHEMA, True, id="conforming-array-values-narrower-than-schema"), - pytest.param(NONCONFORMING_INVALID_ARRAY_RECORD, SCHEMA, False, id="nonconforming-array-is-not-a-string"), - pytest.param(NONCONFORMING_INVALID_OBJECT_RECORD, SCHEMA, False, id="nonconforming-object-is-not-a-string"), - ], -) -def test_conforms_to_schema(record: Mapping[str, Any], schema: Mapping[str, Any], expected_result: bool) -> None: - assert conforms_to_schema(record, schema) == expected_result - - -def test_comparable_types() -> None: - assert ComparableType.OBJECT > ComparableType.STRING - assert ComparableType.STRING > ComparableType.NUMBER - assert ComparableType.NUMBER > ComparableType.INTEGER - assert ComparableType.INTEGER > ComparableType.BOOLEAN - assert ComparableType["OBJECT"] == ComparableType.OBJECT diff --git a/airbyte-ci/connectors/metadata_service/README.md b/airbyte-ci/connectors/metadata_service/README.md deleted file mode 100644 index 5a0c36548ddf..000000000000 --- a/airbyte-ci/connectors/metadata_service/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Metadata Service - -Airbyte Metadata Service manages the Airbyte Connector Registry. - -This system is responsible for the following: - -- Validating Connector metadata -- Storing Connector metadata in GCS -- Serving Connector metadata to various consumers -- Aggregating Connector metadata to provide a unified view of all connectors -- Triggering actions based on changes to Connector metadata - -## Subsystems - -- [Metadata Lib](./lib) responsible for preparing and validating connector metadata. diff --git a/airbyte-ci/connectors/metadata_service/docs/external_documentation_urls.md b/airbyte-ci/connectors/metadata_service/docs/external_documentation_urls.md deleted file mode 100644 index e67d19bb8804..000000000000 --- a/airbyte-ci/connectors/metadata_service/docs/external_documentation_urls.md +++ /dev/null @@ -1,554 +0,0 @@ -# External Documentation URLs Guide - -## Overview - -This guide explains how to identify, scrape, and maintain `externalDocumentationUrls` in connector `metadata.yaml` files. These URLs point to official vendor documentation that helps users understand API changes, authentication requirements, rate limits, and other critical information. - -### Purpose - -External documentation URLs serve several key purposes: -- Surface vendor changelogs and release notes for tracking breaking changes -- Provide quick access to authentication and permissions documentation -- Link to rate limits and quota information -- Direct users to official API references and data model documentation -- Connect users with vendor status pages and developer communities - -### Schema Location - -The schema is defined in `airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml`. - -The `externalDocumentationUrls` field is an optional array where each entry contains: -- `title` (required): Display title for the documentation link -- `url` (required): URL to the external documentation -- `type` (optional): Category of documentation (see taxonomy below) -- `requiresLogin` (optional, default: false): Whether the URL requires authentication to access - -## Documentation Type Taxonomy - -### api_release_history -**Description:** Changelogs, release notes, API version history, and out-of-cycle changes. - -**Typical URL patterns:** -- `*/changelog*` -- `*/release-notes*` -- `*/api/versions*` -- `*/whats-new*` - -**Examples:** -- Airtable: `https://airtable.com/developers/web/api/changelog` -- Google Ads: `https://developers.google.com/google-ads/api/docs/release-notes` -- Salesforce: `https://help.salesforce.com/s/articleView?id=release-notes.salesforce_release_notes.htm` -- Facebook Marketing: `https://developers.facebook.com/docs/marketing-api/marketing-api-changelog` - -### api_reference -**Description:** API documentation, versioning docs, technical references, and API specifications. - -**Typical URL patterns:** -- `*/api/reference*` -- `*/api/docs*` -- `*/developers/api*` -- `*/api-reference*` - -**Examples:** -- GitLab: `https://docs.gitlab.com/ee/api/rest/` -- Chargebee: `https://apidocs.chargebee.com/docs/api/versioning` -- Azure Blob Storage: `https://learn.microsoft.com/en-us/rest/api/storageservices/` -- Pinecone: `https://docs.pinecone.io/reference/api/introduction` - -### api_deprecations -**Description:** Deprecation notices, future breaking changes, and migration timelines. - -**Typical URL patterns:** -- `*/deprecations*` -- `*/breaking-changes*` -- `*/sunset*` -- `*/migration*` - -**Examples:** -- GitLab: `https://docs.gitlab.com/ee/api/rest/deprecations.html` -- Facebook Marketing: `https://developers.facebook.com/docs/marketing-api/out-of-cycle-changes/` -- Stripe: `https://stripe.com/docs/upgrades#api-versions` - -### authentication_guide -**Description:** Official OAuth/API key setup, consent flows, and authentication methods. - -**Typical URL patterns:** -- `*/oauth*` -- `*/authentication*` -- `*/auth*` -- `*/api-keys*` -- `*/credentials*` - -**Examples:** -- Airtable: `https://airtable.com/developers/web/api/oauth-reference` -- Google Cloud: `https://cloud.google.com/iam/docs/service-accounts` -- Salesforce: `https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm` -- Snowflake: `https://docs.snowflake.com/en/user-guide/key-pair-auth` - -### permissions_scopes -**Description:** Required roles, permissions, OAuth scopes, or database GRANTs. - -**Typical URL patterns:** -- `*/permissions*` -- `*/scopes*` -- `*/roles*` -- `*/access-control*` -- `*/grants*` - -**Examples:** -- BigQuery: `https://cloud.google.com/bigquery/docs/access-control` -- Salesforce: `https://help.salesforce.com/s/articleView?id=sf.connected_app_create_api_integration.htm` -- Google Ads: `https://developers.google.com/google-ads/api/docs/oauth/overview` -- Postgres: `https://www.postgresql.org/docs/current/sql-grant.html` - -### rate_limits -**Description:** Rate limits, quotas, throttling behavior, and concurrency limits. - -**Typical URL patterns:** -- `*/rate-limits*` -- `*/quotas*` -- `*/limits*` -- `*/throttling*` - -**Examples:** -- BigQuery: `https://cloud.google.com/bigquery/quotas` -- GitHub: `https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting` -- Salesforce: `https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/` -- Stripe: `https://stripe.com/docs/rate-limits` - -### status_page -**Description:** Vendor status/uptime pages for incident tracking. - -**Typical URL patterns:** -- `status.*` -- `*/status*` -- `*/system-status*` - -**Examples:** -- Snowflake: `https://status.snowflake.com/` -- Google Cloud: `https://status.cloud.google.com/` -- Salesforce: `https://status.salesforce.com/` -- GitHub: `https://www.githubstatus.com/` - -### data_model_reference -**Description:** Object/field/endpoint reference for SaaS APIs (what tables/fields exist). - -**Typical URL patterns:** -- `*/object-reference*` -- `*/data-model*` -- `*/schema*` -- `*/objects*` - -**Examples:** -- Salesforce: `https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/` -- HubSpot: `https://developers.hubspot.com/docs/api/crm/understanding-the-crm` -- GitHub: `https://docs.github.com/en/rest/overview/resources-in-the-rest-api` - -### sql_reference -**Description:** SQL dialect/reference docs for databases and warehouses. - -**Typical URL patterns:** -- `*/sql-reference*` -- `*/sql/reference*` -- `*/language-reference*` - -**Examples:** -- BigQuery: `https://cloud.google.com/bigquery/docs/reference/standard-sql` -- Snowflake: `https://docs.snowflake.com/en/sql-reference` -- Postgres: `https://www.postgresql.org/docs/current/sql.html` -- Redshift: `https://docs.aws.amazon.com/redshift/latest/dg/c_SQL_reference.html` - -### migration_guide -**Description:** Vendor migration/breaking-change guides between versions. - -**Typical URL patterns:** -- `*/migration*` -- `*/upgrade*` -- `*/version-migration*` - -**Examples:** -- Stripe: `https://stripe.com/docs/upgrades` -- Elasticsearch: `https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes.html` - -### developer_community -**Description:** Official forums/communities for vendor Q&A and technical discussions. - -**Typical URL patterns:** -- `community.*` -- `*/community*` -- `*/forum*` -- `*/discussions*` - -**Examples:** -- Airtable: `https://community.airtable.com/development-apis-11` -- Salesforce: `https://developer.salesforce.com/forums` -- Snowflake: `https://community.snowflake.com/` - -### other -**Description:** Catch-all for documentation that doesn't fit other categories. Use sparingly. - -**Note:** Since `type` is optional, prefer omitting the type field rather than using "other" when a link doesn't fit neatly into a category. - -## How to Find Documentation URLs - -### Preferred Sources (in order of priority) -1. **Official vendor documentation** (e.g., `docs.vendor.com`, `developers.vendor.com`) -2. **Official vendor blogs** (e.g., `blog.vendor.com`, `developers.vendor.com/blog`) -3. **Official community forums** (e.g., `community.vendor.com`) - -### Search Query Templates - -Use these search patterns to find appropriate documentation: - -``` -# Release notes / changelogs -site: "release notes" -site: "changelog" -site: "API changelog" - -# API reference -site: "API reference" -site: "API documentation" - -# Authentication -site: "OAuth" -site: "authentication" -site: "API key" - -# Permissions -site: "permissions" -site: "scopes" -site: "access control" - -# Rate limits -site: "rate limits" -site: "quotas" -site: "API limits" - -# Status page -site:status. -"" status page - -# SQL reference (for databases/warehouses) -site: "SQL reference" -site: "language reference" -``` - -### URL Selection Heuristics - -1. **Prefer canonical, version-agnostic URLs** that are updated over time rather than version-specific pages - - ✅ Good: `https://docs.vendor.com/api/changelog` - - ❌ Avoid: `https://docs.vendor.com/v2.3/api/changelog` - -2. **Avoid locale-specific paths** when generic URLs exist - - ✅ Good: `https://docs.vendor.com/api/reference` - - ❌ Avoid: `https://docs.vendor.com/en-us/api/reference` - -3. **Prefer stable root pages** over deep-linked anchors that may change - - ✅ Good: `https://docs.vendor.com/rate-limits` - - ⚠️ Use with caution: `https://docs.vendor.com/api#rate-limits-section` - -4. **Always use official vendor domains** - never third-party mirrors or documentation aggregators - -5. **Set `requiresLogin: true`** when the URL requires authentication to access - -## Connector Family-Specific Guidance - -### Data Warehouses (BigQuery, Snowflake, Redshift, Databricks) - -**Priority types to include:** -- `sql_reference` (required) - SQL dialect documentation -- `authentication_guide` - Service account, key pair, or IAM auth -- `permissions_scopes` - Required roles and grants -- `api_release_history` - Release notes for API/driver changes -- `status_page` - Service status monitoring -- `rate_limits` - Query limits, concurrency limits (if applicable) - -**Example (BigQuery):** -```yaml -externalDocumentationUrls: - - title: Release notes - url: https://cloud.google.com/bigquery/docs/release-notes - type: api_release_history - - title: Standard SQL reference - url: https://cloud.google.com/bigquery/docs/reference/standard-sql - type: sql_reference - - title: Service account authentication - url: https://cloud.google.com/iam/docs/service-accounts - type: authentication_guide - - title: Access control and permissions - url: https://cloud.google.com/bigquery/docs/access-control - type: permissions_scopes - - title: Quotas and limits - url: https://cloud.google.com/bigquery/quotas - type: rate_limits - - title: Google Cloud Status - url: https://status.cloud.google.com/ - type: status_page -``` - -### Databases (Postgres, MySQL, MSSQL, MongoDB) - -**Priority types to include:** -- `sql_reference` (for SQL databases) - SQL dialect documentation -- `authentication_guide` - Connection and authentication methods -- `permissions_scopes` - User permissions and grants -- `api_release_history` - Version release notes (if applicable) - -**Example (Postgres):** -```yaml -externalDocumentationUrls: - - title: PostgreSQL documentation - url: https://www.postgresql.org/docs/current/ - type: api_reference - - title: SQL reference - url: https://www.postgresql.org/docs/current/sql.html - type: sql_reference - - title: Authentication methods - url: https://www.postgresql.org/docs/current/auth-methods.html - type: authentication_guide - - title: GRANT permissions - url: https://www.postgresql.org/docs/current/sql-grant.html - type: permissions_scopes -``` - -### SaaS APIs (Salesforce, HubSpot, GitHub, Stripe) - -**Priority types to include:** -- `api_release_history` (required) - Changelogs and release notes -- `api_reference` - API documentation -- `api_deprecations` - Deprecation schedules (if available) -- `authentication_guide` - OAuth or API key setup -- `permissions_scopes` - Required scopes or permissions -- `rate_limits` - API rate limits -- `data_model_reference` - Object/field reference -- `developer_community` - Developer forums -- `status_page` - Service status - -**Example (Salesforce):** -```yaml -externalDocumentationUrls: - - title: API release notes - url: https://help.salesforce.com/s/articleView?id=release-notes.salesforce_release_notes.htm - type: api_release_history - - title: REST API reference - url: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/ - type: api_reference - - title: Connected app OAuth - url: https://help.salesforce.com/s/articleView?id=sf.connected_app_create.htm - type: authentication_guide - - title: OAuth scopes - url: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_tokens_scopes.htm - type: permissions_scopes - - title: API rate limits - url: https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/ - type: rate_limits - - title: Object reference - url: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/ - type: data_model_reference - - title: Salesforce Status - url: https://status.salesforce.com/ - type: status_page -``` - -### Vector Stores (Pinecone, Weaviate, Qdrant, Chroma) - -**Priority types to include:** -- `api_reference` (required) - API documentation -- `rate_limits` - Request limits and quotas -- `authentication_guide` - API key or authentication setup -- `api_release_history` - Release notes (if available) -- `status_page` - Service status (if available) -- `migration_guide` - Version migration guides (if versions differ significantly) - -**Example (Pinecone):** -```yaml -externalDocumentationUrls: - - title: API reference - url: https://docs.pinecone.io/reference/api/introduction - type: api_reference - - title: Authentication - url: https://docs.pinecone.io/guides/get-started/authentication - type: authentication_guide - - title: Rate limits - url: https://docs.pinecone.io/troubleshooting/rate-limits - type: rate_limits - - title: Release notes - url: https://docs.pinecone.io/release-notes - type: api_release_history -``` - -## Maintenance Guidelines - -### Link Stability Checks - -Periodically validate that external documentation URLs are still accessible: - -1. **HTTP status validation** - Check that URLs return 200 or 3xx status codes -2. **Flag 4xx/5xx errors** - Document broken links for replacement -3. **Set `requiresLogin: true`** when appropriate to avoid false positives - -**Simple validation script:** -```bash -# Check a single URL -curl -I -L -s -o /dev/null -w "%{http_code}" "https://docs.vendor.com/api/changelog" - -# Expected: 200, 301, 302 -# Action needed: 404, 403, 500 -``` - -### Update Cadence - -- **Quarterly sweep** - Review all external documentation URLs for broken links -- **On vendor announcements** - Update when vendors announce major documentation restructuring -- **On deprecation notices** - Add `api_deprecations` links when vendors announce breaking changes - -### Replacement Rules - -When updating broken or outdated links: - -1. **Prefer same domain** - Look for the new location on the same vendor domain -2. **Check vendor consolidation** - Vendors may consolidate multiple doc sites into one -3. **Update to canonical** - If vendor provides a redirect, update to the final destination -4. **Document in PR** - Note why the link was changed in the PR description - -### PR Review Practices - -When reviewing PRs that add or modify external documentation URLs: - -1. **Spot-check 2-3 links** per connector to verify they're accessible and relevant -2. **Verify type categorization** - Ensure the `type` field matches the content -3. **Check for duplicates** - Avoid adding the same URL twice with different titles -4. **Validate requiresLogin** - Confirm whether authentication is actually required - -## YAML Template - -Here's a canonical example showing multiple types: - -```yaml -externalDocumentationUrls: - - title: Release notes - url: https://cloud.google.com/bigquery/docs/release-notes - type: api_release_history - - title: Standard SQL reference - url: https://cloud.google.com/bigquery/docs/reference/standard-sql - type: sql_reference - - title: Service account authentication - url: https://cloud.google.com/iam/docs/service-accounts - type: authentication_guide - - title: Access control and permissions - url: https://cloud.google.com/bigquery/docs/access-control - type: permissions_scopes - - title: Quotas and limits - url: https://cloud.google.com/bigquery/quotas - type: rate_limits - - title: Google Cloud Status - url: https://status.cloud.google.com/ - type: status_page - requiresLogin: false -``` - -## Validation Checklist - -Before submitting a PR with external documentation URLs: - -- [ ] Schema validation passes (run `poetry run metadata_service validate `) -- [ ] All URLs return HTTP 200 or 3xx status codes -- [ ] No paywalls or login requirements (unless `requiresLogin: true` is set) -- [ ] URLs are canonical and version-agnostic when possible -- [ ] Type categorization is accurate -- [ ] No duplicate URLs with different titles -- [ ] Official vendor domains only (no third-party mirrors) -- [ ] Titles are descriptive and consistent with vendor terminology - -## Common Pitfalls to Avoid - -1. **Don't over-categorize** - If a link doesn't fit neatly, omit the `type` field rather than forcing it -2. **Avoid version-specific URLs** - Prefer evergreen URLs that are updated over time -3. **Don't link to third-party docs** - Always use official vendor documentation -4. **Watch for locale redirects** - Some URLs may redirect based on browser locale -5. **Don't assume status pages exist** - Not all vendors have public status pages -6. **Avoid deep anchors** - Deep-linked sections may change; prefer stable root pages -7. **Don't duplicate content** - If a single page covers multiple topics, link it once with the most specific type - -## Examples by Connector Type - -### Example: Snowflake (Data Warehouse) -```yaml -externalDocumentationUrls: - - title: Release notes - url: https://docs.snowflake.com/en/release-notes - type: api_release_history - - title: SQL reference - url: https://docs.snowflake.com/en/sql-reference - type: sql_reference - - title: Key pair authentication - url: https://docs.snowflake.com/en/user-guide/key-pair-auth - type: authentication_guide - - title: Access control - url: https://docs.snowflake.com/en/user-guide/security-access-control - type: permissions_scopes - - title: Snowflake Status - url: https://status.snowflake.com/ - type: status_page -``` - -### Example: Stripe (SaaS API) -```yaml -externalDocumentationUrls: - - title: API changelog - url: https://stripe.com/docs/upgrades#api-changelog - type: api_release_history - - title: API reference - url: https://stripe.com/docs/api - type: api_reference - - title: API versioning and upgrades - url: https://stripe.com/docs/upgrades - type: migration_guide - - title: Authentication - url: https://stripe.com/docs/keys - type: authentication_guide - - title: Rate limits - url: https://stripe.com/docs/rate-limits - type: rate_limits - - title: Stripe Status - url: https://status.stripe.com/ - type: status_page -``` - -### Example: Postgres (Database) -```yaml -externalDocumentationUrls: - - title: PostgreSQL documentation - url: https://www.postgresql.org/docs/current/ - type: api_reference - - title: SQL commands - url: https://www.postgresql.org/docs/current/sql-commands.html - type: sql_reference - - title: Client authentication - url: https://www.postgresql.org/docs/current/client-authentication.html - type: authentication_guide - - title: Database roles and privileges - url: https://www.postgresql.org/docs/current/user-manag.html - type: permissions_scopes -``` - -## Regenerating Schema After Changes - -If you modify the schema enum in `ConnectorMetadataDefinitionV0.yaml`, regenerate the generated models: - -```bash -cd airbyte-ci/connectors/metadata_service/lib -poetry install -poetry run poe generate-models -``` - -This will update: -- `metadata_service/models/generated/ConnectorMetadataDefinitionV0.json` -- `metadata_service/models/generated/ConnectorMetadataDefinitionV0.py` - -## Additional Resources - -- [Connector Metadata Schema](../lib/metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml) -- [Metadata Service README](../lib/README.md) -- [Connector Development Guide](https://docs.airbyte.com/connector-development/) diff --git a/airbyte-ci/connectors/metadata_service/lib/README.md b/airbyte-ci/connectors/metadata_service/lib/README.md deleted file mode 100644 index 2ea1156757b1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# Connector Metadata Service Library - -This submodule is responsible for managing all the logic related to validating, uploading, and managing connector metadata. - -## Installation - -To use this submodule, it is recommended that you use Poetry to manage dependencies. - -``` -poetry install -``` - -### Node.js Requirement - -The model generation process also requires Node.js to bundle JSON schemas. Install Node.js: - -- On macOS: `brew install node` -- On Ubuntu/Debian: `sudo apt-get install nodejs npm` -- On other systems: https://nodejs.org/ - -Node.js dependencies will be automatically installed when running `poetry run poe generate-models`. - -## Generating Models - -This submodule includes a tool for generating Python models from JSON Schema specifications. To generate the models, we use the library [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator). The generated models are stored in `models/generated`. - -To generate the models, run the following command: - -```bash -poetry run poe generate-models - -``` - -This will read the JSON Schema specifications in `models/src` and generate Python models in `models/generated`. - -## Running Tests - -```bash -poetry run pytest -``` - -## Changelog - -### 0.24.1 -Update Python version requirement from 3.10 to 3.11. - -## Validating Metadata Files - -To be considered valid, a connector must have a metadata.yaml file which must conform to the [ConnectorMetadataDefinitionV0](./metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml) schema, and a documentation file. - -The paths to both files must be passed to the validate command. - -```bash -poetry run metadata_service validate tests/fixtures/metadata_validate/valid/metadata_simple.yaml tests/fixtures/doc.md -``` - -## Useful Commands - -### Replicate Production Data in your Development Bucket - -This will replicate all the production data to your development bucket. This is useful for testing the metadata service with real up to date data. - -_💡 Note: A prerequisite is you have [gsutil](https://cloud.google.com/storage/docs/gsutil) installed and have run `gsutil auth login`_ - -_⚠️ Warning: Its important to know that this will remove ANY files you have in your destination buckets as it calls `gsutil rsync` with `-d` enabled._ - -```bash -TARGET_BUCKET= poetry run poe replicate-prod -``` - -### Copy specific connector version to your Development Bucket - -This will copy the specified connector version to your development bucket. This is useful for testing the metadata service with a specific version of a connector. - -_💡 Note: A prerequisite is you have [gsutil](https://cloud.google.com/storage/docs/gsutil) installed and have run `gsutil auth login`_ - -```bash -TARGET_BUCKET= CONNECTOR="airbyte/source-stripe" VERSION="3.17.0-preview.ea013c8" poetry run poe copy-connector-from-prod -``` - -### Promote Connector Version to Latest - -This will promote the specified connector version to the latest version in the registry. This is useful for creating a mocked registry in which a prerelease connector is treated as if it was already published. - -_💡 Note: A prerequisite is you have [gsutil](https://cloud.google.com/storage/docs/gsutil) installed and have run `gsutil auth login`_ - -_⚠️ Warning: Its important to know that this will remove ANY existing files in the latest folder that are not in the versioned folder as it calls `gsutil rsync` with `-d` enabled._ - -```bash -TARGET_BUCKET= CONNECTOR="airbyte/source-stripe" VERSION="3.17.0-preview.ea013c8" poetry run poe promote-connector-to-latest -``` diff --git a/airbyte-ci/connectors/metadata_service/lib/bin/bundle-schemas.js b/airbyte-ci/connectors/metadata_service/lib/bin/bundle-schemas.js deleted file mode 100755 index 003d71e7a266..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/bin/bundle-schemas.js +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env node - -/** - * Bundle JSON schemas using @apidevtools/json-schema-ref-parser - * This script resolves all $ref references in the schema files and creates a single bundled schema. - */ - -const $RefParser = require('@apidevtools/json-schema-ref-parser'); -const fs = require('fs'); -const path = require('path'); - -const YAML_DIR = 'metadata_service/models/src'; -const OUTPUT_DIR = 'metadata_service/models/generated'; -const ENTRY_SCHEMA = path.join(YAML_DIR, 'ConnectorMetadataDefinitionV0.yaml'); -const BUNDLE_OUTPUT = path.join(OUTPUT_DIR, 'ConnectorMetadataDefinitionV0.json'); - -async function bundleSchemas() { - try { - console.log('📦 Bundling JSON schemas...'); - console.log(` Entry schema: ${ENTRY_SCHEMA}`); - console.log(` Output: ${BUNDLE_OUTPUT}`); - - if (!fs.existsSync(YAML_DIR)) { - console.error(`❌ Error: The yaml directory does not exist: ${YAML_DIR}`); - process.exit(1); - } - - if (!fs.existsSync(OUTPUT_DIR)) { - fs.mkdirSync(OUTPUT_DIR, { recursive: true }); - } - - const schema = await $RefParser.bundle(ENTRY_SCHEMA, { - dereference: { - circular: 'ignore' // Handle circular references gracefully - } - }); - - fs.writeFileSync(BUNDLE_OUTPUT, JSON.stringify(schema, null, 2)); - - console.log('✅ Successfully bundled schema to', BUNDLE_OUTPUT); - console.log(' This bundled schema can be used for IDE validation and other tools.'); - } catch (error) { - console.error('❌ Error bundling schemas:', error.message); - process.exit(1); - } -} - -bundleSchemas(); diff --git a/airbyte-ci/connectors/metadata_service/lib/bin/generate-metadata-models.sh b/airbyte-ci/connectors/metadata_service/lib/bin/generate-metadata-models.sh deleted file mode 100755 index 4f5790990b91..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/bin/generate-metadata-models.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -set -e - - -YAML_DIR=metadata_service/models/src -OUTPUT_DIR=metadata_service/models/generated - -# Ensure the yaml directory exists -if [ ! -d "$YAML_DIR" ]; then - echo "The yaml directory does not exist: $YAML_DIR" - exit 1 -fi - - -rm -rf "$OUTPUT_DIR"/*.py -mkdir -p "$OUTPUT_DIR" - -echo "# generated by generate-metadata-models" > "$OUTPUT_DIR"/__init__.py - -for f in "$YAML_DIR"/*.yaml; do - filename_wo_ext=$(basename "$f" | cut -d . -f 1) - echo "from .$filename_wo_ext import *" >> "$OUTPUT_DIR"/__init__.py - - datamodel-codegen \ - --input "$YAML_DIR/$filename_wo_ext.yaml" \ - --output "$OUTPUT_DIR/$filename_wo_ext.py" \ - --use-title-as-name \ - --use-double-quotes \ - --enum-field-as-literal all \ - --disable-timestamp -done - -echo "" -echo "Generating bundled JSON schema..." -node bin/bundle-schemas.js diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/commands.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/commands.py deleted file mode 100644 index 203eb59746d7..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/commands.py +++ /dev/null @@ -1,252 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import logging -import pathlib - -import click -import sentry_sdk -from pydantic import ValidationError - -from metadata_service.constants import METADATA_FILE_NAME, VALID_REGISTRIES -from metadata_service.gcs_upload import ( - MetadataDeleteInfo, - MetadataUploadInfo, - delete_release_candidate_from_gcs, - promote_release_candidate_in_gcs, - upload_metadata_to_gcs, -) -from metadata_service.registry import generate_and_persist_connector_registry -from metadata_service.registry_entry import generate_and_persist_registry_entry -from metadata_service.registry_report import generate_and_persist_registry_report -from metadata_service.sentry import setup_sentry -from metadata_service.specs_secrets_mask import generate_and_persist_specs_secrets_mask -from metadata_service.stale_metadata_report import generate_and_publish_stale_metadata_report -from metadata_service.validators.metadata_validator import PRE_UPLOAD_VALIDATORS, ValidatorOptions, validate_and_load - - -def setup_logging(debug: bool = False): - """Configure logging for the CLI.""" - level = logging.DEBUG if debug else logging.INFO - logging.basicConfig( - level=level, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - handlers=[logging.StreamHandler()], - ) - # Suppress logging from the following libraries - logging.getLogger("urllib3").setLevel(logging.WARNING) - logging.getLogger("slack_sdk.web.base_client").setLevel(logging.WARNING) - logging.getLogger("google.resumable_media").setLevel(logging.WARNING) - - -logger = logging.getLogger(__name__) - - -def log_metadata_upload_info(metadata_upload_info: MetadataUploadInfo): - """Log the results of the metadata upload.""" - for file in metadata_upload_info.uploaded_files: - if file.uploaded: - click.secho(f"File:{file.id} for {metadata_upload_info.metadata_file_path} was uploaded to {file.blob_id}.", fg="green") - else: - click.secho( - f"File:{file.id} for {metadata_upload_info.metadata_file_path} was not uploaded.", - fg="yellow", - ) - - -def log_metadata_deletion_info(metadata_deletion_info: MetadataDeleteInfo): - """Log the results of the metadata deletion.""" - for remote_file in metadata_deletion_info.deleted_files: - if remote_file.deleted: - click.secho(f"The {remote_file.description} was deleted ({remote_file.blob_id}).", fg="green") - else: - click.secho( - f"The {remote_file.description} was not deleted ({remote_file.blob_id}).", - fg="red", - ) - - -@click.group(help="Airbyte Metadata Service top-level command group.") -@click.option("--debug", is_flag=True, help="Enable debug logging", default=False) -def metadata_service(debug: bool): - """Top-level command group with logging configuration.""" - setup_sentry() - setup_logging(debug) - - -@metadata_service.command(help="Validate a given metadata YAML file.") -@click.argument("metadata_file_path", type=click.Path(exists=True, path_type=pathlib.Path), required=True) -@click.argument("docs_path", type=click.Path(exists=True, path_type=pathlib.Path), required=True) -def validate(metadata_file_path: pathlib.Path, docs_path: pathlib.Path): - metadata_file_path = metadata_file_path if not metadata_file_path.is_dir() else metadata_file_path / METADATA_FILE_NAME - - click.echo(f"Validating {metadata_file_path}...") - metadata, error = validate_and_load(metadata_file_path, PRE_UPLOAD_VALIDATORS, ValidatorOptions(docs_path=str(docs_path))) - if metadata: - click.echo(f"{metadata_file_path} is a valid ConnectorMetadataDefinitionV0 YAML file.") - else: - click.echo(f"{metadata_file_path} is not a valid ConnectorMetadataDefinitionV0 YAML file.") - click.echo(str(error)) - exit(1) - - -@metadata_service.command(help="Upload a metadata YAML file to a GCS bucket.") -@click.argument("metadata-file-path", type=click.Path(exists=True, path_type=pathlib.Path), required=True) -@click.argument("docs-path", type=click.Path(exists=True, path_type=pathlib.Path), required=True) -@click.argument("bucket-name", type=click.STRING, required=True) -@click.option("--prerelease", type=click.STRING, required=False, default=None, help="The prerelease tag of the connector.") -@click.option("--disable-dockerhub-checks", is_flag=True, help="Disable 'image exists on DockerHub' validations.", default=False) -def upload(metadata_file_path: pathlib.Path, docs_path: pathlib.Path, bucket_name: str, prerelease: str, disable_dockerhub_checks: bool): - metadata_file_path = metadata_file_path if not metadata_file_path.is_dir() else metadata_file_path / METADATA_FILE_NAME - validator_opts = ValidatorOptions( - docs_path=str(docs_path), prerelease_tag=prerelease, disable_dockerhub_checks=disable_dockerhub_checks - ) - try: - upload_info = upload_metadata_to_gcs(bucket_name, metadata_file_path, validator_opts) - log_metadata_upload_info(upload_info) - except (ValidationError, FileNotFoundError) as e: - click.secho(f"The metadata file could not be uploaded: {str(e)}", fg="red") - exit(1) - if upload_info.metadata_uploaded: - exit(0) - else: - exit(5) - - -@metadata_service.command(help="Generate and publish a stale metadata report to Slack.") -@click.argument("bucket-name", type=click.STRING, required=True) -def publish_stale_metadata_report(bucket_name: str): - click.echo(f"Starting stale metadata report for bucket: {bucket_name}") - logger.debug("Starting stale metadata report generation and publishing process") - try: - report_published, error_message = generate_and_publish_stale_metadata_report(bucket_name) - if not report_published: - logger.warning(f"Failed to publish the report to Slack: '{error_message}'.") - click.secho(f"WARNING: The stale metadata report could not be published: '{error_message}'", fg="red") - exit(1) - else: - click.secho(f"Stale metadata report for bucket: {bucket_name} completed successfully", fg="green") - logger.debug("Stale metadata report generation and publishing process completed.") - except Exception as e: - logger.exception(f"A fatal error occurred when generating and publishing the stale metadata report") - click.secho(f"FATAL ERROR: The stale metadata report could not be published: '{e}'", fg="red") - exit(1) - - -@metadata_service.command(help="Rollback a release candidate by deleting its metadata files from a GCS bucket.") -@click.argument("connector-docker-repository", type=click.STRING) -@click.argument("connector-version", type=click.STRING) -@click.argument("bucket-name", type=click.STRING) -def rollback_release_candidate(connector_docker_repository: str, connector_version: str, bucket_name: str): - try: - deletion_info = delete_release_candidate_from_gcs(bucket_name, connector_docker_repository, connector_version) - log_metadata_deletion_info(deletion_info) - except (FileNotFoundError, ValueError) as e: - click.secho(f"The release candidate could not be deleted: {str(e)}", fg="red") - exit(1) - - -@metadata_service.command(help="Promote a release candidate by moving its metadata files to the main release folder in a GCS bucket.") -@click.argument("connector-docker-repository", type=click.STRING) -@click.argument("connector-version", type=click.STRING) -@click.argument("bucket-name", type=click.STRING) -def promote_release_candidate(connector_docker_repository: str, connector_version: str, bucket_name: str): - try: - upload_info, deletion_info = promote_release_candidate_in_gcs(bucket_name, connector_docker_repository, connector_version) - log_metadata_upload_info(upload_info) - log_metadata_deletion_info(deletion_info) - except (FileNotFoundError, ValueError) as e: - click.secho(f"The release candidate could not be promoted: {str(e)}", fg="red") - exit(1) - - -@metadata_service.command(help="Generate the cloud registry and persist it to GCS.") -@click.argument("bucket-name", type=click.STRING, required=True) -@click.argument("registry-type", type=click.Choice(VALID_REGISTRIES), required=True) -@sentry_sdk.trace -def generate_connector_registry(bucket_name: str, registry_type: str): - # Set Sentry context for the generate_registry command - sentry_sdk.set_tag("command", "generate_registry") - sentry_sdk.set_tag("bucket_name", bucket_name) - sentry_sdk.set_tag("registry_type", registry_type) - - logger.info(f"Starting {registry_type} registry generation and upload process.") - try: - generate_and_persist_connector_registry(bucket_name, registry_type) - logger.info(f"SUCCESS: {registry_type} registry generation and upload process completed successfully.") - sentry_sdk.set_tag("operation_success", True) - except Exception as e: - sentry_sdk.set_tag("operation_success", False) - sentry_sdk.capture_exception(e) - logger.exception(f"FATAL ERROR: An error occurred when generating and persisting the {registry_type} registry") - exit(1) - - -@metadata_service.command(help="Generate the specs secrets mask and persist it to GCS.") -@click.argument("bucket-name", type=click.STRING, required=True) -@sentry_sdk.trace -def generate_specs_secrets_mask(bucket_name: str): - # Set Sentry context for the generate_specs_secrets_mask command - sentry_sdk.set_tag("command", "generate_specs_secrets_mask") - sentry_sdk.set_tag("bucket_name", bucket_name) - - logger.info("Starting specs secrets mask generation and upload process.") - try: - generate_and_persist_specs_secrets_mask(bucket_name) - sentry_sdk.set_tag("operation_success", True) - logger.info("Specs secrets mask generation and upload process completed successfully.") - except Exception as e: - sentry_sdk.set_tag("operation_success", False) - sentry_sdk.capture_exception(e) - logger.exception(f"FATAL ERROR: An error occurred when generating and persisting the specs secrets mask") - exit(1) - - -@metadata_service.command(help="Generate the registry entry and persist it to GCS.") -@click.option("--bucket-name", type=click.STRING, required=True) -@click.option("--metadata-file-path", type=click.STRING, required=True) -@click.option("--registry-type", type=click.Choice(VALID_REGISTRIES), required=True) -@click.option("--docker-image-tag", type=click.STRING, required=True) -@click.option( - "--pre-release/--main-release", type=bool, is_flag=True, required=True, help="Whether this is a prerelease or mainrelease publish." -) -@sentry_sdk.trace -def generate_registry_entry( - bucket_name: str, metadata_file_path: pathlib.Path, registry_type: str, docker_image_tag: str, pre_release: bool -): - # Set Sentry context for the generate_registry_entry command - sentry_sdk.set_tag("command", "generate_registry_entry") - sentry_sdk.set_tag("bucket_name", bucket_name) - - logger.info("Starting registry entry generation and upload process.") - try: - generate_and_persist_registry_entry(bucket_name, metadata_file_path, registry_type, docker_image_tag, pre_release) - sentry_sdk.set_tag("operation_success", True) - logger.info("Registry entry generation and upload process completed successfully.") - except Exception as e: - sentry_sdk.set_tag("operation_success", False) - sentry_sdk.capture_exception(e) - logger.exception(f"FATAL ERROR: An error occurred when generating and persisting the registry entry") - exit(1) - - -@metadata_service.command(help="Generate the registry report and persist it to GCS.") -@click.argument("bucket-name", type=click.STRING, required=True) -@sentry_sdk.trace -def generate_registry_report(bucket_name: str): - # Set Sentry context for the generate_specs_secrets_mask command - sentry_sdk.set_tag("command", "generate_registry_report") - sentry_sdk.set_tag("bucket_name", bucket_name) - - logger.info("Starting registry report generation and upload process.") - try: - generate_and_persist_registry_report(bucket_name) - sentry_sdk.set_tag("operation_success", True) - logger.info("Registry report generation and upload process completed successfully.") - except Exception as e: - sentry_sdk.set_tag("operation_success", False) - sentry_sdk.capture_exception(e) - logger.exception(f"FATAL ERROR: An error occurred when generating and persisting the registry report") - exit(1) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/constants.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/constants.py deleted file mode 100644 index da78f9620709..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/constants.py +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import datetime -from typing import Optional - -CONNECTORS_PATH = "airbyte-integrations/connectors" -METADATA_FILE_NAME = "metadata.yaml" -MANIFEST_FILE_NAME = "manifest.yaml" -COMPONENTS_PY_FILE_NAME = "components.py" -ICON_FILE_NAME = "icon.svg" -METADATA_FOLDER = "metadata" -DOCS_FOLDER_PATH = "docs/integrations" -DOC_FILE_NAME = "doc.md" -DOC_INAPP_FILE_NAME = "doc.inapp.md" -COMPONENTS_ZIP_FILE_NAME = "components.zip" -COMPONENTS_ZIP_SHA256_FILE_NAME = "components.zip.sha256" -LATEST_GCS_FOLDER_NAME = "latest" -RELEASE_CANDIDATE_GCS_FOLDER_NAME = "release_candidate" -METADATA_CDN_BASE_URL = "https://connectors.airbyte.com/files" -DEFAULT_ASSET_URL = "https://storage.googleapis.com" - -VALID_REGISTRIES = ["oss", "cloud"] -REGISTRIES_FOLDER = "registries/v0" -ANALYTICS_BUCKET = "ab-analytics-connector-metrics" -ANALYTICS_FOLDER = "data/connector_quality_metrics" -PUBLIC_GCS_BASE_URL = "https://storage.googleapis.com/" - -VALID_REGISTRIES = ["oss", "cloud"] -REGISTRIES_FOLDER = "registries/v0" -ANALYTICS_BUCKET = "ab-analytics-connector-metrics" -ANALYTICS_FOLDER = "data/connector_quality_metrics" -PUBLIC_GCS_BASE_URL = "https://storage.googleapis.com/" - -GITHUB_REPO_NAME = "airbytehq/airbyte" -EXTENSIBILITY_TEAM_SLACK_TEAM_ID = "S08SQDL2RS9" # @oc-extensibility-critical-systems -STALE_REPORT_CHANNEL = "C05507UP11A" # #dev-connectors-extensibility-alerts -PUBLISH_UPDATE_CHANNEL = "C056HGD1QSW" # #connector-publish-updates -# We give 6 hours for the metadata to be updated -# This is an empirical value that we can adjust if needed -# When our auto-merge pipeline runs it can merge hundreds of up-to-date PRs following. -# Given our current publish concurrency of 10 runners, it can take up to 6 hours to publish all the connectors. -# A shorter grace period could lead to false positives in stale metadata detection. -PUBLISH_GRACE_PERIOD = datetime.timedelta(hours=6) -SLACK_NOTIFICATIONS_ENABLED = "true" - -SPECS_SECRETS_MASK_FILE_NAME = "specs_secrets_mask.yaml" - -CONNECTOR_DEPENDENCY_FOLDER = "connector_dependencies" -CONNECTOR_DEPENDENCY_FILE_NAME = "dependencies.json" - - -def get_public_url_for_gcs_file(bucket_name: str, file_path: str, cdn_url: Optional[str] = None) -> str: - """Get the public URL to a file in the GCS bucket. - - Args: - bucket_name: The name of the GCS bucket. - file_path: The path to the file in the bucket. - cdn_url: The base URL of the CDN that serves the bucket. - - Returns: - The public URL to the file. - """ - return f"{cdn_url}/{file_path}" if cdn_url else f"{DEFAULT_ASSET_URL}/{bucket_name}/{file_path}" diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/docker_hub.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/docker_hub.py deleted file mode 100644 index 924319ebff32..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/docker_hub.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import os -import time -from typing import Dict, Optional - -import requests - - -def get_docker_hub_auth_token() -> str: - docker_username = os.environ.get("DOCKER_HUB_USERNAME") - docker_password = os.environ.get("DOCKER_HUB_PASSWORD") - - if not (docker_username and docker_password): - raise ValueError("Please set the DOCKER_HUB_USERNAME and DOCKER_HUB_PASSWORD environment variables.") - - auth_url = "https://hub.docker.com/v2/users/login/" - auth_data = {"username": docker_username, "password": docker_password} - # make request look like docker client to avoid cloudflare blocks in CI - headers = {"User-Agent": "Docker-Client/24.0.0 (linux)"} - response = requests.post(auth_url, json=auth_data, headers=headers) - - if response.status_code != 200: - print(f"Failed to log in to Docker Hub - {response.status_code}: {response.text}") - raise ValueError("Failed to authenticate with Docker Hub. Please check your credentials.") - - token = response.json().get("token") - return token - - -def get_docker_hub_headers() -> Dict | None: - if not os.environ.get("DOCKER_HUB_USERNAME") or not os.environ.get("DOCKER_HUB_PASSWORD"): - # If the Docker Hub credentials are not provided (or are empty), we can only anonymously call the Docker Hub API. - # This will only work for public images and lead to a lower rate limit. - return {} - else: - token = get_docker_hub_auth_token() - return {"Authorization": f"JWT {token}"} if token else {} - - -def get_docker_hub_tags_and_digests( - image_name: str, - retries: int = 0, - wait_sec: int = 30, - next_page_url: str | None = None, - tags_and_digests: Dict[str, str] | None = None, - paginate: bool = True, -) -> Dict[str, str]: - """Find all released tags and digests for an image. - - Args: - image_name (str): The image name to get tags and digest - retries (int, optional): The number of times to retry the request. Defaults to 0. - wait_sec (int, optional): The number of seconds to wait between retries. Defaults to 30. - next_page_url (str | None, optional): The next DockerHub page to consume. Defaults to None. - tags_and_digest (Dict[str, str] | None, optional): The accumulated tags and digests for recursion. Defaults to None. - - Returns: - Dict[str, str]: Mapping of image tag to digest - """ - headers = get_docker_hub_headers() - tags_and_digests = tags_and_digests or {} - - if not next_page_url: - tags_url = f"https://registry.hub.docker.com/v2/repositories/{image_name}/tags" - else: - tags_url = next_page_url - - # Allow for retries as the DockerHub API is not always reliable with returning the latest publish. - for _ in range(retries + 1): - response = requests.get(tags_url, headers=headers) - if response.ok: - break - - # This is to handle the case when a connector has not ever been released yet. - if response.status_code == 404: - print(f"{tags_url} returned a 404. The connector might not be released yet.") - print(response) - return tags_and_digests - time.sleep(wait_sec) - - response.raise_for_status() - json_response = response.json() - tags_and_digests.update({result["name"]: result.get("digest") for result in json_response.get("results", [])}) - if paginate: - if next_page_url := json_response.get("next"): - tags_and_digests.update( - get_docker_hub_tags_and_digests( - image_name, retries=retries, wait_sec=wait_sec, next_page_url=next_page_url, tags_and_digests=tags_and_digests - ) - ) - return tags_and_digests - - -def get_latest_version_on_dockerhub(image_name: str) -> str | None: - tags_and_digests = get_docker_hub_tags_and_digests(image_name, retries=3, wait_sec=30) - if latest_digest := tags_and_digests.get("latest"): - for tag, digest in tags_and_digests.items(): - if digest == latest_digest and tag != "latest": - return tag - return None - - -def is_image_on_docker_hub(image_name: str, version: str, digest: Optional[str] = None, retries: int = 0, wait_sec: int = 30) -> bool: - """Check if a given image and version exists on Docker Hub. - - Args: - image_name (str): The name of the image to check. - version (str): The version of the image to check. - digest (str, optional): The digest of the image to check. Defaults to None. - retries (int, optional): The number of times to retry the request. Defaults to 0. - wait_sec (int, optional): The number of seconds to wait between retries. Defaults to 30. - Returns: - bool: True if the image and version exists on Docker Hub, False otherwise. - """ - - headers = get_docker_hub_headers() - - tag_url = f"https://registry.hub.docker.com/v2/repositories/{image_name}/tags/{version}" - - # Allow for retries as the DockerHub API is not always reliable with returning the latest publish. - for _ in range(retries + 1): - response = requests.get(tag_url, headers=headers) - if response.ok: - break - time.sleep(wait_sec) - - if not response.ok: - response.raise_for_status() - return False - - # If a digest is provided, check that it matches the digest of the image on Docker Hub. - if digest is not None: - return f"sha256:{digest}" == response.json()["digest"] - return True diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/gcs_upload.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/gcs_upload.py deleted file mode 100644 index c555ba2e13cf..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/gcs_upload.py +++ /dev/null @@ -1,679 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import logging -import re -import tempfile -from dataclasses import dataclass -from pathlib import Path -from typing import List, NamedTuple, Optional, Tuple - -import git -import requests -import yaml -from google.cloud import storage -from pydash import set_ -from pydash.objects import get - -from metadata_service.constants import ( - COMPONENTS_PY_FILE_NAME, - COMPONENTS_ZIP_FILE_NAME, - COMPONENTS_ZIP_SHA256_FILE_NAME, - DOC_FILE_NAME, - DOC_INAPP_FILE_NAME, - ICON_FILE_NAME, - LATEST_GCS_FOLDER_NAME, - MANIFEST_FILE_NAME, - METADATA_FILE_NAME, - METADATA_FOLDER, - RELEASE_CANDIDATE_GCS_FOLDER_NAME, -) -from metadata_service.helpers.files import compute_gcs_md5, create_zip_and_get_sha256 -from metadata_service.helpers.gcs import get_gcs_storage_client -from metadata_service.models.generated.ConnectorMetadataDefinitionV0 import ConnectorMetadataDefinitionV0 -from metadata_service.models.generated.GitInfo import GitInfo -from metadata_service.models.transform import to_json_sanitized_dict -from metadata_service.validators.metadata_validator import POST_UPLOAD_VALIDATORS, ValidatorOptions, validate_and_load - -# 🧩 TYPES - - -@dataclass(frozen=True) -class UploadedFile: - id: str - uploaded: bool - blob_id: Optional[str] - - -@dataclass(frozen=True) -class DeletedFile: - id: str - deleted: bool - description: str - blob_id: Optional[str] - - -@dataclass(frozen=True) -class MetadataUploadInfo: - metadata_uploaded: bool - metadata_file_path: str - uploaded_files: List[UploadedFile] - - -@dataclass(frozen=True) -class MetadataDeleteInfo: - metadata_deleted: bool - deleted_files: List[DeletedFile] - - -class MaybeUpload(NamedTuple): - uploaded: bool - blob_id: str - - -@dataclass -class ManifestOnlyFilePaths: - zip_file_path: Path | None - sha256_file_path: Path | None - sha256: str | None - manifest_file_path: Path - - -# 🛣️ FILES AND PATHS - - -def get_doc_local_file_path(docs_path: Path, inapp: bool) -> Optional[Path]: - extension = ".inapp.md" if inapp else ".md" - return docs_path.with_suffix(extension) - - -def get_manifest_only_file_paths(working_directory: Path) -> ManifestOnlyFilePaths: - """Create a zip file for components if they exist and return its SHA256 hash.""" - yaml_manifest_file_path = working_directory / MANIFEST_FILE_NAME - components_py_file_path = working_directory / COMPONENTS_PY_FILE_NAME - - if not components_py_file_path.exists(): - return ManifestOnlyFilePaths( - zip_file_path=None, - sha256_file_path=None, - sha256=None, - manifest_file_path=yaml_manifest_file_path, - ) - - with ( - tempfile.NamedTemporaryFile(mode="wb", suffix=".zip", delete=False) as zip_tmp_file, - tempfile.NamedTemporaryFile(mode="w", suffix=".sha256", delete=False) as sha256_tmp_file, - ): - python_components_zip_file_path = Path(zip_tmp_file.name) - python_components_zip_sha256_file_path = Path(sha256_tmp_file.name) - - files_to_zip: List[Path] = [components_py_file_path, yaml_manifest_file_path] - components_zip_sha256 = create_zip_and_get_sha256(files_to_zip, python_components_zip_file_path) - sha256_tmp_file.write(components_zip_sha256) - - return ManifestOnlyFilePaths( - zip_file_path=python_components_zip_file_path, - sha256_file_path=python_components_zip_sha256_file_path, - sha256=components_zip_sha256, - manifest_file_path=yaml_manifest_file_path, - ) - - -def _write_metadata_to_tmp_file(metadata_dict: dict) -> Path: - """Write the metadata to a temporary file.""" - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp_file: - yaml.dump(metadata_dict, tmp_file) - return Path(tmp_file.name) - - -# 🛠️ HELPERS - - -def _safe_load_metadata_file(metadata_file_path: Path) -> dict: - try: - metadata = yaml.safe_load(metadata_file_path.read_text()) - if metadata is None or not isinstance(metadata, dict): - raise ValueError(f"Validation error: Metadata file {metadata_file_path} is invalid yaml.") - return metadata - except Exception as e: - raise ValueError(f"Validation error: Metadata file {metadata_file_path} is invalid yaml: {e}") - - -def _any_uploaded(uploaded_files: List[UploadedFile], keys: List[str]) -> bool: - """Check if the list of uploaded files contains any of the provided keys.""" - for uploaded_file in uploaded_files: - if uploaded_file.id in keys: - return True - return False - - -def _commit_to_git_info(commit: git.Commit) -> GitInfo: - return GitInfo( - commit_sha=commit.hexsha, - commit_timestamp=commit.authored_datetime, - commit_author=commit.author.name, - commit_author_email=commit.author.email, - ) - - -def _get_git_info_for_file(original_metadata_file_path: Path) -> Optional[GitInfo]: - """ - Add additional information to the metadata file before uploading it to GCS. - - e.g. The git commit hash, the date of the commit, the author of the commit, etc. - - """ - try: - repo = git.Repo(search_parent_directories=True) - - # get the commit hash for the last commit that modified the metadata file - commit_sha = repo.git.log("-1", "--format=%H", str(original_metadata_file_path)) - - commit = repo.commit(commit_sha) - return _commit_to_git_info(commit) - except git.exc.InvalidGitRepositoryError: - logging.warning(f"Metadata file {original_metadata_file_path} is not in a git repository, skipping author info attachment.") - return None - except git.exc.GitCommandError as e: - if "unknown revision or path not in the working tree" in str(e): - logging.warning(f"Metadata file {original_metadata_file_path} is not tracked by git, skipping author info attachment.") - return None - else: - raise e - - -# 🚀 UPLOAD - - -def _save_blob_to_gcs(blob_to_save: storage.blob.Blob, file_path: Path, disable_cache: bool = False) -> bool: - """Uploads a file to the bucket.""" - print(f"Uploading {file_path} to {blob_to_save.name}...") - - # Set Cache-Control header to no-cache to avoid caching issues - # This is IMPORTANT because if we don't set this header, the metadata file will be cached by GCS - # and the next time we try to download it, we will get the stale version - if disable_cache: - blob_to_save.cache_control = "no-cache" - - blob_to_save.upload_from_filename(file_path) - - return True - - -def upload_file_if_changed( - local_file_path: Path, bucket: storage.bucket.Bucket, blob_path: str, disable_cache: bool = False -) -> MaybeUpload: - """Upload a file to GCS if it has changed.""" - local_file_md5_hash = compute_gcs_md5(local_file_path) - remote_blob = bucket.blob(blob_path) - - # reload the blob to get the md5_hash - if remote_blob.exists(): - remote_blob.reload() - - remote_blob_md5_hash = remote_blob.md5_hash if remote_blob.exists() else None - - print(f"Local {local_file_path} md5_hash: {local_file_md5_hash}") - print(f"Remote {blob_path} md5_hash: {remote_blob_md5_hash}") - - if local_file_md5_hash != remote_blob_md5_hash: - uploaded = _save_blob_to_gcs(remote_blob, local_file_path, disable_cache=disable_cache) - return MaybeUpload(uploaded, remote_blob.id) - - return MaybeUpload(False, remote_blob.id) - - -def _file_upload( - local_path: Path | None, - gcp_connector_dir: str, - bucket: storage.bucket.Bucket, - file_key: str, - *, - upload_as_version: bool, - upload_as_latest: bool, - skip_if_not_exists: bool = True, - disable_cache: bool = False, - version_folder: Optional[str] = None, - override_destination_file_name: str | None = None, -) -> tuple[UploadedFile, UploadedFile]: - """Upload a file to GCS. - - Optionally upload it as a versioned file and/or as the latest version. - - Args: - local_path: Path to the file to upload. - gcp_connector_dir: Path to the connector folder in GCS. This is the parent folder, - containing the versioned and "latest" folders as its subdirectories. - bucket: GCS bucket to upload the file to. - upload_as_version: The version to upload the file as or 'False' to skip uploading - the versioned copy. - upload_as_latest: Whether to upload the file as the latest version. - skip_if_not_exists: Whether to skip the upload if the file does not exist. Otherwise, - an exception will be raised if the file does not exist. - - Returns: Tuple of two UploadInfo objects, each containing a boolean indicating whether the file was - uploaded, the blob id, and the description. The first tuple is for the versioned file, the second for the - latest file. - """ - if upload_as_version and not version_folder: - raise ValueError("version_folder must be provided if upload_as_version is True") - - latest_file_key = f"latest_{file_key}" - versioned_file_key = f"versioned_{file_key}" - versioned_file_info = UploadedFile(id=versioned_file_key, uploaded=False, blob_id=None) - latest_file_info = UploadedFile(id=latest_file_key, uploaded=False, blob_id=None) - if not local_path or not local_path.exists(): - msg = f"Expected to find file at {local_path}, but none was found." - if skip_if_not_exists: - logging.warning(msg) - return versioned_file_info, latest_file_info - - raise FileNotFoundError(msg) - - file_name = local_path.name if override_destination_file_name is None else override_destination_file_name - - if upload_as_version: - remote_upload_path = f"{gcp_connector_dir}/{version_folder}" - versioned_uploaded, versioned_blob_id = upload_file_if_changed( - local_file_path=local_path, - bucket=bucket, - blob_path=f"{remote_upload_path}/{file_name}", - disable_cache=disable_cache, - ) - versioned_file_info = UploadedFile(id=versioned_file_key, uploaded=versioned_uploaded, blob_id=versioned_blob_id) - - if upload_as_latest: - remote_upload_path = f"{gcp_connector_dir}/{LATEST_GCS_FOLDER_NAME}" - latest_uploaded, latest_blob_id = upload_file_if_changed( - local_file_path=local_path, - bucket=bucket, - blob_path=f"{remote_upload_path}/{file_name}", - disable_cache=disable_cache, - ) - latest_file_info = UploadedFile(id=latest_file_key, uploaded=latest_uploaded, blob_id=latest_blob_id) - - return versioned_file_info, latest_file_info - - -# 🔧 METADATA MODIFICATIONS - - -def _apply_prerelease_overrides(metadata_dict: dict, validator_opts: ValidatorOptions) -> dict: - """Apply any prerelease overrides to the metadata file before uploading it to GCS.""" - if validator_opts.prerelease_tag is None: - return metadata_dict - - # replace any dockerImageTag references with the actual tag - # this includes metadata.data.dockerImageTag, metadata.data.registryOverrides[].dockerImageTag - # where registries is a dictionary of registry name to registry object - metadata_dict["data"]["dockerImageTag"] = validator_opts.prerelease_tag - for registry in get(metadata_dict, "data.registryOverrides", {}).values(): - if "dockerImageTag" in registry: - registry["dockerImageTag"] = validator_opts.prerelease_tag - - return metadata_dict - - -def _apply_author_info_to_metadata_file(metadata_dict: dict, original_metadata_file_path: Path) -> dict: - """Apply author info to the metadata file before uploading it to GCS.""" - git_info = _get_git_info_for_file(original_metadata_file_path) - if git_info: - # Apply to the nested / optional field at metadata.data.generated.git - git_info_dict = to_json_sanitized_dict(git_info, exclude_none=True) - metadata_dict = set_(metadata_dict, "data.generated.git", git_info_dict) - return metadata_dict - - -def _apply_python_components_sha_to_metadata_file( - metadata_dict: dict, - python_components_sha256: Optional[str] = None, -) -> dict: - """If a `components.py` file is required, store the necessary information in the metadata. - - This adds a `required=True` flag and the sha256 hash of the `python_components.zip` file. - - This is a no-op if `python_components_sha256` is not provided. - """ - if python_components_sha256: - metadata_dict = set_(metadata_dict, "data.generated.pythonComponents.required", True) - metadata_dict = set_( - metadata_dict, - "data.generated.pythonComponents.sha256", - python_components_sha256, - ) - - return metadata_dict - - -def _apply_sbom_url_to_metadata_file(metadata_dict: dict) -> dict: - """Apply sbom url to the metadata file before uploading it to GCS.""" - try: - sbom_url = f"https://connectors.airbyte.com/files/sbom/{metadata_dict['data']['dockerRepository']}/{metadata_dict['data']['dockerImageTag']}.spdx.json" - except KeyError: - return metadata_dict - response = requests.head(sbom_url) - if response.ok: - metadata_dict = set_(metadata_dict, "data.generated.sbomUrl", sbom_url) - return metadata_dict - - -def _apply_modifications_to_metadata_file( - original_metadata_file_path: Path, - validator_opts: ValidatorOptions, - components_zip_sha256: str | None = None, -) -> Path: - """Apply modifications to the metadata file before uploading it to GCS. - - e.g. The git commit hash, the date of the commit, the author of the commit, etc. - - Args: - original_metadata_file_path (Path): Path to the original metadata file. - validator_opts (ValidatorOptions): Options to use when validating the metadata file. - components_zip_sha256 (str): The sha256 hash of the `python_components.zip` file. This is - required if the `python_components.zip` file is present. - """ - metadata = _safe_load_metadata_file(original_metadata_file_path) - metadata = _apply_prerelease_overrides(metadata, validator_opts) - metadata = _apply_author_info_to_metadata_file(metadata, original_metadata_file_path) - metadata = _apply_python_components_sha_to_metadata_file(metadata, components_zip_sha256) - metadata = _apply_sbom_url_to_metadata_file(metadata) - return _write_metadata_to_tmp_file(metadata) - - -# 💎 Main Logic - - -def upload_metadata_to_gcs(bucket_name: str, metadata_file_path: Path, validator_opts: ValidatorOptions) -> MetadataUploadInfo: - """Upload a metadata file to a GCS bucket. - - If the per 'version' key already exists it won't be overwritten. - Also updates the 'latest' key on each new version. - - Args: - bucket_name (str): Name of the GCS bucket to which the metadata file will be uploade. - metadata_file_path (Path): Path to the metadata file. - service_account_file_path (Path): Path to the JSON file with the service account allowed to read and write on the bucket. - prerelease_tag (Optional[str]): Whether the connector is a prerelease_tag or not. - Returns: - Tuple[bool, str]: Whether the metadata file was uploaded and its blob id. - """ - # Get our working directory - working_directory = metadata_file_path.parent - manifest_only_file_info = get_manifest_only_file_paths(working_directory) - - metadata_file_path = _apply_modifications_to_metadata_file( - original_metadata_file_path=metadata_file_path, - validator_opts=validator_opts, - components_zip_sha256=manifest_only_file_info.sha256, - ) - - metadata, error = validate_and_load(metadata_file_path, POST_UPLOAD_VALIDATORS, validator_opts) - if metadata is None: - raise ValueError(f"Metadata file {metadata_file_path} is invalid for uploading: {error}") - - is_pre_release = validator_opts.prerelease_tag is not None - is_release_candidate = "-rc" in metadata.data.dockerImageTag - should_upload_release_candidate = is_release_candidate and not is_pre_release - should_upload_latest = not is_release_candidate and not is_pre_release - - storage_client = get_gcs_storage_client() - bucket = storage_client.bucket(bucket_name) - docs_path = Path(validator_opts.docs_path) - gcp_connector_dir = f"{METADATA_FOLDER}/{metadata.data.dockerRepository}" - - # Upload version metadata and doc - # If the connector is a pre-release, we use the pre-release tag as the version - # Otherwise, we use the dockerImageTag from the metadata - version_folder = metadata.data.dockerImageTag if not is_pre_release else validator_opts.prerelease_tag - - # Start uploading files - uploaded_files = [] - - # Metadata upload - metadata_files_uploaded = _file_upload( - file_key="metadata", - local_path=metadata_file_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - version_folder=version_folder, - upload_as_version=True, - upload_as_latest=should_upload_latest, - disable_cache=True, - override_destination_file_name=METADATA_FILE_NAME, - ) - uploaded_files.extend(metadata_files_uploaded) - - # Release candidate upload - # We just upload the current metadata to the "release_candidate" path - # The doc and inapp doc are not uploaded, which means that the release candidate will still point to the latest doc - if should_upload_release_candidate: - release_candidate_files_uploaded = _file_upload( - file_key="release_candidate", - local_path=metadata_file_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - version_folder=RELEASE_CANDIDATE_GCS_FOLDER_NAME, - upload_as_version=True, - upload_as_latest=False, - disable_cache=True, - override_destination_file_name=METADATA_FILE_NAME, - ) - uploaded_files.extend(release_candidate_files_uploaded) - - # Icon upload - - icon_files_uploaded = _file_upload( - file_key="icon", - local_path=working_directory / ICON_FILE_NAME, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=False, - upload_as_latest=should_upload_latest, - ) - uploaded_files.extend(icon_files_uploaded) - - # Doc upload - - local_doc_path = get_doc_local_file_path(docs_path, inapp=False) - doc_files_uploaded = _file_upload( - file_key="doc", - local_path=local_doc_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=True, - version_folder=version_folder, - upload_as_latest=should_upload_latest, - override_destination_file_name=DOC_FILE_NAME, - ) - uploaded_files.extend(doc_files_uploaded) - - local_inapp_doc_path = get_doc_local_file_path(docs_path, inapp=True) - inapp_doc_files_uploaded = _file_upload( - file_key="inapp_doc", - local_path=local_inapp_doc_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=True, - version_folder=version_folder, - upload_as_latest=should_upload_latest, - override_destination_file_name=DOC_INAPP_FILE_NAME, - ) - uploaded_files.extend(inapp_doc_files_uploaded) - - # Manifest and components upload - - manifest_files_uploaded = _file_upload( - file_key="manifest", - local_path=manifest_only_file_info.manifest_file_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=True, - version_folder=version_folder, - upload_as_latest=should_upload_latest, - override_destination_file_name=MANIFEST_FILE_NAME, - ) - uploaded_files.extend(manifest_files_uploaded) - - components_zip_sha256_files_uploaded = _file_upload( - file_key="components_zip_sha256", - local_path=manifest_only_file_info.sha256_file_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=True, - version_folder=version_folder, - upload_as_latest=should_upload_latest, - override_destination_file_name=COMPONENTS_ZIP_SHA256_FILE_NAME, - ) - uploaded_files.extend(components_zip_sha256_files_uploaded) - - components_zip_files_uploaded = _file_upload( - file_key="components_zip", - local_path=manifest_only_file_info.zip_file_path, - gcp_connector_dir=gcp_connector_dir, - bucket=bucket, - upload_as_version=True, - version_folder=version_folder, - upload_as_latest=should_upload_latest, - override_destination_file_name=COMPONENTS_ZIP_FILE_NAME, - ) - uploaded_files.extend(components_zip_files_uploaded) - - return MetadataUploadInfo( - uploaded_files=uploaded_files, - metadata_file_path=str(metadata_file_path), - metadata_uploaded=_any_uploaded( - uploaded_files, - [ - "latest_metadata", - "version_metadata", - "version_release_candidate", - ], - ), - ) - - -def delete_release_candidate_from_gcs(bucket_name: str, docker_repository: str, connector_version: str) -> MetadataDeleteInfo: - """ - Delete a release candidate from a GCS bucket. - The release candidate and version metadata file will be deleted. - We first check that the release candidate metadata file hash matches the version metadata file hash. - Args: - bucket_name (str): Name of the GCS bucket from which the release candidate will be deleted. - docker_repository (str): Name of the connector docker image. - connector_version (str): Version of the connector. - Returns: - MetadataDeleteInfo: Information about the files that were deleted. - """ - storage_client = get_gcs_storage_client() - bucket = storage_client.bucket(bucket_name) - - gcp_connector_dir = f"{METADATA_FOLDER}/{docker_repository}" - version_path = f"{gcp_connector_dir}/{connector_version}/{METADATA_FILE_NAME}" - rc_path = f"{gcp_connector_dir}/{RELEASE_CANDIDATE_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}" - - version_blob = bucket.blob(version_path) - rc_blob = bucket.blob(rc_path) - - if not version_blob.exists(): - raise FileNotFoundError(f"Version metadata file {version_path} does not exist in the bucket. ") - if not rc_blob.exists(): - raise FileNotFoundError(f"Release candidate metadata file {rc_path} does not exist in the bucket. ") - if rc_blob.md5_hash != version_blob.md5_hash: - raise ValueError( - f"Release candidate metadata file {rc_path} hash does not match the version metadata file {version_path} hash. Unsafe to delete. Please check the Remote Release Candidate to confirm its the version you would like to remove and rerun with --force" - ) - - deleted_files = [] - rc_blob.delete() - deleted_files.append( - DeletedFile( - id="release_candidate_metadata", - deleted=True, - description="release candidate metadata", - blob_id=rc_blob.id, - ) - ) - version_blob.delete() - deleted_files.append( - DeletedFile( - id="version_metadata", - deleted=True, - description="versioned metadata", - blob_id=version_blob.id, - ) - ) - - return MetadataDeleteInfo( - metadata_deleted=True, - deleted_files=deleted_files, - ) - - -def promote_release_candidate_in_gcs( - bucket_name: str, docker_repository: str, connector_version: str -) -> Tuple[MetadataUploadInfo, MetadataDeleteInfo]: - """Promote a release candidate to the latest version in a GCS bucket. - The release candidate metadata file will be copied to the latest metadata file and then deleted. - We first check that the release candidate metadata file hash matches the version metadata file hash. - Args: - bucket_name (str): Name of the GCS bucket from which the release candidate will be deleted. - docker_repository (str): Name of the connector docker image. - connector_version (str): Version of the connector. - Returns: - Tuple[MetadataUploadInfo, MetadataDeleteInfo]: Information about the files that were uploaded (new latest version) and deleted (release candidate). - """ - - storage_client = get_gcs_storage_client() - bucket = storage_client.bucket(bucket_name) - - gcp_connector_dir = f"{METADATA_FOLDER}/{docker_repository}" - version_path = f"{gcp_connector_dir}/{connector_version}/{METADATA_FILE_NAME}" - rc_path = f"{gcp_connector_dir}/{RELEASE_CANDIDATE_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}" - latest_path = f"{gcp_connector_dir}/{LATEST_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}" - - version_blob = bucket.blob(version_path) - latest_blob = bucket.blob(latest_path) - rc_blob = bucket.blob(rc_path) - - if not version_blob.exists(): - raise FileNotFoundError(f"Version metadata file {version_path} does not exist in the bucket.") - if not rc_blob.exists(): - raise FileNotFoundError(f"Release candidate metadata file {rc_path} does not exist in the bucket.") - - if rc_blob.md5_hash != version_blob.md5_hash: - raise ValueError( - f"""Release candidate metadata file {rc_path} hash does not match the version metadata file {version_path} hash. Unsafe to promote. - It's likely that something changed the release candidate hash but have not changed the metadata for the lastest matching version.""" - ) - - uploaded_files = [] - deleted_files = [] - - bucket.copy_blob(rc_blob, bucket, latest_blob) - uploaded_files.append( - UploadedFile( - id="latest_metadata", - uploaded=True, - blob_id=latest_blob.id, - ) - ) - - rc_blob.delete() - deleted_files.append( - DeletedFile( - id="release_candidate_metadata", - deleted=True, - description="release candidate metadata", - blob_id=rc_blob.id, - ) - ) - - return MetadataUploadInfo( - metadata_uploaded=True, - metadata_file_path=str(version_path), - uploaded_files=uploaded_files, - ), MetadataDeleteInfo( - metadata_deleted=True, - deleted_files=deleted_files, - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/files.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/files.py deleted file mode 100644 index 680577209fd0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/files.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import base64 -import hashlib -import zipfile -from pathlib import Path -from typing import List - - -def compute_gcs_md5(file_name: Path) -> str: - hash_md5 = hashlib.md5() - with Path.open(file_name, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash_md5.update(chunk) - - return base64.b64encode(hash_md5.digest()).decode("utf8") - - -def compute_sha256(file_name: Path) -> str: - hash_sha256 = hashlib.sha256() - with Path.open(file_name, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash_sha256.update(chunk) - - return base64.b64encode(hash_sha256.digest()).decode("utf8") - - -def create_zip_and_get_sha256(files_to_zip: List[Path], output_zip_path: Path) -> str: - """Create a zip file from given files and return its SHA256 hash.""" - with zipfile.ZipFile(output_zip_path, "w") as zipf: - for file_path in files_to_zip: - if file_path.exists(): - zipf.write(filename=file_path, arcname=file_path.name) - - zip_sha256 = compute_sha256(output_zip_path) - return zip_sha256 diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/gcs.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/gcs.py deleted file mode 100644 index 89fbc059653e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/gcs.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import json -import os -from typing import Optional - -from google.cloud import storage -from google.oauth2 import service_account - - -def get_gcs_storage_client(gcs_creds: Optional[str] = None) -> storage.Client: - """Get the GCS storage client using credentials form GCS_CREDENTIALS env variable.""" - gcs_creds = os.environ.get("GCS_CREDENTIALS") if not gcs_creds else gcs_creds - if not gcs_creds: - raise ValueError("Please set the GCS_CREDENTIALS env var.") - - service_account_info = json.loads(gcs_creds) - credentials = service_account.Credentials.from_service_account_info(service_account_info) - return storage.Client(credentials=credentials) - - -def safe_read_gcs_file(gcs_blob: storage.Blob) -> Optional[str]: - """Read the connector metrics jsonl blob. - - Args: - gcs_blob (storage.Blob): The blob. - - Returns: - dict: The metrics. - """ - if not gcs_blob.exists(): - return None - - return gcs_blob.download_as_string().decode("utf-8") diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/object_helpers.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/object_helpers.py deleted file mode 100644 index 96ba5d72b905..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/object_helpers.py +++ /dev/null @@ -1,47 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - - -import copy -from enum import EnumMeta - - -def deep_copy_params(to_call): - def f(*args, **kwargs): - return to_call(*copy.deepcopy(args), **copy.deepcopy(kwargs)) - - return f - - -class CaseInsensitiveKeys(EnumMeta): - """A metaclass for creating enums with case-insensitive keys.""" - - def __getitem__(cls, item): - try: - return super().__getitem__(item) - except Exception: - for key in cls._member_map_: - if key.casefold() == item.casefold(): - return super().__getitem__(key) - - -def default_none_to_dict(value, key, obj): - """Set the value of a key in a dictionary to an empty dictionary if the value is None. - - Useful with pydash's set_with function. - - e.g. set_with(obj, key, value, default_none_to_dict) - - For more information, see https://github.com/dgilland/pydash/issues/122 - - Args: - value: The value to check. - key: The key to set in the dictionary. - obj: The dictionary to set the key in. - """ - if obj is None: - return - - if value is None: - obj[key] = {} diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/slack.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/slack.py deleted file mode 100644 index 977673e20d61..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/helpers/slack.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import logging -import os -from typing import Generator, Optional - -from slack_sdk import WebClient - -from metadata_service.constants import SLACK_NOTIFICATIONS_ENABLED - -logger = logging.getLogger(__name__) - - -def _chunk_messages(message: str) -> Generator[str, None, None]: - """Split message into messages with no more than 3992 chars each. Slack will automatically split any messages that are 4000 chars or more.""" - slack_message_char_limit = 3992 - msg = "" - for line in message.splitlines(): - if len(msg + line + "\n") > slack_message_char_limit: - yield msg - msg += line + "\n" - yield msg - - -def send_slack_message(channel: str, message: str, enable_code_block_wrapping: bool = False) -> tuple[bool, Optional[str]]: - """ - Send a slack message to the given channel. - - Args: - channel (str): The channel to send the message to. - message (str): The message to send. - - Returns: - tuple[bool, str]: (success, error_message) - """ - if (slack_token := os.environ.get("SLACK_TOKEN")) and SLACK_NOTIFICATIONS_ENABLED == "true": - # Ensure that a failure to send a slack message does not cause the pipeline to fail - try: - slack_client = WebClient(token=slack_token) - for message_chunk in _chunk_messages(message): - if enable_code_block_wrapping: - message_chunk = f"```{message_chunk}```" - - slack_client.chat_postMessage(channel=channel, text=message_chunk) - return True, None - except Exception as e: - logger.error(f"Failed to send slack message: {e}") - return False, str(e) - elif not slack_token: - logger.info("Slack token is not set - skipping message send") - else: - logger.info("Slack notifications are disabled - skipping message send") - return True, None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ActorDefinitionResourceRequirements.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ActorDefinitionResourceRequirements.py deleted file mode 100644 index 1f6e484eef73..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ActorDefinitionResourceRequirements.py +++ /dev/null @@ -1,54 +0,0 @@ -# generated by datamodel-codegen: -# filename: ActorDefinitionResourceRequirements.yaml - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AirbyteInternal.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AirbyteInternal.py deleted file mode 100644 index 60cd440cc218..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AirbyteInternal.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: AirbyteInternal.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AllowedHosts.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AllowedHosts.py deleted file mode 100644 index ce4534f3adca..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/AllowedHosts.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: AllowedHosts.yaml - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Extra, Field - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBreakingChanges.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBreakingChanges.py deleted file mode 100644 index b0df13b93210..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBreakingChanges.py +++ /dev/null @@ -1,65 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorBreakingChanges.yaml - -from __future__ import annotations - -from datetime import date -from typing import Dict, List, Optional - -from pydantic import AnyUrl, BaseModel, Extra, Field, constr -from typing_extensions import Literal - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBuildOptions.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBuildOptions.py deleted file mode 100644 index ad7a10d5ab56..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorBuildOptions.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorBuildOptions.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra - - -class ConnectorBuildOptions(BaseModel): - class Config: - extra = Extra.forbid - - baseImage: Optional[str] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorIPCOptions.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorIPCOptions.py deleted file mode 100644 index 63a5d58f3408..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorIPCOptions.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorIPCOptions.yaml - -from __future__ import annotations - -from typing import List - -from pydantic import BaseModel, Extra -from typing_extensions import Literal - - -class DataChannel(BaseModel): - class Config: - extra = Extra.forbid - - version: str - supportedSerialization: List[Literal["JSONL", "PROTOBUF"]] - supportedTransport: List[Literal["STDIO", "SOCKET"]] - - -class ConnectorIPCOptions(BaseModel): - class Config: - extra = Extra.forbid - - dataChannel: DataChannel diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.json b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.json deleted file mode 100644 index aa7a75351f55..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.json +++ /dev/null @@ -1,897 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/ConnectorMetadataDefinitionV0.yml", - "title": "ConnectorMetadataDefinitionV0", - "description": "describes the metadata of a connector", - "type": "object", - "required": [ - "metadataSpecVersion", - "data" - ], - "additionalProperties": false, - "properties": { - "metadataSpecVersion": { - "type": "string" - }, - "data": { - "type": "object", - "required": [ - "name", - "definitionId", - "connectorType", - "dockerRepository", - "dockerImageTag", - "license", - "documentationUrl", - "githubIssueLabel", - "connectorSubtype", - "releaseStage" - ], - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "icon": { - "type": "string" - }, - "definitionId": { - "type": "string", - "format": "uuid" - }, - "connectorBuildOptions": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBuildOptions.yaml", - "title": "ConnectorBuildOptions", - "description": "metadata specific to the build process.", - "type": "object", - "additionalProperties": false, - "properties": { - "baseImage": { - "type": "string" - } - } - }, - "connectorTestSuitesOptions": { - "type": "array", - "items": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorTestOptions.yaml", - "title": "ConnectorTestSuiteOptions", - "description": "Options for a specific connector test suite.", - "type": "object", - "required": [ - "suite" - ], - "additionalProperties": false, - "properties": { - "suite": { - "description": "Name of the configured test suite", - "type": "string", - "enum": [ - "unitTests", - "integrationTests", - "acceptanceTests", - "liveTests" - ] - }, - "testSecrets": { - "description": "List of secrets required to run the test suite", - "type": "array", - "items": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/Secret.yaml", - "title": "Secret", - "description": "An object describing a secret's metadata", - "type": "object", - "required": [ - "name", - "secretStore" - ], - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The secret name in the secret store" - }, - "fileName": { - "type": "string", - "description": "The name of the file to which the secret value would be persisted" - }, - "secretStore": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SecretStore.yaml", - "title": "SecretStore", - "description": "An object describing a secret store metadata", - "type": "object", - "additionalProperties": false, - "properties": { - "alias": { - "type": "string", - "description": "The alias of the secret store which can map to its actual secret address" - }, - "type": { - "type": "string", - "description": "The type of the secret store", - "enum": [ - "GSM" - ] - } - } - } - } - } - }, - "testConnections": { - "description": "List of sandbox cloud connections that tests can be run against", - "type": "array", - "items": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/TestConnections.yaml", - "title": "TestConnections", - "description": "List of sandbox cloud connections that tests can be run against", - "type": "object", - "required": [ - "name", - "id" - ], - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The connection name" - }, - "id": { - "type": "string", - "description": "The connection ID" - } - } - } - } - } - } - }, - "connectorType": { - "type": "string", - "enum": [ - "destination", - "source" - ] - }, - "dockerRepository": { - "type": "string" - }, - "dockerImageTag": { - "type": "string" - }, - "supportsDbt": { - "type": "boolean" - }, - "supportsNormalization": { - "type": "boolean" - }, - "license": { - "type": "string" - }, - "documentationUrl": { - "type": "string", - "format": "uri" - }, - "externalDocumentationUrls": { - "type": "array", - "description": "An array of external vendor documentation URLs (changelogs, API references, deprecation notices, etc.)", - "items": { - "type": "object", - "required": [ - "title", - "url" - ], - "additionalProperties": false, - "properties": { - "title": { - "type": "string", - "description": "Display title for the documentation link" - }, - "url": { - "type": "string", - "format": "uri", - "description": "URL to the external documentation" - }, - "type": { - "type": "string", - "description": "Category of documentation", - "enum": [ - "api_deprecations", - "api_reference", - "api_release_history", - "authentication_guide", - "data_model_reference", - "developer_community", - "migration_guide", - "openapi_spec", - "other", - "permissions_scopes", - "rate_limits", - "sql_reference", - "status_page" - ] - }, - "requiresLogin": { - "type": "boolean", - "description": "Whether the URL requires authentication to access", - "default": false - } - } - } - }, - "githubIssueLabel": { - "type": "string" - }, - "maxSecondsBetweenMessages": { - "description": "Maximum delay between 2 airbyte protocol messages, in second. The source will timeout if this delay is reached", - "type": "integer" - }, - "releaseDate": { - "description": "The date when this connector was first released, in yyyy-mm-dd format.", - "type": "string", - "format": "date" - }, - "protocolVersion": { - "type": "string", - "description": "the Airbyte Protocol version supported by the connector" - }, - "erdUrl": { - "type": "string", - "description": "The URL where you can visualize the ERD" - }, - "connectorSubtype": { - "type": "string", - "enum": [ - "api", - "database", - "datalake", - "file", - "custom", - "message_queue", - "unknown", - "vectorstore" - ] - }, - "releaseStage": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-config/config-models/src/main/resources/types/ReleaseStage.yaml", - "title": "ReleaseStage", - "description": "enum that describes a connector's release stage", - "type": "string", - "enum": [ - "alpha", - "beta", - "generally_available", - "custom" - ] - }, - "supportLevel": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/SupportLevel.yaml", - "title": "SupportLevel", - "description": "enum that describes a connector's release stage", - "type": "string", - "enum": [ - "community", - "certified", - "archived" - ] - }, - "tags": { - "type": "array", - "description": "An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - "items": { - "type": "string" - }, - "default": [] - }, - "registryOverrides": { - "anyOf": [ - { - "type": "object", - "additionalProperties": false, - "properties": { - "oss": { - "anyOf": [ - { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/RegistryOverrides.yml", - "title": "RegistryOverrides", - "description": "describes the overrides per registry of a connector", - "type": "object", - "additionalProperties": false, - "required": [ - "enabled" - ], - "properties": { - "enabled": { - "type": "boolean", - "default": false - }, - "name": { - "type": "string" - }, - "dockerRepository": { - "type": "string" - }, - "dockerImageTag": { - "type": "string" - }, - "supportsDbt": { - "type": "boolean" - }, - "supportsNormalization": { - "type": "boolean" - }, - "license": { - "type": "string" - }, - "documentationUrl": { - "type": "string", - "format": "uri" - }, - "connectorSubtype": { - "type": "string" - }, - "allowedHosts": { - "$ref": "#/properties/data/properties/allowedHosts" - }, - "normalizationConfig": { - "$ref": "#/properties/data/properties/normalizationConfig" - }, - "suggestedStreams": { - "$ref": "#/properties/data/properties/suggestedStreams" - }, - "resourceRequirements": { - "$ref": "#/properties/data/properties/resourceRequirements" - } - } - } - ] - }, - "cloud": { - "anyOf": [ - { - "$ref": "#/properties/data/properties/registryOverrides/anyOf/0/properties/oss/anyOf/0" - } - ] - } - } - } - ] - }, - "allowedHosts": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AllowedHosts.yaml", - "title": "AllowedHosts", - "description": "A connector's allowed hosts. If present, the platform will limit communication to only hosts which are listed in `AllowedHosts.hosts`.", - "type": "object", - "additionalProperties": true, - "properties": { - "hosts": { - "type": "array", - "description": "An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - "items": { - "type": "string" - } - } - } - }, - "releases": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorReleases.yaml", - "title": "ConnectorReleases", - "description": "Contains information about different types of releases for a connector.", - "type": "object", - "additionalProperties": false, - "properties": { - "rolloutConfiguration": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RolloutConfiguration.yaml", - "title": "RolloutConfiguration", - "description": "configuration for the rollout of a connector", - "type": "object", - "additionalProperties": false, - "properties": { - "enableProgressiveRollout": { - "type": "boolean", - "default": false, - "description": "Whether to enable progressive rollout for the connector." - }, - "initialPercentage": { - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 0, - "description": "The percentage of users that should receive the new version initially." - }, - "maxPercentage": { - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 50, - "description": "The percentage of users who should receive the release candidate during the test phase before full rollout." - }, - "advanceDelayMinutes": { - "type": "integer", - "minimum": 10, - "default": 10, - "description": "The number of minutes to wait before advancing the rollout percentage." - } - } - }, - "breakingChanges": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBreakingChanges.yaml", - "title": "ConnectorBreakingChanges", - "description": "Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - "type": "object", - "additionalProperties": false, - "minProperties": 1, - "patternProperties": { - "^\\d+\\.\\d+\\.\\d+$": { - "$ref": "#/properties/data/properties/releases/properties/breakingChanges/definitions/VersionBreakingChange" - } - }, - "definitions": { - "VersionBreakingChange": { - "description": "Contains information about a breaking change, including the deadline to upgrade and a message detailing the change.", - "type": "object", - "additionalProperties": false, - "required": [ - "upgradeDeadline", - "message" - ], - "properties": { - "upgradeDeadline": { - "description": "The deadline by which to upgrade before the breaking change takes effect.", - "type": "string", - "format": "date" - }, - "message": { - "description": "Descriptive message detailing the breaking change.", - "type": "string" - }, - "deadlineAction": { - "description": "Action to do when the deadline is reached.", - "type": "string", - "enum": [ - "auto_upgrade", - "disable" - ] - }, - "migrationDocumentationUrl": { - "description": "URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - "type": "string", - "format": "uri" - }, - "scopedImpact": { - "description": "List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/properties/data/properties/releases/properties/breakingChanges/definitions/BreakingChangeScope" - } - } - } - }, - "BreakingChangeScope": { - "description": "A scope that can be used to limit the impact of a breaking change.", - "type": "object", - "oneOf": [ - { - "$ref": "#/properties/data/properties/releases/properties/breakingChanges/definitions/StreamBreakingChangeScope" - } - ] - }, - "StreamBreakingChangeScope": { - "description": "A scope that can be used to limit the impact of a breaking change to specific streams.", - "type": "object", - "additionalProperties": false, - "required": [ - "scopeType", - "impactedScopes" - ], - "properties": { - "scopeType": { - "type": "string", - "const": "stream" - }, - "impactedScopes": { - "description": "List of streams that are impacted by the breaking change.", - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - } - } - } - }, - "migrationDocumentationUrl": { - "description": "URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - "type": "string", - "format": "uri" - } - } - }, - "normalizationConfig": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/NormalizationDestinationDefinitionConfig.yaml", - "title": "NormalizationDestinationDefinitionConfig", - "description": "describes a normalization config for destination definition", - "type": "object", - "required": [ - "normalizationRepository", - "normalizationTag", - "normalizationIntegrationType" - ], - "additionalProperties": true, - "properties": { - "normalizationRepository": { - "type": "string", - "description": "a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used." - }, - "normalizationTag": { - "type": "string", - "description": "a field indicating the tag of the docker repository to be used for normalization." - }, - "normalizationIntegrationType": { - "type": "string", - "description": "a field indicating the type of integration dialect to use for normalization." - } - } - }, - "suggestedStreams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SuggestedStreams.yaml", - "title": "SuggestedStreams", - "description": "A source's suggested streams. These will be suggested by default for new connections using this source. Otherwise, all streams will be selected. This is useful for when your source has a lot of streams, but the average user will only want a subset of them synced.", - "type": "object", - "additionalProperties": true, - "properties": { - "streams": { - "type": "array", - "description": "An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - "items": { - "type": "string" - } - } - } - }, - "resourceRequirements": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ActorDefinitionResourceRequirements.yaml", - "title": "ActorDefinitionResourceRequirements", - "description": "actor definition specific resource requirements", - "type": "object", - "additionalProperties": false, - "properties": { - "default": { - "description": "if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - "$ref": "#/properties/data/properties/resourceRequirements/definitions/JobTypeResourceLimit/properties/resourceRequirements" - }, - "jobSpecific": { - "type": "array", - "items": { - "$ref": "#/properties/data/properties/resourceRequirements/definitions/JobTypeResourceLimit" - } - } - }, - "definitions": { - "JobTypeResourceLimit": { - "description": "sets resource requirements for a specific job type for an actor definition. these values override the default, if both are set.", - "type": "object", - "additionalProperties": false, - "required": [ - "jobType", - "resourceRequirements" - ], - "properties": { - "jobType": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/JobType.yaml", - "title": "JobType", - "description": "enum that describes the different types of jobs that the platform runs.", - "type": "string", - "enum": [ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate" - ] - }, - "resourceRequirements": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ResourceRequirements.yaml", - "title": "ResourceRequirements", - "description": "generic configuration for pod source requirements", - "type": "object", - "additionalProperties": false, - "properties": { - "cpu_request": { - "type": "string" - }, - "cpu_limit": { - "type": "string" - }, - "memory_request": { - "type": "string" - }, - "memory_limit": { - "type": "string" - } - } - } - } - } - } - }, - "ab_internal": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/AirbyteInternal.yml", - "title": "AirbyteInternal", - "description": "Fields for internal use only", - "type": "object", - "additionalProperties": true, - "properties": { - "sl": { - "type": "integer", - "enum": [ - 0, - 100, - 200, - 300 - ] - }, - "ql": { - "type": "integer", - "enum": [ - 0, - 100, - 200, - 300, - 400, - 500, - 600 - ] - }, - "isEnterprise": { - "type": "boolean", - "default": false - }, - "requireVersionIncrementsInPullRequests": { - "type": "boolean", - "default": true, - "description": "When false, version increment checks will be skipped for this connector" - } - } - }, - "remoteRegistries": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/RemoteRegistries.yml", - "title": "RemoteRegistries", - "description": "describes how the connector is published to remote registries", - "type": "object", - "additionalProperties": false, - "properties": { - "pypi": { - "$ref": "#/properties/data/properties/remoteRegistries/definitions/PyPi" - } - }, - "definitions": { - "PyPi": { - "title": "PyPi", - "description": "describes the PyPi publishing options", - "type": "object", - "additionalProperties": false, - "required": [ - "enabled", - "packageName" - ], - "properties": { - "enabled": { - "type": "boolean" - }, - "packageName": { - "type": "string", - "description": "The name of the package on PyPi." - } - } - } - } - }, - "supportsRefreshes": { - "type": "boolean", - "default": false - }, - "generated": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/GeneratedFields.yaml", - "title": "GeneratedFields", - "description": "Optional schema for fields generated at metadata upload time", - "type": "object", - "properties": { - "git": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GitInfo.yaml", - "title": "GitInfo", - "description": "Information about the author of the last commit that modified this file. DO NOT DEFINE THIS FIELD MANUALLY. It will be overwritten by the CI.", - "type": "object", - "additionalProperties": false, - "properties": { - "commit_sha": { - "type": "string", - "description": "The git commit sha of the last commit that modified this file." - }, - "commit_timestamp": { - "type": "string", - "format": "date-time", - "description": "The git commit timestamp of the last commit that modified this file." - }, - "commit_author": { - "type": "string", - "description": "The git commit author of the last commit that modified this file." - }, - "commit_author_email": { - "type": "string", - "description": "The git commit author email of the last commit that modified this file." - } - } - }, - "source_file_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/SourceFileInfo.yaml", - "title": "SourceFileInfo", - "description": "Information about the source file that generated the registry entry", - "type": "object", - "properties": { - "metadata_etag": { - "type": "string" - }, - "metadata_file_path": { - "type": "string" - }, - "metadata_bucket_name": { - "type": "string" - }, - "metadata_last_modified": { - "type": "string" - }, - "registry_entry_generated_at": { - "type": "string" - } - } - }, - "metrics": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/ConnectorMetrics.yaml", - "title": "ConnectorMetrics", - "description": "Information about the source file that generated the registry entry", - "type": "object", - "properties": { - "all": { - "type": "ConnectorMetric" - }, - "cloud": { - "type": "ConnectorMetric" - }, - "oss": { - "type": "ConnectorMetric" - } - }, - "definitions": { - "ConnectorMetric": { - "type": "object", - "properties": { - "usage": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - } - ] - }, - "sync_success_rate": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - } - ] - }, - "connector_version": { - "type": "string" - } - }, - "additionalProperties": true - } - } - }, - "sbomUrl": { - "type": "string", - "description": "URL to the SBOM file" - } - } - }, - "supportsFileTransfer": { - "type": "boolean", - "default": false - }, - "supportsDataActivation": { - "type": "boolean", - "default": false - }, - "connectorIPCOptions": { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorIPCOptions.yaml", - "title": "ConnectorIPCOptions", - "type": "object", - "required": [ - "dataChannel" - ], - "additionalProperties": false, - "properties": { - "dataChannel": { - "type": "object", - "required": [ - "version", - "supportedSerialization", - "supportedTransport" - ], - "additionalProperties": false, - "properties": { - "version": { - "type": "string" - }, - "supportedSerialization": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "JSONL", - "PROTOBUF" - ] - } - }, - "supportedTransport": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "STDIO", - "SOCKET" - ] - } - } - } - } - } - } - } - } - } -} diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.py deleted file mode 100644 index 347ee83fa23c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetadataDefinitionV0.py +++ /dev/null @@ -1,479 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorMetadataDefinitionV0.yaml - -from __future__ import annotations - -from datetime import date, datetime -from typing import Any, Dict, List, Optional, Union -from uuid import UUID - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class ExternalDocumentationUrl(BaseModel): - class Config: - extra = Extra.forbid - - title: str = Field(..., description="Display title for the documentation link") - url: AnyUrl = Field(..., description="URL to the external documentation") - type: Optional[ - Literal[ - "api_deprecations", - "api_reference", - "api_release_history", - "authentication_guide", - "data_model_reference", - "developer_community", - "migration_guide", - "openapi_spec", - "other", - "permissions_scopes", - "rate_limits", - "sql_reference", - "status_page", - ] - ] = Field(None, description="Category of documentation") - requiresLogin: Optional[bool] = Field( - False, description="Whether the URL requires authentication to access" - ) - - -class ConnectorBuildOptions(BaseModel): - class Config: - extra = Extra.forbid - - baseImage: Optional[str] = None - - -class SecretStore(BaseModel): - class Config: - extra = Extra.forbid - - alias: Optional[str] = Field( - None, - description="The alias of the secret store which can map to its actual secret address", - ) - type: Optional[Literal["GSM"]] = Field( - None, description="The type of the secret store" - ) - - -class TestConnections(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The connection name") - id: str = Field(..., description="The connection ID") - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) - - -class PyPi(BaseModel): - class Config: - extra = Extra.forbid - - enabled: bool - packageName: str = Field(..., description="The name of the package on PyPi.") - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class DataChannel(BaseModel): - class Config: - extra = Extra.forbid - - version: str - supportedSerialization: List[Literal["JSONL", "PROTOBUF"]] - supportedTransport: List[Literal["STDIO", "SOCKET"]] - - -class ConnectorIPCOptions(BaseModel): - class Config: - extra = Extra.forbid - - dataChannel: DataChannel - - -class Secret(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The secret name in the secret store") - fileName: Optional[str] = Field( - None, - description="The name of the file to which the secret value would be persisted", - ) - secretStore: SecretStore - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class RemoteRegistries(BaseModel): - class Config: - extra = Extra.forbid - - pypi: Optional[PyPi] = None - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") - - -class ConnectorTestSuiteOptions(BaseModel): - class Config: - extra = Extra.forbid - - suite: Literal["unitTests", "integrationTests", "acceptanceTests", "liveTests"] = ( - Field(..., description="Name of the configured test suite") - ) - testSecrets: Optional[List[Secret]] = Field( - None, description="List of secrets required to run the test suite" - ) - testConnections: Optional[List[TestConnections]] = Field( - None, - description="List of sandbox cloud connections that tests can be run against", - ) - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class RegistryOverrides(BaseModel): - class Config: - extra = Extra.forbid - - enabled: bool - name: Optional[str] = None - dockerRepository: Optional[str] = None - dockerImageTag: Optional[str] = None - supportsDbt: Optional[bool] = None - supportsNormalization: Optional[bool] = None - license: Optional[str] = None - documentationUrl: Optional[AnyUrl] = None - connectorSubtype: Optional[str] = None - allowedHosts: Optional[AllowedHosts] = None - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - suggestedStreams: Optional[SuggestedStreams] = None - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class RegistryOverride(BaseModel): - class Config: - extra = Extra.forbid - - oss: Optional[RegistryOverrides] = None - cloud: Optional[RegistryOverrides] = None - - -class ConnectorReleases(BaseModel): - class Config: - extra = Extra.forbid - - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) - - -class Data(BaseModel): - class Config: - extra = Extra.forbid - - name: str - icon: Optional[str] = None - definitionId: UUID - connectorBuildOptions: Optional[ConnectorBuildOptions] = None - connectorTestSuitesOptions: Optional[List[ConnectorTestSuiteOptions]] = None - connectorType: Literal["destination", "source"] - dockerRepository: str - dockerImageTag: str - supportsDbt: Optional[bool] = None - supportsNormalization: Optional[bool] = None - license: str - documentationUrl: AnyUrl - externalDocumentationUrls: Optional[List[ExternalDocumentationUrl]] = Field( - None, - description="An array of external vendor documentation URLs (changelogs, API references, deprecation notices, etc.)", - ) - githubIssueLabel: str - maxSecondsBetweenMessages: Optional[int] = Field( - None, - description="Maximum delay between 2 airbyte protocol messages, in second. The source will timeout if this delay is reached", - ) - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - erdUrl: Optional[str] = Field( - None, description="The URL where you can visualize the ERD" - ) - connectorSubtype: Literal[ - "api", - "database", - "datalake", - "file", - "custom", - "message_queue", - "unknown", - "vectorstore", - ] - releaseStage: ReleaseStage - supportLevel: Optional[SupportLevel] = None - tags: Optional[List[str]] = Field( - [], - description="An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - ) - registryOverrides: Optional[RegistryOverride] = None - allowedHosts: Optional[AllowedHosts] = None - releases: Optional[ConnectorReleases] = None - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - suggestedStreams: Optional[SuggestedStreams] = None - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - ab_internal: Optional[AirbyteInternal] = None - remoteRegistries: Optional[RemoteRegistries] = None - supportsRefreshes: Optional[bool] = False - generated: Optional[GeneratedFields] = None - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - connectorIPCOptions: Optional[ConnectorIPCOptions] = None - - -class ConnectorMetadataDefinitionV0(BaseModel): - class Config: - extra = Extra.forbid - - metadataSpecVersion: str - data: Data diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetrics.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetrics.py deleted file mode 100644 index 33a9f1ba0668..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorMetrics.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorMetrics.yaml - -from __future__ import annotations - -from typing import Any, Optional, Union - -from pydantic import BaseModel, Extra -from typing_extensions import Literal - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorPackageInfo.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorPackageInfo.py deleted file mode 100644 index e4496fa446b5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorPackageInfo.py +++ /dev/null @@ -1,12 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorPackageInfo.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel - - -class ConnectorPackageInfo(BaseModel): - cdk_version: Optional[str] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryDestinationDefinition.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryDestinationDefinition.py deleted file mode 100644 index 09b5baee491c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryDestinationDefinition.py +++ /dev/null @@ -1,407 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorRegistryDestinationDefinition.yaml - -from __future__ import annotations - -from datetime import date, datetime -from typing import Any, Dict, List, Optional, Union -from uuid import UUID - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class ConnectorPackageInfo(BaseModel): - cdk_version: Optional[str] = None - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class ConnectorRegistryDestinationDefinition(BaseModel): - class Config: - extra = Extra.allow - - destinationDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - tags: Optional[List[str]] = Field( - None, - description="An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - supportsDbt: Optional[bool] = Field( - None, - description="an optional flag indicating whether DBT is used in the normalization. If the flag value is NULL - DBT is not used.", - ) - allowedHosts: Optional[AllowedHosts] = None - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - supportsRefreshes: Optional[bool] = False - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - - -class ConnectorRegistryReleases(BaseModel): - class Config: - extra = Extra.forbid - - releaseCandidates: Optional[ConnectorReleaseCandidates] = None - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) - - -class ConnectorReleaseCandidates(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[ - constr(regex=r"^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$"), VersionReleaseCandidate - ] = Field( - ..., - description="Each entry denotes a release candidate version of a connector.", - ) - - -class VersionReleaseCandidate(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Union[ - ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition - ] = Field( - ..., - description="Contains information about a release candidate version of a connector.", - ) - - -class ConnectorRegistrySourceDefinition(BaseModel): - class Config: - extra = Extra.allow - - sourceDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - sourceType: Optional[Literal["api", "file", "database", "custom"]] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - allowedHosts: Optional[AllowedHosts] = None - suggestedStreams: Optional[SuggestedStreams] = None - maxSecondsBetweenMessages: Optional[int] = Field( - None, - description="Number of seconds allowed between 2 airbyte protocol messages. The source will timeout if this delay is reach", - ) - erdUrl: Optional[str] = Field( - None, description="The URL where you can visualize the ERD" - ) - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - - -ConnectorRegistryDestinationDefinition.update_forward_refs() -ConnectorRegistryReleases.update_forward_refs() -ConnectorReleaseCandidates.update_forward_refs() -VersionReleaseCandidate.update_forward_refs() diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryReleases.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryReleases.py deleted file mode 100644 index 4c7b94fb8fd2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryReleases.py +++ /dev/null @@ -1,406 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorRegistryReleases.yaml - -from __future__ import annotations - -from datetime import date, datetime -from typing import Any, Dict, List, Optional, Union -from uuid import UUID - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class ConnectorPackageInfo(BaseModel): - cdk_version: Optional[str] = None - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class ConnectorRegistryReleases(BaseModel): - class Config: - extra = Extra.forbid - - releaseCandidates: Optional[ConnectorReleaseCandidates] = None - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) - - -class ConnectorReleaseCandidates(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[ - constr(regex=r"^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$"), VersionReleaseCandidate - ] = Field( - ..., - description="Each entry denotes a release candidate version of a connector.", - ) - - -class VersionReleaseCandidate(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Union[ - ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition - ] = Field( - ..., - description="Contains information about a release candidate version of a connector.", - ) - - -class ConnectorRegistrySourceDefinition(BaseModel): - class Config: - extra = Extra.allow - - sourceDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - sourceType: Optional[Literal["api", "file", "database", "custom"]] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - allowedHosts: Optional[AllowedHosts] = None - suggestedStreams: Optional[SuggestedStreams] = None - maxSecondsBetweenMessages: Optional[int] = Field( - None, - description="Number of seconds allowed between 2 airbyte protocol messages. The source will timeout if this delay is reach", - ) - erdUrl: Optional[str] = Field( - None, description="The URL where you can visualize the ERD" - ) - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - - -class ConnectorRegistryDestinationDefinition(BaseModel): - class Config: - extra = Extra.allow - - destinationDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - tags: Optional[List[str]] = Field( - None, - description="An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - supportsDbt: Optional[bool] = Field( - None, - description="an optional flag indicating whether DBT is used in the normalization. If the flag value is NULL - DBT is not used.", - ) - allowedHosts: Optional[AllowedHosts] = None - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - supportsRefreshes: Optional[bool] = False - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - - -ConnectorRegistryReleases.update_forward_refs() -ConnectorReleaseCandidates.update_forward_refs() -VersionReleaseCandidate.update_forward_refs() diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistrySourceDefinition.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistrySourceDefinition.py deleted file mode 100644 index 17f6573ad80e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistrySourceDefinition.py +++ /dev/null @@ -1,407 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorRegistrySourceDefinition.yaml - -from __future__ import annotations - -from datetime import date, datetime -from typing import Any, Dict, List, Optional, Union -from uuid import UUID - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class ConnectorPackageInfo(BaseModel): - cdk_version: Optional[str] = None - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class ConnectorRegistrySourceDefinition(BaseModel): - class Config: - extra = Extra.allow - - sourceDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - sourceType: Optional[Literal["api", "file", "database", "custom"]] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - allowedHosts: Optional[AllowedHosts] = None - suggestedStreams: Optional[SuggestedStreams] = None - maxSecondsBetweenMessages: Optional[int] = Field( - None, - description="Number of seconds allowed between 2 airbyte protocol messages. The source will timeout if this delay is reach", - ) - erdUrl: Optional[str] = Field( - None, description="The URL where you can visualize the ERD" - ) - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - - -class ConnectorRegistryReleases(BaseModel): - class Config: - extra = Extra.forbid - - releaseCandidates: Optional[ConnectorReleaseCandidates] = None - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) - - -class ConnectorReleaseCandidates(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[ - constr(regex=r"^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$"), VersionReleaseCandidate - ] = Field( - ..., - description="Each entry denotes a release candidate version of a connector.", - ) - - -class VersionReleaseCandidate(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Union[ - ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition - ] = Field( - ..., - description="Contains information about a release candidate version of a connector.", - ) - - -class ConnectorRegistryDestinationDefinition(BaseModel): - class Config: - extra = Extra.allow - - destinationDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - tags: Optional[List[str]] = Field( - None, - description="An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - supportsDbt: Optional[bool] = Field( - None, - description="an optional flag indicating whether DBT is used in the normalization. If the flag value is NULL - DBT is not used.", - ) - allowedHosts: Optional[AllowedHosts] = None - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - supportsRefreshes: Optional[bool] = False - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - - -ConnectorRegistrySourceDefinition.update_forward_refs() -ConnectorRegistryReleases.update_forward_refs() -ConnectorReleaseCandidates.update_forward_refs() -VersionReleaseCandidate.update_forward_refs() diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryV0.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryV0.py deleted file mode 100644 index c57c9684ede9..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorRegistryV0.py +++ /dev/null @@ -1,413 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorRegistryV0.yaml - -from __future__ import annotations - -from datetime import date, datetime -from typing import Any, Dict, List, Optional, Union -from uuid import UUID - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class AirbyteInternal(BaseModel): - class Config: - extra = Extra.allow - - sl: Optional[Literal[0, 100, 200, 300]] = None - ql: Optional[Literal[0, 100, 200, 300, 400, 500, 600]] = None - isEnterprise: Optional[bool] = False - requireVersionIncrementsInPullRequests: Optional[bool] = Field( - True, - description="When false, version increment checks will be skipped for this connector", - ) - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class ConnectorPackageInfo(BaseModel): - cdk_version: Optional[str] = None - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class ConnectorRegistryV0(BaseModel): - destinations: List[ConnectorRegistryDestinationDefinition] - sources: List[ConnectorRegistrySourceDefinition] - - -class ConnectorRegistryDestinationDefinition(BaseModel): - class Config: - extra = Extra.allow - - destinationDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - tags: Optional[List[str]] = Field( - None, - description="An array of tags that describe the connector. E.g: language:python, keyword:rds, etc.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - supportsDbt: Optional[bool] = Field( - None, - description="an optional flag indicating whether DBT is used in the normalization. If the flag value is NULL - DBT is not used.", - ) - allowedHosts: Optional[AllowedHosts] = None - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - supportsRefreshes: Optional[bool] = False - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - - -class ConnectorRegistryReleases(BaseModel): - class Config: - extra = Extra.forbid - - releaseCandidates: Optional[ConnectorReleaseCandidates] = None - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) - - -class ConnectorReleaseCandidates(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[ - constr(regex=r"^\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?$"), VersionReleaseCandidate - ] = Field( - ..., - description="Each entry denotes a release candidate version of a connector.", - ) - - -class VersionReleaseCandidate(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Union[ - ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition - ] = Field( - ..., - description="Contains information about a release candidate version of a connector.", - ) - - -class ConnectorRegistrySourceDefinition(BaseModel): - class Config: - extra = Extra.allow - - sourceDefinitionId: UUID - name: str - dockerRepository: str - dockerImageTag: str - documentationUrl: str - icon: Optional[str] = None - iconUrl: Optional[str] = None - sourceType: Optional[Literal["api", "file", "database", "custom"]] = None - spec: Dict[str, Any] - tombstone: Optional[bool] = Field( - False, - description="if false, the configuration is active. if true, then this configuration is permanently off.", - ) - public: Optional[bool] = Field( - False, - description="true if this connector definition is available to all workspaces", - ) - custom: Optional[bool] = Field( - False, description="whether this is a custom connector definition" - ) - releaseStage: Optional[ReleaseStage] = None - supportLevel: Optional[SupportLevel] = None - releaseDate: Optional[date] = Field( - None, - description="The date when this connector was first released, in yyyy-mm-dd format.", - ) - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None - protocolVersion: Optional[str] = Field( - None, description="the Airbyte Protocol version supported by the connector" - ) - allowedHosts: Optional[AllowedHosts] = None - suggestedStreams: Optional[SuggestedStreams] = None - maxSecondsBetweenMessages: Optional[int] = Field( - None, - description="Number of seconds allowed between 2 airbyte protocol messages. The source will timeout if this delay is reach", - ) - erdUrl: Optional[str] = Field( - None, description="The URL where you can visualize the ERD" - ) - releases: Optional[ConnectorRegistryReleases] = None - ab_internal: Optional[AirbyteInternal] = None - generated: Optional[GeneratedFields] = None - packageInfo: Optional[ConnectorPackageInfo] = None - language: Optional[str] = Field( - None, description="The language the connector is written in" - ) - supportsFileTransfer: Optional[bool] = False - supportsDataActivation: Optional[bool] = False - - -ConnectorRegistryV0.update_forward_refs() -ConnectorRegistryDestinationDefinition.update_forward_refs() -ConnectorRegistryReleases.update_forward_refs() -ConnectorReleaseCandidates.update_forward_refs() -VersionReleaseCandidate.update_forward_refs() diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorReleases.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorReleases.py deleted file mode 100644 index 79ed85a208d9..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorReleases.py +++ /dev/null @@ -1,98 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorReleases.yaml - -from __future__ import annotations - -from datetime import date -from typing import Dict, List, Optional - -from pydantic import AnyUrl, BaseModel, Extra, Field, conint, constr -from typing_extensions import Literal - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) - - -class StreamBreakingChangeScope(BaseModel): - class Config: - extra = Extra.forbid - - scopeType: str = Field("stream", const=True) - impactedScopes: List[str] = Field( - ..., - description="List of streams that are impacted by the breaking change.", - min_items=1, - ) - - -class BreakingChangeScope(BaseModel): - __root__: StreamBreakingChangeScope = Field( - ..., - description="A scope that can be used to limit the impact of a breaking change.", - ) - - -class VersionBreakingChange(BaseModel): - class Config: - extra = Extra.forbid - - upgradeDeadline: date = Field( - ..., - description="The deadline by which to upgrade before the breaking change takes effect.", - ) - message: str = Field( - ..., description="Descriptive message detailing the breaking change." - ) - deadlineAction: Optional[Literal["auto_upgrade", "disable"]] = Field( - None, description="Action to do when the deadline is reached." - ) - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version}", - ) - scopedImpact: Optional[List[BreakingChangeScope]] = Field( - None, - description="List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types.", - min_items=1, - ) - - -class ConnectorBreakingChanges(BaseModel): - class Config: - extra = Extra.forbid - - __root__: Dict[constr(regex=r"^\d+\.\d+\.\d+$"), VersionBreakingChange] = Field( - ..., - description="Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade.", - title="ConnectorBreakingChanges", - ) - - -class ConnectorReleases(BaseModel): - class Config: - extra = Extra.forbid - - rolloutConfiguration: Optional[RolloutConfiguration] = None - breakingChanges: Optional[ConnectorBreakingChanges] = None - migrationDocumentationUrl: Optional[AnyUrl] = Field( - None, - description="URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorTestSuiteOptions.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorTestSuiteOptions.py deleted file mode 100644 index a40b4094c818..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ConnectorTestSuiteOptions.py +++ /dev/null @@ -1,58 +0,0 @@ -# generated by datamodel-codegen: -# filename: ConnectorTestSuiteOptions.yaml - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class SecretStore(BaseModel): - class Config: - extra = Extra.forbid - - alias: Optional[str] = Field( - None, - description="The alias of the secret store which can map to its actual secret address", - ) - type: Optional[Literal["GSM"]] = Field( - None, description="The type of the secret store" - ) - - -class TestConnections(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The connection name") - id: str = Field(..., description="The connection ID") - - -class Secret(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The secret name in the secret store") - fileName: Optional[str] = Field( - None, - description="The name of the file to which the secret value would be persisted", - ) - secretStore: SecretStore - - -class ConnectorTestSuiteOptions(BaseModel): - class Config: - extra = Extra.forbid - - suite: Literal["unitTests", "integrationTests", "acceptanceTests", "liveTests"] = ( - Field(..., description="Name of the configured test suite") - ) - testSecrets: Optional[List[Secret]] = Field( - None, description="List of secrets required to run the test suite" - ) - testConnections: Optional[List[TestConnections]] = Field( - None, - description="List of sandbox cloud connections that tests can be run against", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GeneratedFields.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GeneratedFields.py deleted file mode 100644 index 62cfeadbe366..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GeneratedFields.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: GeneratedFields.yaml - -from __future__ import annotations - -from datetime import datetime -from typing import Any, Optional, Union - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None - - -class ConnectorMetrics(BaseModel): - all: Optional[Any] = None - cloud: Optional[Any] = None - oss: Optional[Any] = None - - -class ConnectorMetric(BaseModel): - class Config: - extra = Extra.allow - - usage: Optional[Union[str, Literal["low", "medium", "high"]]] = None - sync_success_rate: Optional[Union[str, Literal["low", "medium", "high"]]] = None - connector_version: Optional[str] = None - - -class GeneratedFields(BaseModel): - git: Optional[GitInfo] = None - source_file_info: Optional[SourceFileInfo] = None - metrics: Optional[ConnectorMetrics] = None - sbomUrl: Optional[str] = Field(None, description="URL to the SBOM file") diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GitInfo.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GitInfo.py deleted file mode 100644 index 1e947b632188..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/GitInfo.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: GitInfo.yaml - -from __future__ import annotations - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Extra, Field - - -class GitInfo(BaseModel): - class Config: - extra = Extra.forbid - - commit_sha: Optional[str] = Field( - None, - description="The git commit sha of the last commit that modified this file.", - ) - commit_timestamp: Optional[datetime] = Field( - None, - description="The git commit timestamp of the last commit that modified this file.", - ) - commit_author: Optional[str] = Field( - None, - description="The git commit author of the last commit that modified this file.", - ) - commit_author_email: Optional[str] = Field( - None, - description="The git commit author email of the last commit that modified this file.", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/JobType.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/JobType.py deleted file mode 100644 index aef4f7ad5f99..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/JobType.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: JobType.yaml - -from __future__ import annotations - -from pydantic import BaseModel, Field -from typing_extensions import Literal - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/NormalizationDestinationDefinitionConfig.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/NormalizationDestinationDefinitionConfig.py deleted file mode 100644 index 00a642bfaeb1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/NormalizationDestinationDefinitionConfig.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: NormalizationDestinationDefinitionConfig.yaml - -from __future__ import annotations - -from pydantic import BaseModel, Extra, Field - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RegistryOverrides.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RegistryOverrides.py deleted file mode 100644 index eb6908bc65b2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RegistryOverrides.py +++ /dev/null @@ -1,111 +0,0 @@ -# generated by datamodel-codegen: -# filename: RegistryOverrides.yaml - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import AnyUrl, BaseModel, Extra, Field -from typing_extensions import Literal - - -class AllowedHosts(BaseModel): - class Config: - extra = Extra.allow - - hosts: Optional[List[str]] = Field( - None, - description="An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted.", - ) - - -class NormalizationDestinationDefinitionConfig(BaseModel): - class Config: - extra = Extra.allow - - normalizationRepository: str = Field( - ..., - description="a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used.", - ) - normalizationTag: str = Field( - ..., - description="a field indicating the tag of the docker repository to be used for normalization.", - ) - normalizationIntegrationType: str = Field( - ..., - description="a field indicating the type of integration dialect to use for normalization.", - ) - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None - - -class JobType(BaseModel): - __root__: Literal[ - "get_spec", - "check_connection", - "discover_schema", - "sync", - "reset_connection", - "connection_updater", - "replicate", - ] = Field( - ..., - description="enum that describes the different types of jobs that the platform runs.", - title="JobType", - ) - - -class JobTypeResourceLimit(BaseModel): - class Config: - extra = Extra.forbid - - jobType: JobType - resourceRequirements: ResourceRequirements - - -class ActorDefinitionResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - default: Optional[ResourceRequirements] = Field( - None, - description="if set, these are the requirements that should be set for ALL jobs run for this actor definition.", - ) - jobSpecific: Optional[List[JobTypeResourceLimit]] = None - - -class RegistryOverrides(BaseModel): - class Config: - extra = Extra.forbid - - enabled: bool - name: Optional[str] = None - dockerRepository: Optional[str] = None - dockerImageTag: Optional[str] = None - supportsDbt: Optional[bool] = None - supportsNormalization: Optional[bool] = None - license: Optional[str] = None - documentationUrl: Optional[AnyUrl] = None - connectorSubtype: Optional[str] = None - allowedHosts: Optional[AllowedHosts] = None - normalizationConfig: Optional[NormalizationDestinationDefinitionConfig] = None - suggestedStreams: Optional[SuggestedStreams] = None - resourceRequirements: Optional[ActorDefinitionResourceRequirements] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ReleaseStage.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ReleaseStage.py deleted file mode 100644 index cb7c9b909b0b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ReleaseStage.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: ReleaseStage.yaml - -from __future__ import annotations - -from pydantic import BaseModel, Field -from typing_extensions import Literal - - -class ReleaseStage(BaseModel): - __root__: Literal["alpha", "beta", "generally_available", "custom"] = Field( - ..., - description="enum that describes a connector's release stage", - title="ReleaseStage", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RemoteRegistries.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RemoteRegistries.py deleted file mode 100644 index b44447eb9c76..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RemoteRegistries.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: RemoteRegistries.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field - - -class PyPi(BaseModel): - class Config: - extra = Extra.forbid - - enabled: bool - packageName: str = Field(..., description="The name of the package on PyPi.") - - -class RemoteRegistries(BaseModel): - class Config: - extra = Extra.forbid - - pypi: Optional[PyPi] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ResourceRequirements.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ResourceRequirements.py deleted file mode 100644 index abc7e6173d05..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/ResourceRequirements.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: ResourceRequirements.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra - - -class ResourceRequirements(BaseModel): - class Config: - extra = Extra.forbid - - cpu_request: Optional[str] = None - cpu_limit: Optional[str] = None - memory_request: Optional[str] = None - memory_limit: Optional[str] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RolloutConfiguration.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RolloutConfiguration.py deleted file mode 100644 index ceebd789da84..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/RolloutConfiguration.py +++ /dev/null @@ -1,29 +0,0 @@ -# generated by datamodel-codegen: -# filename: RolloutConfiguration.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field, conint - - -class RolloutConfiguration(BaseModel): - class Config: - extra = Extra.forbid - - enableProgressiveRollout: Optional[bool] = Field( - False, description="Whether to enable progressive rollout for the connector." - ) - initialPercentage: Optional[conint(ge=0, le=100)] = Field( - 0, - description="The percentage of users that should receive the new version initially.", - ) - maxPercentage: Optional[conint(ge=0, le=100)] = Field( - 50, - description="The percentage of users who should receive the release candidate during the test phase before full rollout.", - ) - advanceDelayMinutes: Optional[conint(ge=10)] = Field( - 10, - description="The number of minutes to wait before advancing the rollout percentage.", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/Secret.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/Secret.py deleted file mode 100644 index ee6498906619..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/Secret.py +++ /dev/null @@ -1,34 +0,0 @@ -# generated by datamodel-codegen: -# filename: Secret.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class SecretStore(BaseModel): - class Config: - extra = Extra.forbid - - alias: Optional[str] = Field( - None, - description="The alias of the secret store which can map to its actual secret address", - ) - type: Optional[Literal["GSM"]] = Field( - None, description="The type of the secret store" - ) - - -class Secret(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The secret name in the secret store") - fileName: Optional[str] = Field( - None, - description="The name of the file to which the secret value would be persisted", - ) - secretStore: SecretStore diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SecretStore.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SecretStore.py deleted file mode 100644 index a8df2ec03b11..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SecretStore.py +++ /dev/null @@ -1,22 +0,0 @@ -# generated by datamodel-codegen: -# filename: SecretStore.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - - -class SecretStore(BaseModel): - class Config: - extra = Extra.forbid - - alias: Optional[str] = Field( - None, - description="The alias of the secret store which can map to its actual secret address", - ) - type: Optional[Literal["GSM"]] = Field( - None, description="The type of the secret store" - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SourceFileInfo.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SourceFileInfo.py deleted file mode 100644 index ad3d859338b5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SourceFileInfo.py +++ /dev/null @@ -1,16 +0,0 @@ -# generated by datamodel-codegen: -# filename: SourceFileInfo.yaml - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel - - -class SourceFileInfo(BaseModel): - metadata_etag: Optional[str] = None - metadata_file_path: Optional[str] = None - metadata_bucket_name: Optional[str] = None - metadata_last_modified: Optional[str] = None - registry_entry_generated_at: Optional[str] = None diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SuggestedStreams.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SuggestedStreams.py deleted file mode 100644 index 9a3d7cdf4012..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SuggestedStreams.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: SuggestedStreams.yaml - -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Extra, Field - - -class SuggestedStreams(BaseModel): - class Config: - extra = Extra.allow - - streams: Optional[List[str]] = Field( - None, - description="An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested.", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SupportLevel.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SupportLevel.py deleted file mode 100644 index 7c5e001789f3..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/SupportLevel.py +++ /dev/null @@ -1,15 +0,0 @@ -# generated by datamodel-codegen: -# filename: SupportLevel.yaml - -from __future__ import annotations - -from pydantic import BaseModel, Field -from typing_extensions import Literal - - -class SupportLevel(BaseModel): - __root__: Literal["community", "certified", "archived"] = Field( - ..., - description="enum that describes a connector's release stage", - title="SupportLevel", - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/TestConnections.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/TestConnections.py deleted file mode 100644 index 47f65d3462b0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/TestConnections.py +++ /dev/null @@ -1,14 +0,0 @@ -# generated by datamodel-codegen: -# filename: TestConnections.yaml - -from __future__ import annotations - -from pydantic import BaseModel, Extra, Field - - -class TestConnections(BaseModel): - class Config: - extra = Extra.forbid - - name: str = Field(..., description="The connection name") - id: str = Field(..., description="The connection ID") diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/__init__.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/__init__.py deleted file mode 100644 index 3877faa22716..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/generated/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by generate-metadata-models -from .ActorDefinitionResourceRequirements import * -from .AirbyteInternal import * -from .AllowedHosts import * -from .ConnectorBreakingChanges import * -from .ConnectorBuildOptions import * -from .ConnectorIPCOptions import * -from .ConnectorMetadataDefinitionV0 import * -from .ConnectorMetrics import * -from .ConnectorPackageInfo import * -from .ConnectorRegistryDestinationDefinition import * -from .ConnectorRegistryReleases import * -from .ConnectorRegistrySourceDefinition import * -from .ConnectorRegistryV0 import * -from .ConnectorReleases import * -from .ConnectorTestSuiteOptions import * -from .GeneratedFields import * -from .GitInfo import * -from .JobType import * -from .NormalizationDestinationDefinitionConfig import * -from .RegistryOverrides import * -from .ReleaseStage import * -from .RemoteRegistries import * -from .ResourceRequirements import * -from .RolloutConfiguration import * -from .Secret import * -from .SecretStore import * -from .SourceFileInfo import * -from .SuggestedStreams import * -from .SupportLevel import * -from .TestConnections import * diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ActorDefinitionResourceRequirements.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ActorDefinitionResourceRequirements.yaml deleted file mode 100644 index f9ea5817c1ca..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ActorDefinitionResourceRequirements.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ActorDefinitionResourceRequirements.yaml -title: ActorDefinitionResourceRequirements -description: actor definition specific resource requirements -type: object -# set to false because we need the validations on seeds to be strict. otherwise, we will just add whatever is in the seed file into the db. -additionalProperties: false -properties: - default: - description: if set, these are the requirements that should be set for ALL jobs run for this actor definition. - "$ref": ResourceRequirements.yaml - jobSpecific: - type: array - items: - "$ref": "#/definitions/JobTypeResourceLimit" -definitions: - JobTypeResourceLimit: - description: sets resource requirements for a specific job type for an actor definition. these values override the default, if both are set. - type: object - # set to false because we need the validations on seeds to be strict. otherwise, we will just add whatever is in the seed file into the db. - additionalProperties: false - required: - - jobType - - resourceRequirements - properties: - jobType: - "$ref": JobType.yaml - resourceRequirements: - "$ref": ResourceRequirements.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AirbyteInternal.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AirbyteInternal.yaml deleted file mode 100644 index 4d310406c8e1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AirbyteInternal.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/AirbyteInternal.yml -title: AirbyteInternal -description: Fields for internal use only -type: object -additionalProperties: true -properties: - sl: - type: integer - enum: - - 0 - - 100 - - 200 - - 300 - ql: - type: integer - enum: - - 0 - - 100 - - 200 - - 300 - - 400 - - 500 - - 600 - isEnterprise: - type: boolean - default: false - requireVersionIncrementsInPullRequests: - type: boolean - default: true - description: When false, version increment checks will be skipped for this connector diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AllowedHosts.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AllowedHosts.yaml deleted file mode 100644 index e796573835e3..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AllowedHosts.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/AllowedHosts.yaml -title: AllowedHosts -description: A connector's allowed hosts. If present, the platform will limit communication to only hosts which are listed in `AllowedHosts.hosts`. -type: object -additionalProperties: true -properties: - hosts: - type: array - description: An array of hosts that this connector can connect to. AllowedHosts not being present for the source or destination means that access to all hosts is allowed. An empty list here means that no network access is granted. - items: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBreakingChanges.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBreakingChanges.yaml deleted file mode 100644 index 3751e7bb6f74..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBreakingChanges.yaml +++ /dev/null @@ -1,65 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBreakingChanges.yaml -title: ConnectorBreakingChanges -description: Each entry denotes a breaking change in a specific version of a connector that requires user action to upgrade. -type: object -additionalProperties: false -minProperties: 1 -patternProperties: - "^\\d+\\.\\d+\\.\\d+$": - $ref: "#/definitions/VersionBreakingChange" -definitions: - VersionBreakingChange: - description: Contains information about a breaking change, including the deadline to upgrade and a message detailing the change. - type: object - additionalProperties: false - required: - - upgradeDeadline - - message - properties: - upgradeDeadline: - description: The deadline by which to upgrade before the breaking change takes effect. - type: string - format: date - message: - description: Descriptive message detailing the breaking change. - type: string - deadlineAction: - description: Action to do when the deadline is reached. - type: string - enum: - - auto_upgrade - - disable - migrationDocumentationUrl: - description: URL to documentation on how to migrate to the current version. Defaults to ${documentationUrl}-migrations#${version} - type: string - format: uri - scopedImpact: - description: List of scopes that are impacted by the breaking change. If not specified, the breaking change cannot be scoped to reduce impact via the supported scope types. - type: array - minItems: 1 - items: - $ref: "#/definitions/BreakingChangeScope" - BreakingChangeScope: - description: A scope that can be used to limit the impact of a breaking change. - type: object - oneOf: - - $ref: "#/definitions/StreamBreakingChangeScope" - StreamBreakingChangeScope: - description: A scope that can be used to limit the impact of a breaking change to specific streams. - type: object - additionalProperties: false - required: - - scopeType - - impactedScopes - properties: - scopeType: - type: string - const: stream - impactedScopes: - description: List of streams that are impacted by the breaking change. - type: array - minItems: 1 - items: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBuildOptions.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBuildOptions.yaml deleted file mode 100644 index c040dd5404be..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBuildOptions.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorBuildOptions.yaml -title: ConnectorBuildOptions -description: metadata specific to the build process. -type: object -additionalProperties: false -properties: - baseImage: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorIPCOptions.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorIPCOptions.yaml deleted file mode 100644 index a8041f139259..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorIPCOptions.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorIPCOptions.yaml -title: ConnectorIPCOptions -type: object -required: - - dataChannel -additionalProperties: false -properties: - dataChannel: - type: object - required: - - version - - supportedSerialization - - supportedTransport - additionalProperties: false - properties: - version: - type: string - supportedSerialization: - type: array - items: - type: string - enum: ["JSONL", "PROTOBUF"] - supportedTransport: - type: array - items: - type: string - enum: ["STDIO", "SOCKET"] diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml deleted file mode 100644 index 04d0fdce1fb0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetadataDefinitionV0.yaml +++ /dev/null @@ -1,173 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/ConnectorMetadataDefinitionV0.yml - -title: ConnectorMetadataDefinitionV0 -description: describes the metadata of a connector -type: object -required: - - metadataSpecVersion - - data -additionalProperties: false -properties: - metadataSpecVersion: - type: "string" - data: - type: object - required: - - name - - definitionId - - connectorType - - dockerRepository - - dockerImageTag - - license - - documentationUrl - - githubIssueLabel - - connectorSubtype - - releaseStage - additionalProperties: false - properties: - name: - type: string - icon: - type: string - definitionId: - type: string - format: uuid - connectorBuildOptions: - "$ref": ConnectorBuildOptions.yaml - connectorTestSuitesOptions: - type: array - items: - "$ref": ConnectorTestSuiteOptions.yaml - connectorType: - type: string - enum: - - destination - - source - dockerRepository: - type: string - dockerImageTag: - type: string - supportsDbt: - type: boolean - supportsNormalization: - type: boolean - license: - type: string - documentationUrl: - type: string - format: uri - externalDocumentationUrls: - type: array - description: "An array of external vendor documentation URLs (changelogs, API references, deprecation notices, etc.)" - items: - type: object - required: - - title - - url - additionalProperties: false - properties: - title: - type: string - description: "Display title for the documentation link" - url: - type: string - format: uri - description: "URL to the external documentation" - type: - type: string - description: "Category of documentation" - enum: - - api_deprecations - - api_reference - - api_release_history - - authentication_guide - - data_model_reference - - developer_community - - migration_guide - - openapi_spec - - other - - permissions_scopes - - rate_limits - - sql_reference - - status_page - requiresLogin: - type: boolean - description: "Whether the URL requires authentication to access" - default: false - githubIssueLabel: - type: string - maxSecondsBetweenMessages: - description: Maximum delay between 2 airbyte protocol messages, in second. The source will timeout if this delay is reached - type: integer - releaseDate: - description: The date when this connector was first released, in yyyy-mm-dd format. - type: string - format: date - protocolVersion: - type: string - description: the Airbyte Protocol version supported by the connector - erdUrl: - type: string - description: The URL where you can visualize the ERD - connectorSubtype: - type: string - enum: - - api - - database - - datalake - - file - - custom - - message_queue - - unknown - - vectorstore - releaseStage: - "$ref": ReleaseStage.yaml - supportLevel: - "$ref": SupportLevel.yaml - tags: - type: array - description: "An array of tags that describe the connector. E.g: language:python, keyword:rds, etc." - items: - type: string - default: [] - registryOverrides: - anyOf: - - type: object - additionalProperties: false - properties: - oss: - anyOf: - - "$ref": RegistryOverrides.yaml - cloud: - anyOf: - - "$ref": RegistryOverrides.yaml - - allowedHosts: - "$ref": AllowedHosts.yaml - releases: - "$ref": ConnectorReleases.yaml - normalizationConfig: - "$ref": NormalizationDestinationDefinitionConfig.yaml - suggestedStreams: - "$ref": SuggestedStreams.yaml - resourceRequirements: - "$ref": ActorDefinitionResourceRequirements.yaml - ab_internal: - "$ref": AirbyteInternal.yaml - remoteRegistries: - "$ref": RemoteRegistries.yaml - supportsRefreshes: - type: boolean - default: false - generated: - "$ref": GeneratedFields.yaml - supportsFileTransfer: - type: boolean - default: false - supportsDataActivation: - type: boolean - default: false - connectorIPCOptions: - "$ref": ConnectorIPCOptions.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetrics.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetrics.yaml deleted file mode 100644 index 0684df0c0a6c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorMetrics.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/ConnectorMetrics.yaml -title: ConnectorMetrics -description: Information about the source file that generated the registry entry -type: object -properties: - all: - type: ConnectorMetric - cloud: - type: ConnectorMetric - oss: - type: ConnectorMetric -definitions: - ConnectorMetric: - type: object - properties: - usage: - oneOf: - - type: string - - type: string - enum: [low, medium, high] - sync_success_rate: - oneOf: - - type: string - - type: string - enum: [low, medium, high] - connector_version: - type: string - additionalProperties: true diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorPackageInfo.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorPackageInfo.yaml deleted file mode 100644 index 32c478fe8b97..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorPackageInfo.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/ConnectorPackageInfo.yaml -title: ConnectorPackageInfo -description: Information about the contents of the connector image -type: object -properties: - cdk_version: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryDestinationDefinition.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryDestinationDefinition.yaml deleted file mode 100644 index 64333fceffea..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryDestinationDefinition.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-config/config-models/src/main/resources/types/ConnectorRegistryDestinationDefinition.yaml -title: ConnectorRegistryDestinationDefinition -description: describes a destination -type: object -required: - - destinationDefinitionId - - name - - dockerRepository - - dockerImageTag - - documentationUrl - - spec -additionalProperties: true -properties: - destinationDefinitionId: - type: string - format: uuid - name: - type: string - dockerRepository: - type: string - dockerImageTag: - type: string - documentationUrl: - type: string - icon: - type: string - iconUrl: - type: string - spec: - type: object - tombstone: - description: if false, the configuration is active. if true, then this - configuration is permanently off. - type: boolean - default: false - public: - description: true if this connector definition is available to all workspaces - type: boolean - default: false - custom: - description: whether this is a custom connector definition - type: boolean - default: false - releaseStage: - "$ref": ReleaseStage.yaml - supportLevel: - "$ref": SupportLevel.yaml - releaseDate: - description: The date when this connector was first released, in yyyy-mm-dd format. - type: string - format: date - tags: - type: array - description: "An array of tags that describe the connector. E.g: language:python, keyword:rds, etc." - items: - type: string - resourceRequirements: - "$ref": ActorDefinitionResourceRequirements.yaml - protocolVersion: - type: string - description: the Airbyte Protocol version supported by the connector - normalizationConfig: - "$ref": NormalizationDestinationDefinitionConfig.yaml - supportsDbt: - type: boolean - description: an optional flag indicating whether DBT is used in the normalization. If the flag value is NULL - DBT is not used. - allowedHosts: - "$ref": AllowedHosts.yaml - releases: - "$ref": ConnectorRegistryReleases.yaml - ab_internal: - "$ref": AirbyteInternal.yaml - supportsRefreshes: - type: boolean - default: false - supportsFileTransfer: - type: boolean - default: false - supportsDataActivation: - type: boolean - default: false - generated: - "$ref": GeneratedFields.yaml - packageInfo: - "$ref": ConnectorPackageInfo.yaml - language: - type: string - description: The language the connector is written in diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryReleases.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryReleases.yaml deleted file mode 100644 index b1b26ce792e4..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryReleases.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryReleases.yaml -title: ConnectorRegistryReleases -description: Contains information about different types of releases for a connector. -type: object -additionalProperties: false -properties: - releaseCandidates: - $ref: "#/definitions/ConnectorReleaseCandidates" - rolloutConfiguration: - $ref: RolloutConfiguration.yaml - breakingChanges: - $ref: ConnectorBreakingChanges.yaml - migrationDocumentationUrl: - description: URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations - type: string - format: uri -definitions: - ConnectorReleaseCandidates: - description: Each entry denotes a release candidate version of a connector. - type: object - additionalProperties: false - minProperties: 1 - maxProperties: 1 - patternProperties: - "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-.]+)?$": - $ref: "#/definitions/VersionReleaseCandidate" - VersionReleaseCandidate: - description: Contains information about a release candidate version of a connector. - additionalProperties: false - type: object - oneOf: - - $ref: ConnectorRegistrySourceDefinition.yaml - - $ref: ConnectorRegistryDestinationDefinition.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistrySourceDefinition.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistrySourceDefinition.yaml deleted file mode 100644 index fd71b4e3878f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistrySourceDefinition.yaml +++ /dev/null @@ -1,92 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-config/config-models/src/main/resources/types/ConnectorRegistrySourceDefinition.yaml -title: ConnectorRegistrySourceDefinition -description: describes a source -type: object -required: - - sourceDefinitionId - - name - - dockerRepository - - dockerImageTag - - documentationUrl - - spec -additionalProperties: true -properties: - sourceDefinitionId: - type: string - format: uuid - name: - type: string - dockerRepository: - type: string - dockerImageTag: - type: string - documentationUrl: - type: string - icon: - type: string - iconUrl: - type: string - sourceType: - type: string - enum: - - api - - file - - database - - custom - spec: - type: object - tombstone: - description: if false, the configuration is active. if true, then this - configuration is permanently off. - type: boolean - default: false - public: - description: true if this connector definition is available to all workspaces - type: boolean - default: false - custom: - description: whether this is a custom connector definition - type: boolean - default: false - releaseStage: - "$ref": ReleaseStage.yaml - supportLevel: - "$ref": SupportLevel.yaml - releaseDate: - description: The date when this connector was first released, in yyyy-mm-dd format. - type: string - format: date - resourceRequirements: - "$ref": ActorDefinitionResourceRequirements.yaml - protocolVersion: - type: string - description: the Airbyte Protocol version supported by the connector - allowedHosts: - "$ref": AllowedHosts.yaml - suggestedStreams: - "$ref": SuggestedStreams.yaml - maxSecondsBetweenMessages: - description: Number of seconds allowed between 2 airbyte protocol messages. The source will timeout if this delay is reach - type: integer - erdUrl: - type: string - description: The URL where you can visualize the ERD - releases: - "$ref": ConnectorRegistryReleases.yaml - ab_internal: - "$ref": AirbyteInternal.yaml - generated: - "$ref": GeneratedFields.yaml - packageInfo: - "$ref": ConnectorPackageInfo.yaml - language: - type: string - description: The language the connector is written in - supportsFileTransfer: - type: boolean - default: false - supportsDataActivation: - type: boolean - default: false diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryV0.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryV0.yaml deleted file mode 100644 index 8e3620816d65..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorRegistryV0.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-config/config-models/src/main/resources/types/ConnectorRegistryV0.yaml -title: ConnectorRegistryV0 -description: describes the collection of connectors retrieved from a registry -type: object -required: - - destinations - - sources -properties: - destinations: - type: array - items: - $ref: ConnectorRegistryDestinationDefinition.yaml - sources: - type: array - items: - $ref: ConnectorRegistrySourceDefinition.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorReleases.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorReleases.yaml deleted file mode 100644 index 80f753045300..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorReleases.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorReleases.yaml -title: ConnectorReleases -description: Contains information about different types of releases for a connector. -type: object -additionalProperties: false -properties: - rolloutConfiguration: - $ref: RolloutConfiguration.yaml - breakingChanges: - $ref: ConnectorBreakingChanges.yaml - migrationDocumentationUrl: - description: URL to documentation on how to migrate from the previous version to the current version. Defaults to ${documentationUrl}-migrations - type: string - format: uri diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorTestSuiteOptions.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorTestSuiteOptions.yaml deleted file mode 100644 index 2830332ee520..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorTestSuiteOptions.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ConnectorTestOptions.yaml -title: ConnectorTestSuiteOptions -description: Options for a specific connector test suite. -type: object -required: - - suite -additionalProperties: false -properties: - suite: - description: "Name of the configured test suite" - type: string - enum: - - "unitTests" - - "integrationTests" - - "acceptanceTests" - - "liveTests" - testSecrets: - description: "List of secrets required to run the test suite" - type: array - items: - "$ref": "Secret.yaml" - testConnections: - description: "List of sandbox cloud connections that tests can be run against" - type: array - items: - "$ref": "TestConnections.yaml" diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GeneratedFields.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GeneratedFields.yaml deleted file mode 100644 index 0fc59e75d4b5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GeneratedFields.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/GeneratedFields.yaml -title: GeneratedFields -description: Optional schema for fields generated at metadata upload time -type: object -properties: - git: - "$ref": GitInfo.yaml - source_file_info: - "$ref": SourceFileInfo.yaml - metrics: - "$ref": ConnectorMetrics.yaml - sbomUrl: - type: string - description: URL to the SBOM file diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GitInfo.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GitInfo.yaml deleted file mode 100644 index ce03d88c6f70..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GitInfo.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/GitInfo.yaml -title: GitInfo -description: Information about the author of the last commit that modified this file. DO NOT DEFINE THIS FIELD MANUALLY. It will be overwritten by the CI. -type: object -additionalProperties: false -properties: - commit_sha: - type: string - description: The git commit sha of the last commit that modified this file. - commit_timestamp: - type: string - format: date-time - description: The git commit timestamp of the last commit that modified this file. - commit_author: - type: string - description: The git commit author of the last commit that modified this file. - commit_author_email: - type: string - description: The git commit author email of the last commit that modified this file. diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/JobType.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/JobType.yaml deleted file mode 100644 index 5cac4337e2bf..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/JobType.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/JobType.yaml -title: JobType -description: enum that describes the different types of jobs that the platform runs. -type: string -enum: - - get_spec - - check_connection - - discover_schema - - sync - - reset_connection - - connection_updater - - replicate diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/NormalizationDestinationDefinitionConfig.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/NormalizationDestinationDefinitionConfig.yaml deleted file mode 100644 index caf3d79e9c15..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/NormalizationDestinationDefinitionConfig.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/NormalizationDestinationDefinitionConfig.yaml -title: NormalizationDestinationDefinitionConfig -description: describes a normalization config for destination definition -type: object -required: - - normalizationRepository - - normalizationTag - - normalizationIntegrationType -additionalProperties: true -properties: - normalizationRepository: - type: string - description: a field indicating the name of the repository to be used for normalization. If the value of the flag is NULL - normalization is not used. - normalizationTag: - type: string - description: a field indicating the tag of the docker repository to be used for normalization. - normalizationIntegrationType: - type: string - description: a field indicating the type of integration dialect to use for normalization. diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RegistryOverrides.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RegistryOverrides.yaml deleted file mode 100644 index abd651aacc29..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RegistryOverrides.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/RegistryOverrides.yml -title: RegistryOverrides -description: describes the overrides per registry of a connector -type: object -additionalProperties: false -required: - - enabled -properties: - enabled: - type: boolean - default: false - name: - type: string - dockerRepository: - type: string - dockerImageTag: - type: string - supportsDbt: - type: boolean - supportsNormalization: - type: boolean - license: - type: string - documentationUrl: - type: string - format: uri - connectorSubtype: - type: string - allowedHosts: - $ref: AllowedHosts.yaml - normalizationConfig: - "$ref": NormalizationDestinationDefinitionConfig.yaml - suggestedStreams: - $ref: SuggestedStreams.yaml - resourceRequirements: - $ref: ActorDefinitionResourceRequirements.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ReleaseStage.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ReleaseStage.yaml deleted file mode 100644 index 0a0767efd5ef..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ReleaseStage.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-config/config-models/src/main/resources/types/ReleaseStage.yaml -title: ReleaseStage -description: enum that describes a connector's release stage -type: string -enum: - - alpha - - beta - - generally_available - - custom diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RemoteRegistries.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RemoteRegistries.yaml deleted file mode 100644 index 474dc3d0a312..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RemoteRegistries.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/RemoteRegistries.yml -title: RemoteRegistries -description: describes how the connector is published to remote registries -type: object -additionalProperties: false -properties: - pypi: - $ref: "#/definitions/PyPi" -definitions: - PyPi: - title: PyPi - description: describes the PyPi publishing options - type: object - additionalProperties: false - required: - - enabled - - packageName - properties: - enabled: - type: boolean - packageName: - type: string - description: The name of the package on PyPi. diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ResourceRequirements.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ResourceRequirements.yaml deleted file mode 100644 index aef3e6f6c9f0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ResourceRequirements.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/ResourceRequirements.yaml -title: ResourceRequirements -description: generic configuration for pod source requirements -type: object -additionalProperties: false -properties: - cpu_request: - type: string - cpu_limit: - type: string - memory_request: - type: string - memory_limit: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RolloutConfiguration.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RolloutConfiguration.yaml deleted file mode 100644 index c2811bb8ed30..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RolloutConfiguration.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/RolloutConfiguration.yaml -title: RolloutConfiguration -description: configuration for the rollout of a connector -type: object -additionalProperties: false -properties: - enableProgressiveRollout: - type: boolean - default: false - description: Whether to enable progressive rollout for the connector. - initialPercentage: - type: integer - minimum: 0 - maximum: 100 - default: 0 - description: The percentage of users that should receive the new version initially. - maxPercentage: - type: integer - minimum: 0 - maximum: 100 - default: 50 - description: The percentage of users who should receive the release candidate during the test phase before full rollout. - advanceDelayMinutes: - type: integer - minimum: 10 - default: 10 - description: The number of minutes to wait before advancing the rollout percentage. diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/Secret.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/Secret.yaml deleted file mode 100644 index a6687c0e698b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/Secret.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/Secret.yaml -title: Secret -description: An object describing a secret's metadata -type: object -required: - - name - - secretStore -additionalProperties: false -properties: - name: - type: string - description: "The secret name in the secret store" - fileName: - type: string - description: "The name of the file to which the secret value would be persisted" - secretStore: - "$ref": SecretStore.yaml diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SecretStore.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SecretStore.yaml deleted file mode 100644 index 2a85367b7985..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SecretStore.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SecretStore.yaml -title: SecretStore -description: An object describing a secret store metadata -type: object -additionalProperties: false -properties: - alias: - type: string - description: "The alias of the secret store which can map to its actual secret address" - type: - type: string - description: "The type of the secret store" - enum: - - "GSM" diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SourceFileInfo.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SourceFileInfo.yaml deleted file mode 100644 index 7345cbf5b564..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SourceFileInfo.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/SourceFileInfo.yaml -title: SourceFileInfo -description: Information about the source file that generated the registry entry -type: object -properties: - metadata_etag: - type: string - metadata_file_path: - type: string - metadata_bucket_name: - type: string - metadata_last_modified: - type: string - registry_entry_generated_at: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SuggestedStreams.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SuggestedStreams.yaml deleted file mode 100644 index 4c540ba7fe98..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SuggestedStreams.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SuggestedStreams.yaml -title: SuggestedStreams -description: A source's suggested streams. These will be suggested by default for new connections using this source. Otherwise, all streams will be selected. This is useful for when your source has a lot of streams, but the average user will only want a subset of them synced. -type: object -additionalProperties: true -properties: - streams: - type: array - description: An array of streams that this connector suggests the average user will want. SuggestedStreams not being present for the source means that all streams are suggested. An empty list here means that no streams are suggested. - items: - type: string diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SupportLevel.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SupportLevel.yaml deleted file mode 100644 index 752d99a27c29..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/SupportLevel.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors_ci/metadata_service/lib/models/src/SupportLevel.yaml -title: SupportLevel -description: enum that describes a connector's release stage -type: string -enum: - - community - - certified - - archived diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/TestConnections.yaml b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/TestConnections.yaml deleted file mode 100644 index 0ca92550ae9f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/TestConnections.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -"$schema": http://json-schema.org/draft-07/schema# -"$id": https://github.com/airbytehq/airbyte/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/src/TestConnections.yaml -title: TestConnections -description: List of sandbox cloud connections that tests can be run against -type: object -required: - - name - - id -additionalProperties: false -properties: - name: - type: string - description: "The connection name" - id: - type: string - description: "The connection ID" diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/transform.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/transform.py deleted file mode 100644 index 971e3e76008a..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/models/transform.py +++ /dev/null @@ -1,71 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json - -from pydantic import BaseModel - - -def _apply_default_pydantic_kwargs(kwargs: dict) -> dict: - """A helper function to apply default kwargs to pydantic models. - - Args: - kwargs (dict): the kwargs to apply - - Returns: - dict: the kwargs with defaults applied - """ - default_kwargs = { - "by_alias": True, # Ensure that the original field name from the jsonschema is used in the event it begins with an underscore (e.g. ab_internal) - "exclude_none": True, # Exclude fields that are None - } - - return {**default_kwargs, **kwargs} - - -def to_json_sanitized_dict(pydantic_model_obj: BaseModel, **kwargs) -> dict: - """A helper function to convert a pydantic model to a sanitized dict. - - Without this pydantic dictionary may contain values that are not JSON serializable. - - Args: - pydantic_model_obj (BaseModel): a pydantic model - - Returns: - dict: a sanitized dictionary - """ - - return json.loads(to_json(pydantic_model_obj, **kwargs)) - - -def to_json(pydantic_model_obj: BaseModel, **kwargs) -> str: - """A helper function to convert a pydantic model to a json string. - - Without this pydantic dictionary may contain values that are not JSON serializable. - - Args: - pydantic_model_obj (BaseModel): a pydantic model - - Returns: - str: a json string - """ - kwargs = _apply_default_pydantic_kwargs(kwargs) - - return pydantic_model_obj.json(**kwargs) - - -def to_dict(pydantic_model_obj: BaseModel, **kwargs) -> dict: - """A helper function to convert a pydantic model to a dict. - - Without this pydantic dictionary may contain values that are not JSON serializable. - - Args: - pydantic_model_obj (BaseModel): a pydantic model - - Returns: - dict: a dict - """ - kwargs = _apply_default_pydantic_kwargs(kwargs) - - return pydantic_model_obj.dict(**kwargs) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry.py deleted file mode 100644 index a421fb8b6cf8..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry.py +++ /dev/null @@ -1,367 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import copy -import json -import logging -import os -from collections import defaultdict -from enum import Enum -from typing import Union - -import semver -import sentry_sdk -from google.cloud import storage -from google.oauth2 import service_account -from pydash.objects import set_with - -from metadata_service.constants import ( - ANALYTICS_BUCKET, - ANALYTICS_FOLDER, - METADATA_FOLDER, - PUBLISH_UPDATE_CHANNEL, - REGISTRIES_FOLDER, - VALID_REGISTRIES, -) -from metadata_service.helpers.gcs import get_gcs_storage_client, safe_read_gcs_file -from metadata_service.helpers.object_helpers import CaseInsensitiveKeys, default_none_to_dict -from metadata_service.helpers.slack import send_slack_message -from metadata_service.models.generated import ConnectorRegistryDestinationDefinition, ConnectorRegistrySourceDefinition, ConnectorRegistryV0 -from metadata_service.models.transform import to_json_sanitized_dict - -logger = logging.getLogger(__name__) - - -PolymorphicRegistryEntry = Union[ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition] - - -class ConnectorTypes(str, Enum, metaclass=CaseInsensitiveKeys): - SOURCE = "source" - DESTINATION = "destination" - - -class ConnectorTypePrimaryKey(str, Enum, metaclass=CaseInsensitiveKeys): - SOURCE = "sourceDefinitionId" - DESTINATION = "destinationDefinitionId" - - -class StringNullJsonDecoder(json.JSONDecoder): - """A JSON decoder that converts "null" strings to None.""" - - def __init__(self, *args, **kwargs): - super().__init__(object_hook=self.object_hook, *args, **kwargs) - - def object_hook(self, obj): - return {k: (None if v == "null" else v) for k, v in obj.items()} - - -def _apply_metrics_to_registry_entry(registry_entry_dict: dict, connector_type: ConnectorTypes, latest_metrics_dict: dict) -> dict: - """Apply the metrics to the registry entry. - - Args: - registry_entry_dict (dict): The registry entry. - latest_metrics_dict (dict): The metrics. - - Returns: - dict: The registry entry with metrics. - """ - connector_id = registry_entry_dict[ConnectorTypePrimaryKey[connector_type.value]] - metrics = latest_metrics_dict.get(connector_id, {}) - - # Safely add metrics to ["generated"]["metrics"], knowing that the key may not exist, or might be None - registry_entry_dict = set_with(registry_entry_dict, "generated.metrics", metrics, default_none_to_dict) - - return registry_entry_dict - - -def _apply_release_candidate_entries(registry_entry_dict: dict, docker_repository_to_rc_registry_entry: dict) -> dict: - """Apply the optionally existing release candidate entries to the registry entry. - We need both the release candidate metadata entry and the release candidate registry entry because the metadata entry contains the rollout configuration, and the registry entry contains the actual RC registry entry. - - Args: - registry_entry_dict (dict): The registry entry. - docker_repository_to_rc_registry_entry (dict): Mapping of docker repository to release candidate registry entry. - - Returns: - dict: The registry entry with release candidates applied. - """ - registry_entry_dict = copy.deepcopy(registry_entry_dict) - if registry_entry_dict["dockerRepository"] in docker_repository_to_rc_registry_entry: - release_candidate_registry_entry = docker_repository_to_rc_registry_entry[registry_entry_dict["dockerRepository"]] - registry_entry_dict = _apply_release_candidates(registry_entry_dict, release_candidate_registry_entry) - return registry_entry_dict - - -def _apply_release_candidates( - latest_registry_entry: dict, - release_candidate_registry_entry: PolymorphicRegistryEntry, -) -> dict: - """Apply the release candidate entries to the registry entry. - - Args: - latest_registry_entry (dict): The latest registry entry. - release_candidate_registry_entry (PolymorphicRegistryEntry): The release candidate registry entry. - - Returns: - dict: The registry entry with release candidates applied. - """ - try: - if not release_candidate_registry_entry.releases.rolloutConfiguration.enableProgressiveRollout: - return latest_registry_entry - # Handle if releases or rolloutConfiguration is not present in the release candidate registry entry - except AttributeError: - return latest_registry_entry - - # If the relase candidate is older than the latest registry entry, don't apply the release candidate and return the latest registry entry - if semver.Version.parse(release_candidate_registry_entry.dockerImageTag) < semver.Version.parse( - latest_registry_entry["dockerImageTag"] - ): - return latest_registry_entry - - updated_registry_entry = copy.deepcopy(latest_registry_entry) - updated_registry_entry.setdefault("releases", {}) - updated_registry_entry["releases"]["releaseCandidates"] = { - release_candidate_registry_entry.dockerImageTag: to_json_sanitized_dict(release_candidate_registry_entry) - } - return updated_registry_entry - - -def _build_connector_registry( - latest_registry_entries: list[PolymorphicRegistryEntry], latest_connector_metrics: dict, docker_repository_to_rc_registry_entry: dict -) -> ConnectorRegistryV0: - registry_dict = {"sources": [], "destinations": []} - - for latest_registry_entry in latest_registry_entries: - connector_type = _get_connector_type_from_registry_entry(latest_registry_entry) - plural_connector_type = f"{connector_type.value}s" - - registry_entry_dict = to_json_sanitized_dict(latest_registry_entry) - - enriched_registry_entry_dict = _apply_metrics_to_registry_entry(registry_entry_dict, connector_type, latest_connector_metrics) - - enriched_registry_entry_dict = _apply_release_candidate_entries( - enriched_registry_entry_dict, docker_repository_to_rc_registry_entry - ) - - registry_dict[plural_connector_type].append(enriched_registry_entry_dict) - - return ConnectorRegistryV0.parse_obj(registry_dict) - - -def _convert_json_to_metrics_dict(jsonl_string: str) -> dict: - """Convert the jsonl string to a metrics dict. - - Args: - jsonl_string (str): The jsonl string. - - Returns: - dict: The metrics dict. - """ - metrics_dict = defaultdict(dict) - jsonl_lines = jsonl_string.splitlines() - for line in jsonl_lines: - data = json.loads(line, cls=StringNullJsonDecoder) - connector_data = data["_airbyte_data"] - connector_definition_id = connector_data["connector_definition_id"] - airbyte_platform = connector_data["airbyte_platform"] - metrics_dict[connector_definition_id][airbyte_platform] = connector_data - - return metrics_dict - - -def _get_connector_type_from_registry_entry(registry_entry: PolymorphicRegistryEntry) -> ConnectorTypes: - """Get the connector type from the registry entry. - - Args: - registry_entry (PolymorphicRegistryEntry): The registry entry. - - Returns: - ConnectorTypes: The connector type. - """ - if hasattr(registry_entry, ConnectorTypePrimaryKey.SOURCE): - return ConnectorTypes.SOURCE - elif hasattr(registry_entry, ConnectorTypePrimaryKey.DESTINATION): - return ConnectorTypes.DESTINATION - else: - raise ValueError("Registry entry is not a source or destination") - - -@sentry_sdk.trace -def _get_latest_registry_entries(bucket: storage.Bucket, registry_type: str) -> list[PolymorphicRegistryEntry]: - """Get the latest registry entries from the GCS bucket. - - Args: - bucket (storage.Bucket): The GCS bucket. - registry_type (str): The registry type. - - Returns: - list[PolymorphicRegistryEntry]: The latest registry entries. - """ - registry_type_file_name = f"{registry_type}.json" - - try: - logger.info(f"Listing blobs in the latest folder: {METADATA_FOLDER}/**/latest/{registry_type_file_name}") - blobs = bucket.list_blobs(match_glob=f"{METADATA_FOLDER}/**/latest/{registry_type_file_name}") - except Exception as e: - logger.error(f"Error listing blobs in the latest folder: {str(e)}") - return [] - - latest_registry_entries = [] - for blob in blobs: - logger.info(f"Reading blob: {blob.name}") - registry_dict = json.loads(safe_read_gcs_file(blob)) - try: - if registry_dict.get(ConnectorTypePrimaryKey.SOURCE.value): - registry_model = ConnectorRegistrySourceDefinition.parse_obj(registry_dict) - elif registry_dict.get(ConnectorTypePrimaryKey.DESTINATION.value): - registry_model = ConnectorRegistryDestinationDefinition.parse_obj(registry_dict) - else: - logger.warning(f"Failed to parse registry model for {blob.name}. Skipping.") - continue - except Exception as e: - logger.error(f"Error parsing registry model for {blob.name}: {str(e)}") - continue - latest_registry_entries.append(registry_model) - return latest_registry_entries - - -@sentry_sdk.trace -def _get_release_candidate_registry_entries(bucket: storage.Bucket, registry_type: str) -> list[PolymorphicRegistryEntry]: - """Get the release candidate registry entries from the GCS bucket. - - Args: - bucket (storage.Bucket): The GCS bucket. - registry_type (str): The registry type. - - Returns: - list[PolymorphicRegistryEntry]: The release candidate registry entries. - """ - blobs = bucket.list_blobs(match_glob=f"{METADATA_FOLDER}/**/release_candidate/{registry_type}.json") - release_candidate_registry_entries = [] - for blob in blobs: - logger.info(f"Reading blob: {blob.name}") - registry_dict = json.loads(safe_read_gcs_file(blob)) - try: - if "/source-" in blob.name: - registry_model = ConnectorRegistrySourceDefinition.parse_obj(registry_dict) - else: - registry_model = ConnectorRegistryDestinationDefinition.parse_obj(registry_dict) - except Exception as e: - logger.error(f"Error parsing registry model for {blob.name}: {str(e)}") - continue - - release_candidate_registry_entries.append(registry_model) - return release_candidate_registry_entries - - -@sentry_sdk.trace -def _get_latest_connector_metrics(bucket: storage.Bucket) -> dict: - """Get the latest connector metrics from the GCS bucket. - - Args: - bucket (storage.Bucket): The GCS bucket. - - Returns: - dict: The latest connector metrics. - """ - try: - logger.info(f"Getting blobs in the analytics folder: {ANALYTICS_FOLDER}") - blobs = bucket.list_blobs(prefix=f"{ANALYTICS_FOLDER}/") - except Exception as e: - logger.error(f"Unexpected error listing blobs at {ANALYTICS_FOLDER}: {str(e)}") - return {} - - if not blobs: - raise ValueError("No blobs found in the analytics folder") - - # Sort blobs by updated time (most recent first) - most_recent_blob = max(blobs, key=lambda blob: blob.updated) - - latest_metrics_jsonl = safe_read_gcs_file(most_recent_blob) - - if latest_metrics_jsonl is None: - logger.warning(f"No metrics found for {most_recent_blob.name}") - return {} - - try: - latest_metrics_dict = _convert_json_to_metrics_dict(latest_metrics_jsonl) - except Exception as e: - logger.error(f"Error converting json to metrics dict: {str(e)}") - return {} - - return latest_metrics_dict - - -@sentry_sdk.trace -def _persist_registry(registry: ConnectorRegistryV0, registry_name: str, bucket: storage.Bucket) -> None: - """Persist the registry to a json file on GCS bucket - - Args: - registry (ConnectorRegistryV0): The registry. - registry_name (str): The name of the registry. One of "cloud" or "oss". - bucket (storage.Bucket): The GCS bucket. - - Returns: - None - """ - - registry_file_name = f"{registry_name}_registry.json" - registry_file_path = f"{REGISTRIES_FOLDER}/{registry_file_name}" - registry_json = registry.json(exclude_none=True) - registry_json = json.dumps(json.loads(registry_json), sort_keys=True) - - try: - logger.info(f"Uploading {registry_name} registry to {registry_file_path}") - blob = bucket.blob(registry_file_path) - # In cloud, airbyte-cron polls the registry frequently, to enable faster connector updates. - # We should set a lower cache duration on the blob so that the cron receives an up-to-date view of the registry. - # However, OSS polls the registry much less frequently, so the default cache setting (1hr max-age) is fine. - if registry_name == "cloud": - blob.cache_control = "public, max-age=120" - blob.upload_from_string(registry_json.encode("utf-8"), content_type="application/json") - logger.info(f"Successfully uploaded {registry_name} registry to {registry_file_path}") - return - except Exception as e: - logger.error(f"Error persisting {registry_file_name} to json: {str(e)}") - raise e - - -def generate_and_persist_connector_registry(bucket_name: str, registry_type: str) -> None: - """Generate and persist the registry to a json file on GCS bucket. - - Args: - bucket_name (str): The name of the GCS bucket. - registry_type (str): The type of the registry. - - Returns: - tuple[bool, Optional[str]]: A tuple containing a boolean indicating success and an optional error message. - """ - if registry_type not in VALID_REGISTRIES: - raise ValueError(f"Invalid registry type: {registry_type}. Valid types are: {', '.join(VALID_REGISTRIES)}.") - - gcs_client = get_gcs_storage_client() - registry_bucket = gcs_client.bucket(bucket_name) - analytics_bucket = gcs_client.bucket(ANALYTICS_BUCKET) - - latest_registry_entries = _get_latest_registry_entries(registry_bucket, registry_type) - - release_candidate_registry_entries = _get_release_candidate_registry_entries(registry_bucket, registry_type) - - docker_repository_to_rc_registry_entry = { - release_candidate_registry_entries.dockerRepository: release_candidate_registry_entries - for release_candidate_registry_entries in release_candidate_registry_entries - } - - latest_connector_metrics = _get_latest_connector_metrics(analytics_bucket) - - connector_registry = _build_connector_registry( - latest_registry_entries, latest_connector_metrics, docker_repository_to_rc_registry_entry - ) - - try: - _persist_registry(connector_registry, registry_type, registry_bucket) - except Exception as e: - message = f"*🤖 🔴 _Registry Generation_ FAILED*:\nFailed to generate and persist {registry_type} registry." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - raise e diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_entry.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_entry.py deleted file mode 100644 index fb1f01a71d78..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_entry.py +++ /dev/null @@ -1,572 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - - -import copy -import datetime -import json -import logging -import os -import pathlib -from dataclasses import dataclass -from typing import List, Optional, Tuple, Union - -import pandas as pd -import sentry_sdk -import yaml -from google.cloud import storage -from pydash import set_with - -from metadata_service.constants import ( - CONNECTOR_DEPENDENCY_FILE_NAME, - CONNECTOR_DEPENDENCY_FOLDER, - CONNECTORS_PATH, - DEFAULT_ASSET_URL, - ICON_FILE_NAME, - METADATA_CDN_BASE_URL, - METADATA_FILE_NAME, - METADATA_FOLDER, - PUBLISH_UPDATE_CHANNEL, - get_public_url_for_gcs_file, -) -from metadata_service.helpers.gcs import get_gcs_storage_client, safe_read_gcs_file -from metadata_service.helpers.object_helpers import deep_copy_params, default_none_to_dict -from metadata_service.helpers.slack import send_slack_message -from metadata_service.models.generated import ( - ConnectorRegistryDestinationDefinition, - ConnectorRegistrySourceDefinition, - GeneratedFields, - SourceFileInfo, -) -from metadata_service.registry import ConnectorTypePrimaryKey, ConnectorTypes -from metadata_service.spec_cache import SpecCache - -logger = logging.getLogger(__name__) - -PYTHON_CDK_SLUG = "python" - -PolymorphicRegistryEntry = Union[ConnectorRegistrySourceDefinition, ConnectorRegistryDestinationDefinition] -TaggedRegistryEntry = Tuple[ConnectorTypes, PolymorphicRegistryEntry] - - -@dataclass(frozen=True) -class RegistryEntryInfo: - entry_blob_path: str - metadata_file_path: str - - -def _apply_metadata_overrides( - metadata_data: dict, registry_type: str, bucket_name: str, metadata_blob: storage.Blob, skip_docker_image_tag: bool = True -) -> dict: - connector_type = metadata_data["connectorType"] - - overridden_metadata_data = _apply_overrides_from_registry(metadata_data, registry_type, skip_docker_image_tag=skip_docker_image_tag) - - # remove fields that are not needed in the registry - del overridden_metadata_data["registryOverrides"] - del overridden_metadata_data["connectorType"] - - # rename field connectorSubtype to sourceType - connector_subtype = overridden_metadata_data.get("connectorSubtype") - if connector_subtype: - overridden_metadata_data["sourceType"] = overridden_metadata_data["connectorSubtype"] - del overridden_metadata_data["connectorSubtype"] - - # rename definitionId field to sourceDefinitionId or destinationDefinitionId - id_field = "sourceDefinitionId" if connector_type == "source" else "destinationDefinitionId" - overridden_metadata_data[id_field] = overridden_metadata_data["definitionId"] - del overridden_metadata_data["definitionId"] - - # add in useless fields that are currently required for porting to the actor definition spec - overridden_metadata_data["tombstone"] = False - overridden_metadata_data["custom"] = False - overridden_metadata_data["public"] = True - - # Add generated fields for source file metadata and git - overridden_metadata_data["generated"] = _apply_generated_fields(overridden_metadata_data, bucket_name, metadata_blob) - - # Add Dependency information - overridden_metadata_data["packageInfo"] = _apply_package_info_fields(overridden_metadata_data, bucket_name) - - # Add language field - overridden_metadata_data = _apply_language_field(overridden_metadata_data) - - # if there is no supportLevel, set it to "community" - if not overridden_metadata_data.get("supportLevel"): - overridden_metadata_data["supportLevel"] = "community" - - # apply ab_internal defaults - overridden_metadata_data = _apply_ab_internal_defaults(overridden_metadata_data) - - # apply icon url and releases - icon_blob = _get_icon_blob_from_gcs(bucket_name, metadata_data) - icon_url = get_public_url_for_gcs_file(icon_blob.bucket.name, icon_blob.name, METADATA_CDN_BASE_URL) - - overridden_metadata_data["iconUrl"] = icon_url - overridden_metadata_data["releases"] = _apply_connector_releases(overridden_metadata_data) - - return overridden_metadata_data - - -@deep_copy_params -def _apply_overrides_from_registry(metadata_data: dict, override_registry_key: str, skip_docker_image_tag: bool = True) -> dict: - """Apply the overrides from the registry to the metadata data. - - Args: - metadata_data: The metadata data field. - override_registry_key: The key of the registry to override the metadata with. - skip_docker_image_tag: If True (default), skip applying dockerImageTag override. - This should be True for the version being published, and False for latest - entries (which should reflect the overridden/pinned version, if applicable). - - Returns: - The metadata data field with the overrides applied. - """ - override_registry = metadata_data["registryOverrides"][override_registry_key] - del override_registry["enabled"] - - # remove any None values from the override registry - override_registry = {k: v for k, v in override_registry.items() if v is not None} - - if skip_docker_image_tag and "dockerImageTag" in override_registry: - del override_registry["dockerImageTag"] - - metadata_data.update(override_registry) - - return metadata_data - - -def _apply_generated_fields(metadata_data: dict, bucket_name: str, metadata_blob: storage.Blob) -> dict: - """Apply generated fields to the metadata data field. - - Args: - metadata_data (dict): The metadata data field. - - Returns: - dict: The metadata data field with the generated fields applied. - """ - # work on our own copy of everything - metadata_data = copy.deepcopy(metadata_data) - - generated_fields = metadata_data.get("generated") or {} - - # Add the source file metadata - # on a GCS blob, the "name" is actually the full path - generated_fields = set_with(generated_fields, "source_file_info.metadata_file_path", metadata_blob.name, default_none_to_dict) - generated_fields = set_with(generated_fields, "source_file_info.metadata_bucket_name", bucket_name, default_none_to_dict) - generated_fields = set_with( - generated_fields, "source_file_info.registry_entry_generated_at", datetime.datetime.now().isoformat(), default_none_to_dict - ) - generated_fields = set_with( - generated_fields, "source_file_info.metadata_last_modified", metadata_blob.updated.isoformat(), default_none_to_dict - ) - - return generated_fields - - -@sentry_sdk.trace -@deep_copy_params -def _apply_package_info_fields(metadata_data: dict, bucket_name: str) -> dict: - """Apply package info fields to the metadata data field. - - Args: - metadata_data (dict): The metadata data field. - bucket_name (str): The name of the GCS bucket. - - Returns: - dict: The metadata data field with the package info fields applied. - """ - - sanitized_connector_technical_name = metadata_data["dockerRepository"].replace("airbyte/", "") - connector_version = metadata_data["dockerImageTag"] - - dependencies_path = ( - f"{CONNECTOR_DEPENDENCY_FOLDER}/{sanitized_connector_technical_name}/{connector_version}/{CONNECTOR_DEPENDENCY_FILE_NAME}" - ) - - package_info_fields = metadata_data.get("packageInfo") or {} - - try: - logger.info( - f"Getting dependencies blob for `{sanitized_connector_technical_name}` `{connector_version}` at path `{dependencies_path}`" - ) - gcs_client = get_gcs_storage_client(gcs_creds=os.environ.get("GCS_CREDENTIALS")) - bucket = gcs_client.bucket(bucket_name) - dependencies_blob = bucket.blob(dependencies_path) - dependencies_blob_contents = safe_read_gcs_file(dependencies_blob) - if dependencies_blob_contents is not None: - dependencies_json = json.loads(dependencies_blob_contents) - cdk_version = None - for package in dependencies_json.get("dependencies", []): - if package.get("package_name") == "airbyte-cdk": - # Note: Prefix the version with the python slug as the python cdk is the only one we have - # versions available for. - cdk_version = f"{PYTHON_CDK_SLUG}:{package.get('version')}" - break - package_info_fields = set_with(package_info_fields, "cdk_version", cdk_version, default_none_to_dict) - except Exception as e: - logger.warning(f"Error reading dependencies file for `{sanitized_connector_technical_name}`: {e}") - raise - - logger.info("Added package info fields.") - - return package_info_fields - - -@deep_copy_params -def _apply_language_field(metadata_data: dict) -> dict: - """Transform the language tag into a top-level field, if it is not already present. - - Args: - metadata_data (dict): The metadata data field. - - Returns: - dict: The metadata data field with the language field applied. - """ - if metadata_data.get("language"): - return metadata_data - - tags = metadata_data.get("tags", []) - languages = [tag.replace("language:", "") for tag in tags if tag.startswith("language:")] - metadata_data["language"] = languages[0] if languages else None - - return metadata_data - - -@deep_copy_params -def _apply_ab_internal_defaults(metadata_data: dict) -> dict: - """Apply ab_internal defaults to the metadata data field. - - Args: - metadata_data (dict): The metadata data field. - - Returns: - dict: The metadata data field with the ab_internal defaults applied. - """ - default_ab_internal_values = { - "sl": 100, - "ql": 100, - } - - existing_ab_internal_values = metadata_data.get("ab_internal") or {} - ab_internal_values = {**default_ab_internal_values, **existing_ab_internal_values} - - metadata_data["ab_internal"] = ab_internal_values - - return metadata_data - - -@deep_copy_params -def _apply_connector_releases(metadata: dict) -> Optional[pd.DataFrame]: - documentation_url = metadata["documentationUrl"] - final_registry_releases = {} - releases = metadata.get("releases") - if releases is not None and releases.get("breakingChanges"): - # apply defaults for connector releases - final_registry_releases["migrationDocumentationUrl"] = _calculate_migration_documentation_url( - metadata["releases"], documentation_url - ) - - # releases has a dictionary field called breakingChanges, where the key is the version and the value is the data for the breaking change - # each breaking change has a migrationDocumentationUrl field that is optional, so we need to apply defaults to it - breaking_changes = metadata["releases"]["breakingChanges"] - if breaking_changes is not None: - for version, breaking_change in breaking_changes.items(): - breaking_change["migrationDocumentationUrl"] = _calculate_migration_documentation_url( - breaking_change, documentation_url, version - ) - final_registry_releases["breakingChanges"] = breaking_changes - - if releases is not None and releases.get("rolloutConfiguration"): - final_registry_releases["rolloutConfiguration"] = metadata["releases"]["rolloutConfiguration"] - return final_registry_releases - - -def _calculate_migration_documentation_url(releases_or_breaking_change: dict, documentation_url: str, version: Optional[str] = None) -> str: - """Calculate the migration documentation url for the connector releases. - - Args: - metadata_releases (dict): The connector releases. - - Returns: - str: The migration documentation url. - """ - - base_url = f"{documentation_url}-migrations" - default_migration_documentation_url = f"{base_url}#{version}" if version is not None else base_url - - return releases_or_breaking_change.get("migrationDocumentationUrl", None) or default_migration_documentation_url - - -def _get_and_parse_yaml_file(file_path: pathlib.Path) -> dict: - """Get and parse the metadata file. - - Args: - metadata_file_path (pathlib.Path): The path to the metadata file. - - Returns: - dict: The file dictionary. - """ - - try: - logger.debug(f"Getting and parsing YAML file: `{file_path}`") - with open(file_path, "r") as f: - file_dict = yaml.safe_load(f) - except Exception as e: - logger.exception(f"Error parsing file") - raise - logger.info("Parsed YAML file.") - return file_dict - - -@sentry_sdk.trace -def _get_icon_blob_from_gcs(bucket_name: str, metadata_entry: dict) -> storage.Blob: - """Get the icon blob from the GCS bucket. - - Args: - bucket (storage.Bucket): The GCS bucket. - metadata_entry (dict): The metadata entry. - - Returns: - storage.Blob: The icon blob. - """ - connector_docker_repository = metadata_entry["dockerRepository"] - icon_file_path = f"{METADATA_FOLDER}/{connector_docker_repository}/latest/{ICON_FILE_NAME}" - try: - logger.info(f"Getting icon blob for {connector_docker_repository}") - gcs_client = get_gcs_storage_client(gcs_creds=os.environ.get("GCS_CREDENTIALS")) - bucket = gcs_client.bucket(bucket_name) - icon_blob = bucket.blob(icon_file_path) - if not icon_blob.exists(): - raise ValueError(f"Icon file not found for `{connector_docker_repository}`") - except Exception as e: - logger.exception(f"Error getting icon blob") - raise - return icon_blob - - -def _get_connector_type_from_registry_entry(registry_entry: dict) -> TaggedRegistryEntry: - """Get the connector type from the registry entry. - - Args: - registry_entry (dict): The registry entry. - - Returns: - TaggedRegistryEntry: The connector type and model. - """ - if registry_entry.get(ConnectorTypePrimaryKey.SOURCE.value): - return (ConnectorTypes.SOURCE, ConnectorRegistrySourceDefinition) - elif registry_entry.get(ConnectorTypePrimaryKey.DESTINATION.value): - return (ConnectorTypes.DESTINATION, ConnectorRegistryDestinationDefinition) - else: - raise Exception("Could not determine connector type from registry entry") - - -def _get_registry_blob_information( - metadata_dict: dict, registry_type: str, metadata_data_with_overrides: dict, is_prerelease: bool -) -> List[RegistryEntryInfo]: - """ - Builds information for each registry blob: GCS path to write the registry entry into, along with - the correct "metadata blob path" for the entry. - - Args: - metadata_dict (dict): The metadata dictionary. - registry_type (str): The registry type. - - Returns: - List[RegistryEntryInfo]: Tuples of the path to write the registry entry to, and the metadata blob path to populate into the entry. - """ - docker_repository = metadata_dict["data"]["dockerRepository"] - registry_entry_paths: List[RegistryEntryInfo] = [] - - # The versioned registry entries always respect the registry overrides. - # For example, with destination-postgres: we'll push oss.json to `destination-postgres//oss.json`, - # but cloud.json goes to `destination-postgres-strict-encrypt//cloud.json`. - versioned_registry_entry_path = f"{METADATA_FOLDER}/{metadata_data_with_overrides['dockerRepository']}/{metadata_data_with_overrides['dockerImageTag']}/{registry_type}.json" - # However, the metadata blob path uses the non-overridden docker repo, for... reasons? - versioned_metadata_blob_path = f"{METADATA_FOLDER}/{docker_repository}/{metadata_data_with_overrides['dockerImageTag']}/metadata.yaml" - # We always publish the versioned registry entry. - registry_entry_paths.append(RegistryEntryInfo(versioned_registry_entry_path, versioned_metadata_blob_path)) - - # If we're not doing a prerelease, we have an extra file to push. - # Note that for these extra files, we point at a different metadata.yaml than the versioned file - # (e.g. the `latest` registry entry points at the `latest` metadata.yaml, instead of at the versioned metadata.yaml) - if not is_prerelease: - if "-rc" in metadata_dict["data"]["dockerImageTag"]: - # We're doing a release candidate publish. Push the RC registry entry. - # This intentionally uses the non-overridden docker_repository. We _always_ upload both cloud+oss registry entries - # to the non-overridden docker_repository path for the `release_candidate` entry. - release_candidate_registry_entry_path = f"{METADATA_FOLDER}/{docker_repository}/release_candidate/{registry_type}.json" - release_candidate_metadata_blob_path = f"{METADATA_FOLDER}/{docker_repository}/release_candidate/metadata.yaml" - registry_entry_paths.append(RegistryEntryInfo(release_candidate_registry_entry_path, release_candidate_metadata_blob_path)) - - else: - # This is a normal publish. Push the `latest` registry entry. - # This intentionally uses the non-overridden docker_repository. We _always_ upload both cloud+oss registry entries - # to the non-overridden docker_repository path for the `latest` entry. - latest_registry_entry_path = f"{METADATA_FOLDER}/{docker_repository}/latest/{registry_type}.json" - latest_registry_metadata_blob_path = f"{METADATA_FOLDER}/{docker_repository}/latest/metadata.yaml" - registry_entry_paths.append(RegistryEntryInfo(latest_registry_entry_path, latest_registry_metadata_blob_path)) - - return registry_entry_paths - - -@sentry_sdk.trace -def _persist_connector_registry_entry(bucket_name: str, registry_entry: PolymorphicRegistryEntry, registry_entry_path: str) -> None: - """Persist the connector registry entry to the GCS bucket. - - Args: - bucket_name (str): The name of the GCS bucket. - registry_entry (PolymorphicRegistryEntry): The registry entry. - registry_entry_path (str): The path to the registry entry. - """ - try: - logger.info(f"Persisting connector registry entry to {registry_entry_path}") - gcs_client = get_gcs_storage_client() - bucket = gcs_client.bucket(bucket_name) - registry_entry_blob = bucket.blob(registry_entry_path) - registry_entry_blob.upload_from_string(registry_entry.json(exclude_none=True)) - except Exception as e: - logger.exception(f"Error persisting connector registry entry") - raise - - -@sentry_sdk.trace -def generate_and_persist_registry_entry( - bucket_name: str, repo_metadata_file_path: pathlib.Path, registry_type: str, docker_image_tag: str, is_prerelease: bool -) -> None: - """Generate and persist the connector registry entry to the GCS bucket. - - Args: - bucket_name (str): The name of the GCS bucket. - repo_metadata_file_path (pathlib.Path): The path to the spec file. - registry_type (str): The registry type. - docker_image_tag (str): The docker image tag associated with this release. Typically a semver string (e.g. '1.2.3'), possibly with a suffix (e.g. '1.2.3-preview.abcde12') - is_prerelease (bool): Whether this is a prerelease, or a main release. - """ - # Read the repo metadata dict to bootstrap ourselves. We need the docker repository, - # so that we can read the metadata from GCS. - repo_metadata_dict = _get_and_parse_yaml_file(repo_metadata_file_path) - docker_repository = repo_metadata_dict["data"]["dockerRepository"] - - try: - # Now that we have the docker repo, read the appropriate versioned metadata from GCS. - # This metadata will differ in a few fields (e.g. in prerelease mode, dockerImageTag will contain the actual prerelease tag `1.2.3-preview.abcde12`), - # so we'll treat this as the source of truth (ish. See below for how we handle the registryOverrides field.) - gcs_client = get_gcs_storage_client(gcs_creds=os.environ.get("GCS_CREDENTIALS")) - bucket = gcs_client.bucket(bucket_name) - metadata_blob = bucket.blob(f"{METADATA_FOLDER}/{docker_repository}/{docker_image_tag}/{METADATA_FILE_NAME}") - # bucket.blob() returns a partially-loaded blob. - # reload() asks GCS to fetch the rest of the information. - # (this doesn't fetch the _contents_ of the blob, only its metadata - modified time, etc.) - metadata_blob.reload() - metadata_dict = yaml.safe_load(metadata_blob.download_as_string()) - except: - logger.exception("Error loading metadata from GCS") - message = f"*🤖 🔴 _Registry Entry Generation_ FAILED*:\nRegistry Entry: `{registry_type}.json`\nConnector: `{repo_metadata_dict['data']['dockerRepository']}`\nGCS Bucket: `{bucket_name}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - raise - - message = f"*🤖 🟡 _Registry Entry Generation_ STARTED*:\nRegistry Entry: `{registry_type}.json`\nConnector: `{docker_repository}`\nGCS Bucket: `{bucket_name}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - - # If the connector is not enabled on the given registry, skip generateing and persisting the registry entry. - if metadata_dict["data"]["registryOverrides"][registry_type]["enabled"]: - metadata_data = metadata_dict["data"] - try: - overridden_metadata_data = _apply_metadata_overrides( - metadata_data, registry_type, bucket_name, metadata_blob, skip_docker_image_tag=True - ) - except Exception as e: - logger.exception(f"Error applying metadata overrides") - message = f"*🤖 🔴 _Registry Entry Generation_ FAILED*:\nRegistry Entry: `{registry_type}.json`\nConnector: `{metadata_data['dockerRepository']}`\nGCS Bucket: `{bucket_name}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - raise - - registry_entry_blob_paths = _get_registry_blob_information(metadata_dict, registry_type, overridden_metadata_data, is_prerelease) - - logger.info("Parsing spec file.") - spec_cache = SpecCache() - # Use the overridden values here. This enables us to read from the appropriate spec cache for strict-encrypt connectors. - cached_spec = spec_cache.find_spec_cache_with_fallback( - overridden_metadata_data["dockerRepository"], overridden_metadata_data["dockerImageTag"], registry_type - ) - overridden_metadata_data["spec"] = spec_cache.download_spec(cached_spec) - logger.info("Spec file parsed and added to metadata.") - - logger.info("Parsing registry entry model.") - _, RegistryEntryModel = _get_connector_type_from_registry_entry(overridden_metadata_data) - registry_entry_model = RegistryEntryModel.parse_obj(overridden_metadata_data) - logger.info("Registry entry model parsed.") - - # Persist the registry entry to the GCS bucket. - for registry_entry_info in registry_entry_blob_paths: - registry_entry_blob_path = registry_entry_info.entry_blob_path - metadata_blob_path = registry_entry_info.metadata_file_path - - # For latest entries, apply the dockerImageTag override (version pinning behavior) - if "/latest/" in registry_entry_blob_path: - latest_metadata = _apply_metadata_overrides( - metadata_data, registry_type, bucket_name, metadata_blob, skip_docker_image_tag=False - ) - latest_spec = spec_cache.find_spec_cache_with_fallback( - latest_metadata["dockerRepository"], latest_metadata["dockerImageTag"], registry_type - ) - latest_metadata["spec"] = spec_cache.download_spec(latest_spec) - current_registry_entry_model = RegistryEntryModel.parse_obj(latest_metadata) - else: - current_registry_entry_model = registry_entry_model - - try: - logger.info( - f"Persisting `{metadata_data['dockerRepository']}` {registry_type} registry entry to `{registry_entry_blob_path}`" - ) - - # set the correct metadata blob path on the registry entry - current_registry_entry_model = copy.deepcopy(current_registry_entry_model) - generated_fields: Optional[GeneratedFields] = current_registry_entry_model.generated - if generated_fields is not None: - source_file_info: Optional[SourceFileInfo] = generated_fields.source_file_info - if source_file_info is not None: - source_file_info.metadata_file_path = metadata_blob_path - _persist_connector_registry_entry(bucket_name, current_registry_entry_model, registry_entry_blob_path) - - message = f"*🤖 🟢 _Registry Entry Generation_ SUCCESS*:\nRegistry Entry: `{registry_type}.json`\nConnector: `{metadata_data['dockerRepository']}`\nGCS Bucket: `{bucket_name}`\nPath: `{registry_entry_blob_path}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - - logger.info("Success.") - - except Exception as e: - logger.exception(f"Error persisting connector registry entry to") - - message = f"*🤖 🔴 _Registry Entry Generation_ FAILED*:\nRegistry Entry: `{registry_type}.json`\nConnector: `{metadata_data['dockerRepository']}`\nGCS Bucket: `{bucket_name}`\nPath: `{registry_entry_blob_path}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - - try: - bucket.delete_blob(registry_entry_blob_path) - except Exception as cleanup_error: - logger.warning(f"Failed to clean up {registry_entry_blob_path}: {cleanup_error}") - raise - else: - logger.info( - f"Registry type {registry_type} is not enabled for `{metadata_dict['data']['dockerRepository']}`, skipping generation and upload." - ) - message = f"*🤖 ⚫ _Registry Entry Generation_ NOOP*:\n_Note: Connector is not enabled on {registry_type} registry. No action required._\nRegistry Entry: `{registry_type}.json`\nConnector: `{metadata_dict['data']['dockerRepository']}`\nGCS Bucket: `{bucket_name}`." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - - # For latest versions that are disabled, delete any existing registry entry to remove it from the registry - if ( - "-rc" not in metadata_dict["data"]["dockerImageTag"] - and "-dev" not in metadata_dict["data"]["dockerImageTag"] - and "-preview" not in metadata_dict["data"]["dockerImageTag"] - ) and not metadata_dict["data"]["registryOverrides"][registry_type]["enabled"]: - logger.info( - f"{registry_type} is not enabled: deleting existing {registry_type} registry entry for {metadata_dict['data']['dockerRepository']} at latest path." - ) - - latest_registry_entry_path = f"{METADATA_FOLDER}/{metadata_dict['data']['dockerRepository']}/latest/{registry_type}.json" - - existing_registry_entry = bucket.blob(latest_registry_entry_path) - if existing_registry_entry.exists(): - bucket.delete_blob(latest_registry_entry_path) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_report.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_report.py deleted file mode 100644 index 9e4061329c28..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/registry_report.py +++ /dev/null @@ -1,353 +0,0 @@ -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -import json -import logging -from dataclasses import dataclass -from os import scandir -from typing import Any, Callable, List - -import pandas as pd -from google.cloud import storage - -from metadata_service.constants import ( - CONNECTORS_PATH, - GITHUB_REPO_NAME, - METADATA_CDN_BASE_URL, - REGISTRIES_FOLDER, - get_public_url_for_gcs_file, -) -from metadata_service.helpers.gcs import get_gcs_storage_client -from metadata_service.models.generated import ConnectorRegistryV0 -from metadata_service.models.transform import to_json_sanitized_dict -from metadata_service.templates.render import ( - ColumnInfo, - dataframe_to_table_html, - icon_image_html, - internal_level_html, - render_connector_registry_locations_html, - simple_link_html, - test_badge_html, -) - -logger = logging.getLogger(__name__) - -REPORT_FILE_NAME = "connector_registry_report" -CONNECTOR_TEST_SUMMARY_FOLDER = "test_summary" -REPORT_FOLDER = "generated_reports" -OSS_SUFFIX = "_oss" -CLOUD_SUFFIX = "_cloud" - -# 🖼️ Dataframe Columns - - -def _github_url(docker_repo_name: str, github_connector_folders: List[str]) -> str | None: - if not isinstance(docker_repo_name, str): - return None - - connector_name = docker_repo_name.replace("airbyte/", "") - if connector_name in github_connector_folders: - return f"https://github.com/{GITHUB_REPO_NAME}/blob/master/airbyte-integrations/connectors/{connector_name}" - else: - return None - - -def _issue_url(row: pd.DataFrame) -> str | None: - docker_repo = row["dockerRepository_oss"] - if not isinstance(docker_repo, str): - print(f"no docker repo: {row}") - return None - - code_name = docker_repo.split("/")[1] - issues_label = ( - f"connectors/{'source' if 'source-' in code_name else 'destination'}/" - f"{code_name.replace('source-', '').replace('destination-', '')}" - ) - return f"https://github.com/{GITHUB_REPO_NAME}/issues?q=is:open+is:issue+label:{issues_label}" - - -def _merge_docker_repo_and_version(row: pd.DataFrame, suffix: str) -> str | None: - docker_repo = row[f"dockerRepository{suffix}"] - docker_version = row[f"dockerImageTag{suffix}"] - - if not isinstance(docker_repo, str): - return None - - return f"{docker_repo}:{docker_version}" - - -def _test_summary_url(row: pd.DataFrame) -> str | None: - docker_repo_name = row["dockerRepository_oss"] - if not isinstance(docker_repo_name, str): - return None - - connector = docker_repo_name.replace("airbyte/", "") - - path = f"{REPORT_FOLDER}/{CONNECTOR_TEST_SUMMARY_FOLDER}/{connector}" - - # get_public_url_for_gcs_file ignores the bucket name if a CDN URL is provided - return get_public_url_for_gcs_file("unused", path, METADATA_CDN_BASE_URL) - - -def _ab_internal_sl(row: pd.DataFrame) -> int | None: - ab_internal = row.get("ab_internal_oss") - if not isinstance(ab_internal, dict) or "sl" not in ab_internal: - return None - sl = ab_internal["sl"] - if not isinstance(sl, int): - raise Exception(f"expected sl to be string; got {type(sl)} ({sl})") - return sl - - -def _ab_internal_ql(row: pd.DataFrame) -> int | None: - ab_internal = row.get("ab_internal_oss") - if not isinstance(ab_internal, dict) or "ql" not in ab_internal: - return None - ql = ab_internal["ql"] - if not isinstance(ql, int): - raise Exception(f"expected ql to be string; got {type(ql)} ({ql})") - return ql - - -# 📊 Dataframe Augmentation - - -def _augment_and_normalize_connector_dataframes( - cloud_df: pd.DataFrame, oss_df: pd.DataFrame, primary_key: str, connector_type: str, github_connector_folders: List[str] -) -> pd.DataFrame: - """ - Normalize the cloud and oss connector dataframes and merge them into a single dataframe. - Augment the dataframe with additional columns that indicate if the connector is in the cloud registry, oss registry, and if the metadata is valid. - """ - - # Add a column 'is_cloud' to indicate if an image/version pair is in the cloud registry - cloud_df["is_cloud"] = True - - # Add a column 'is_oss' to indicate if an image/version pair is in the oss registry - oss_df["is_oss"] = True - - # Merge the two registries on the 'image' and 'version' columns - total_registry = pd.merge(oss_df, cloud_df, how="outer", suffixes=(OSS_SUFFIX, CLOUD_SUFFIX), on=primary_key) - - # remove duplicates from the merged dataframe - total_registry = total_registry.drop_duplicates(subset=primary_key, keep="first") - - # Replace NaN values in the 'is_cloud' and 'is_oss' columns with False - total_registry[["is_cloud", "is_oss"]] = total_registry[["is_cloud", "is_oss"]].fillna(False) - - # Set connectorType to 'source' or 'destination' - total_registry["connector_type"] = connector_type - - total_registry["github_url"] = total_registry["dockerRepository_oss"].apply(lambda x: _github_url(x, github_connector_folders)) - - total_registry["issue_url"] = total_registry.apply(_issue_url, axis=1) - total_registry["test_summary_url"] = total_registry.apply(_test_summary_url, axis=1) - - # Show Internal Fields - total_registry["ab_internal_ql"] = total_registry.apply(_ab_internal_ql, axis=1) - total_registry["ab_internal_sl"] = total_registry.apply(_ab_internal_sl, axis=1) - - # Merge docker repo and version into separate columns - total_registry["docker_image_oss"] = total_registry.apply(lambda x: _merge_docker_repo_and_version(x, OSS_SUFFIX), axis=1) - total_registry["docker_image_cloud"] = total_registry.apply(lambda x: _merge_docker_repo_and_version(x, CLOUD_SUFFIX), axis=1) - total_registry["docker_images_match"] = total_registry["docker_image_oss"] == total_registry["docker_image_cloud"] - - # Rename column primary_key to 'definitionId' - total_registry.rename(columns={primary_key: "definitionId"}, inplace=True) - - return total_registry - - -# ASSETS - - -def _cloud_sources_dataframe(latest_cloud_registry: ConnectorRegistryV0) -> pd.DataFrame: - latest_cloud_registry_dict = to_json_sanitized_dict(latest_cloud_registry) - sources = latest_cloud_registry_dict["sources"] - return pd.DataFrame(sources) - - -def _oss_sources_dataframe(latest_oss_registry: ConnectorRegistryV0) -> pd.DataFrame: - latest_oss_registry_dict = to_json_sanitized_dict(latest_oss_registry) - sources = latest_oss_registry_dict["sources"] - return pd.DataFrame(sources) - - -def _cloud_destinations_dataframe(latest_cloud_registry: ConnectorRegistryV0) -> pd.DataFrame: - latest_cloud_registry_dict = to_json_sanitized_dict(latest_cloud_registry) - destinations = latest_cloud_registry_dict["destinations"] - return pd.DataFrame(destinations) - - -def _oss_destinations_dataframe(latest_oss_registry: ConnectorRegistryV0) -> pd.DataFrame: - latest_oss_registry_dict = to_json_sanitized_dict(latest_oss_registry) - destinations = latest_oss_registry_dict["destinations"] - return pd.DataFrame(destinations) - - -def _all_sources_dataframe(cloud_sources_dataframe, oss_sources_dataframe, github_connector_folders) -> pd.DataFrame: - """ - Merge the cloud and oss sources registries into a single dataframe. - """ - - return _augment_and_normalize_connector_dataframes( - cloud_df=cloud_sources_dataframe, - oss_df=oss_sources_dataframe, - primary_key="sourceDefinitionId", - connector_type="source", - github_connector_folders=github_connector_folders, - ) - - -def _all_destinations_dataframe(cloud_destinations_dataframe, oss_destinations_dataframe, github_connector_folders) -> pd.DataFrame: - """ - Merge the cloud and oss destinations registries into a single dataframe. - """ - - return _augment_and_normalize_connector_dataframes( - cloud_df=cloud_destinations_dataframe, - oss_df=oss_destinations_dataframe, - primary_key="destinationDefinitionId", - connector_type="destination", - github_connector_folders=github_connector_folders, - ) - - -def _github_connector_folders() -> List[str]: - """ - Return a list of all the folders in the github connectors directory. - """ - folder_names = [item.name for item in scandir(CONNECTORS_PATH) if item.is_dir()] - return folder_names - - -def _load_registry(bucket: storage.Bucket, filename: str) -> ConnectorRegistryV0: - latest_oss_registry_gcs_blob = bucket.blob(f"{REGISTRIES_FOLDER}/{filename}") - json_string = latest_oss_registry_gcs_blob.download_as_string().decode("utf-8") - latest_cloud_registry_dict = json.loads(json_string) - return ConnectorRegistryV0.parse_obj(latest_cloud_registry_dict) - - -def generate_and_persist_registry_report(bucket_name: str) -> None: - """Generate and persist the registry report to GCS. - - Args: - bucket_name (str): The name of the bucket to persist the registry report to. - - Returns: - None - """ - client = get_gcs_storage_client() - bucket = client.bucket(bucket_name) - - github_connector_folders = _github_connector_folders() - - latest_cloud_registry = _load_registry(bucket, "cloud_registry.json") - latest_oss_registry = _load_registry(bucket, "oss_registry.json") - - all_destinations_dataframe = _all_destinations_dataframe( - _cloud_destinations_dataframe(latest_cloud_registry), - _oss_destinations_dataframe(latest_oss_registry), - github_connector_folders, - ) - all_sources_dataframe = _all_sources_dataframe( - _cloud_sources_dataframe(latest_cloud_registry), - _oss_sources_dataframe(latest_oss_registry), - github_connector_folders, - ) - - all_connectors_dataframe = pd.concat([all_destinations_dataframe, all_sources_dataframe]) - all_connectors_dataframe.reset_index(inplace=True) - - columns_to_show: List[ColumnInfo] = [ - { - "column": "name_oss", - "title": "Connector Name", - }, - { - "column": "definitionId", - "title": "Definition Id", - }, - { - "column": "iconUrl_oss", - "title": "Icon", - "formatter": icon_image_html, - }, - { - "column": "connector_type", - "title": "Connector Type", - }, - { - "column": "releaseStage_oss", - "title": "Release Stage", - }, - { - "column": "supportLevel_oss", - "title": "Support Level", - }, - { - "column": "ab_internal_sl", - "title": "Internal SL", - "formatter": internal_level_html, - }, - { - "column": "ab_internal_ql", - "title": "Internal QL", - "formatter": internal_level_html, - }, - { - "column": "test_summary_url", - "title": "Build Status", - "formatter": test_badge_html, - }, - { - "column": "is_oss", - "title": "OSS", - }, - { - "column": "is_cloud", - "title": "Cloud", - }, - { - "column": "docker_image_oss", - "title": "Docker Image OSS", - }, - { - "column": "docker_image_cloud", - "title": "Docker Image Cloud", - }, - { - "column": "docker_images_match", - "title": "OSS and Cloud Docker Images Match", - }, - { - "column": "github_url", - "title": "Source", - "formatter": simple_link_html, - }, - { - "column": "documentationUrl_oss", - "title": "Docs", - "formatter": simple_link_html, - }, - { - "column": "issue_url", - "title": "Issues", - "formatter": simple_link_html, - }, - ] - - html_string = render_connector_registry_locations_html( - destinations_table_html=dataframe_to_table_html(all_destinations_dataframe, columns_to_show), - sources_table_html=dataframe_to_table_html(all_sources_dataframe, columns_to_show), - ) - - json_string = all_connectors_dataframe.to_json(orient="records") - - json_file_blob = bucket.blob(f"{REPORT_FOLDER}/{REPORT_FILE_NAME}.json") - html_file_blob = bucket.blob(f"{REPORT_FOLDER}/{REPORT_FILE_NAME}.html") - try: - logger.info(f"Uploading registry report to GCS: {json_file_blob.name}") - json_file_blob.upload_from_string(json_string.encode()) - html_file_blob.upload_from_string(html_string.encode()) - except Exception as e: - logger.error(f"Error uploading registry report to GCS: {e}") - raise e diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/sentry.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/sentry.py deleted file mode 100644 index a482fb29873d..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/sentry.py +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import logging -import os - -import sentry_sdk - -sentry_logger = logging.getLogger("sentry") - - -def setup_sentry(): - """ - Setup the sentry SDK if SENTRY_DSN is defined for the environment. - - Additionally TRACES_SAMPLE_RATE can be set 0-1 otherwise will default to 0. - """ - from sentry_sdk.integrations.argv import ArgvIntegration - from sentry_sdk.integrations.atexit import AtexitIntegration - from sentry_sdk.integrations.dedupe import DedupeIntegration - from sentry_sdk.integrations.logging import LoggingIntegration - from sentry_sdk.integrations.modules import ModulesIntegration - from sentry_sdk.integrations.stdlib import StdlibIntegration - - SENTRY_DSN = os.environ.get("SENTRY_DSN") - SENTRY_ENVIRONMENT = os.environ.get("SENTRY_ENVIRONMENT") - TRACES_SAMPLE_RATE = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", 0)) - - sentry_logger.info("Setting up Sentry with") - sentry_logger.info(f"SENTRY_DSN: {SENTRY_DSN}") - sentry_logger.info(f"SENTRY_ENVIRONMENT: {SENTRY_ENVIRONMENT}") - sentry_logger.info(f"SENTRY_TRACES_SAMPLE_RATE: {TRACES_SAMPLE_RATE}") - - if SENTRY_DSN: - sentry_sdk.init( - dsn=SENTRY_DSN, - traces_sample_rate=TRACES_SAMPLE_RATE, - environment=SENTRY_ENVIRONMENT, - default_integrations=False, - integrations=[ - AtexitIntegration(), - DedupeIntegration(), - StdlibIntegration(), - ModulesIntegration(), - ArgvIntegration(), - LoggingIntegration(), - ], - ) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/spec_cache.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/spec_cache.py deleted file mode 100644 index 6495b34f9026..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/spec_cache.py +++ /dev/null @@ -1,120 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json -from dataclasses import dataclass -from enum import Enum -from typing import List - -from google.cloud import storage - -PROD_SPEC_CACHE_BUCKET_NAME = "io-airbyte-cloud-spec-cache" -CACHE_FOLDER = "specs" - - -class Registries(str, Enum): - OSS = "oss" - CLOUD = "cloud" - - @classmethod - def _missing_(cls, value): - """Returns the registry from the string value. (case insensitive)""" - value = value.lower() - for member in cls: - if member.lower() == value: - return member - return None - - -SPEC_FILE_NAMES = {Registries.OSS: "spec.json", Registries.CLOUD: "spec.cloud.json"} - - -@dataclass -class CachedSpec: - docker_repository: str - docker_image_tag: str - spec_cache_path: str - registry: Registries - - def __str__(self) -> str: - return self.spec_cache_path - - -def get_spec_file_name(registry: Registries) -> str: - return SPEC_FILE_NAMES[registry] - - -def get_registry_from_spec_cache_path(spec_cache_path: str) -> Registries: - """Returns the registry from the spec cache path.""" - for registry in Registries: - file_name = get_spec_file_name(registry) - if file_name in spec_cache_path: - return registry - - raise Exception(f"Could not find any registry file name in spec cache path: {spec_cache_path}") - - -def get_docker_info_from_spec_cache_path(spec_cache_path: str) -> CachedSpec: - """Returns the docker repository and tag from the spec cache path.""" - - registry = get_registry_from_spec_cache_path(spec_cache_path) - registry_file_name = get_spec_file_name(registry) - - # remove the leading "specs/" from the path using CACHE_FOLDER - without_folder = spec_cache_path.replace(f"{CACHE_FOLDER}/", "") - without_file = without_folder.replace(f"/{registry_file_name}", "") - - # split on only the last "/" to get the docker repository and tag - # this is because the docker repository can have "/" in it - docker_image_tag = without_file.split("/")[-1] - docker_repository = without_file.replace(f"/{docker_image_tag}", "") - - return CachedSpec( - docker_repository=docker_repository, docker_image_tag=docker_image_tag, spec_cache_path=spec_cache_path, registry=registry - ) - - -class SpecCache: - def __init__(self, bucket_name: str = PROD_SPEC_CACHE_BUCKET_NAME): - self.client = storage.Client.create_anonymous_client() - self.bucket = self.client.bucket(bucket_name) - self.cached_specs = self.get_all_cached_specs() - - def get_all_cached_specs(self) -> List[CachedSpec]: - """Returns a list of all the specs in the spec cache bucket.""" - - blobs = self.bucket.list_blobs(prefix=CACHE_FOLDER) - - return [get_docker_info_from_spec_cache_path(blob.name) for blob in blobs if blob.name.endswith(".json")] - - def _find_spec_cache(self, docker_repository: str, docker_image_tag: str, registry: Registries) -> CachedSpec: - """Returns the spec cache path for a given docker repository and tag.""" - - # find the spec cache path for the given docker repository and tag - for cached_spec in self.cached_specs: - if ( - cached_spec.docker_repository == docker_repository - and cached_spec.registry == registry - and cached_spec.docker_image_tag == docker_image_tag - ): - return cached_spec - - return None - - def find_spec_cache_with_fallback(self, docker_repository: str, docker_image_tag: str, registry_str: str) -> CachedSpec: - """Returns the spec cache path for a given docker repository and tag and fallback to OSS if none found""" - registry = Registries(registry_str) - - # if the registry is cloud try to return the cloud spec first - if registry == Registries.CLOUD: - spec_cache = self._find_spec_cache(docker_repository, docker_image_tag, registry) - if spec_cache: - return spec_cache - - # fallback to OSS - return self._find_spec_cache(docker_repository, docker_image_tag, Registries.OSS) - - def download_spec(self, spec: CachedSpec) -> dict: - """Downloads the spec from the spec cache bucket.""" - return json.loads(self.bucket.blob(spec.spec_cache_path).download_as_string()) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/specs_secrets_mask.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/specs_secrets_mask.py deleted file mode 100644 index 6b15a7ebd057..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/specs_secrets_mask.py +++ /dev/null @@ -1,100 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# -import json -import logging -import os -from typing import Set - -import dpath.util -import sentry_sdk -import yaml -from google.cloud import storage -from google.oauth2 import service_account - -from metadata_service.constants import PUBLISH_UPDATE_CHANNEL, REGISTRIES_FOLDER, SPECS_SECRETS_MASK_FILE_NAME, VALID_REGISTRIES -from metadata_service.helpers.gcs import get_gcs_storage_client, safe_read_gcs_file -from metadata_service.helpers.slack import send_slack_message -from metadata_service.models.generated import ConnectorRegistryV0 -from metadata_service.models.transform import to_json_sanitized_dict -from metadata_service.registry import PolymorphicRegistryEntry - -logger = logging.getLogger(__name__) - - -@sentry_sdk.trace -def _get_registries_from_gcs(bucket: storage.Bucket) -> list[ConnectorRegistryV0]: - """Get the registries from GCS and return a list of ConnectorRegistryV0 objects.""" - registries = [] - for registry in VALID_REGISTRIES: - registry_name = f"{registry}_registry.json" - try: - logger.info(f"Getting registry {registry_name} from GCS") - blob = bucket.blob(f"{REGISTRIES_FOLDER}/{registry_name}") - registry_dict = json.loads(safe_read_gcs_file(blob)) - registries.append(ConnectorRegistryV0.parse_obj(registry_dict)) - except Exception as e: - logger.error(f"Error getting registry {registry_name} from GCS: {e}") - raise e - - return registries - - -def _get_specs_secrets_from_registry_entries(entries: list[PolymorphicRegistryEntry]) -> Set[str]: - """Get the specs secrets from the registry entries and return a set of secret properties.""" - secret_properties = set() - for entry in entries: - sanitized_entry = to_json_sanitized_dict(entry) - spec_properties = sanitized_entry["spec"]["connectionSpecification"].get("properties") - if spec_properties is None: - continue - for type_path, _ in dpath.util.search(spec_properties, "**/type", yielded=True): - absolute_path = f"/{type_path}" - if "/" in type_path: - property_path, _ = absolute_path.rsplit(sep="/", maxsplit=1) - else: - property_path = absolute_path - property_definition = dpath.util.get(spec_properties, property_path) - marked_as_secret = property_definition.get("airbyte_secret", False) - if marked_as_secret: - secret_properties.add(property_path.split("/")[-1]) - - return secret_properties - - -@sentry_sdk.trace -def _persist_secrets_to_gcs(specs_secrets: Set[str], bucket: storage.Bucket) -> None: - """Persist the specs secrets to GCS.""" - specs_secrets_mask_blob = bucket.blob(f"{REGISTRIES_FOLDER}/{SPECS_SECRETS_MASK_FILE_NAME}") - - try: - logger.info(f"Uploading specs secrets mask to GCS: {specs_secrets_mask_blob.name}") - specs_secrets_mask_blob.upload_from_string(yaml.dump({"properties": sorted(list(specs_secrets))})) - except Exception as e: - logger.error(f"Error uploading specs secrets mask to GCS: {e}") - raise e - - -def generate_and_persist_specs_secrets_mask(bucket_name: str) -> None: - """Generate and persist the specs secrets mask to GCS. - - Args: - bucket_name (str): The name of the bucket to persist the specs secrets mask to. - - Returns: - None - """ - - client = get_gcs_storage_client() - bucket = client.bucket(bucket_name) - - registries = _get_registries_from_gcs(bucket) - all_entries = [entry for registry in registries for entry in registry.sources + registry.destinations] - - all_specs_secrets = _get_specs_secrets_from_registry_entries(all_entries) - - try: - _persist_secrets_to_gcs(all_specs_secrets, bucket) - except Exception as e: - message = f"*🤖 🔴 _Specs Secrets Mask Generation_ FAILED*:\nFailed to generate and persist `{SPECS_SECRETS_MASK_FILE_NAME}` to registry GCS bucket." - send_slack_message(PUBLISH_UPDATE_CHANNEL, message) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/stale_metadata_report.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/stale_metadata_report.py deleted file mode 100644 index 74950b33c22f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/stale_metadata_report.py +++ /dev/null @@ -1,262 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import datetime -import logging -import os -import re -import textwrap -from typing import Any, Mapping, Optional - -import pandas as pd -import requests -import yaml -from github import Auth, Github - -from metadata_service.helpers.gcs import get_gcs_storage_client -from metadata_service.helpers.slack import send_slack_message -from metadata_service.models.generated import ConnectorMetadataDefinitionV0 - -from .constants import ( - EXTENSIBILITY_TEAM_SLACK_TEAM_ID, - GITHUB_REPO_NAME, - METADATA_FILE_NAME, - METADATA_FOLDER, - PUBLISH_GRACE_PERIOD, - PUBLISH_UPDATE_CHANNEL, - STALE_REPORT_CHANNEL, -) - -logger = logging.getLogger(__name__) - - -def _is_younger_than_grace_period(last_modified_at: datetime.datetime) -> bool: - """ - Determine if a metadata entry is younger than the grace period. - - Args: - last_modified_at (datetime.datetime): The last modified date of the metadata entry. - Returns: - bool: True if the metadata entry is younger than the grace period, False otherwise. - """ - grace_period_marker = datetime.datetime.now(datetime.timezone.utc) - PUBLISH_GRACE_PERIOD - return last_modified_at > grace_period_marker - - -def _entry_should_be_on_gcs(metadata_model: ConnectorMetadataDefinitionV0) -> bool: - """ - Determine if a metadata entry should be on GCS. - - Args: - metadata_model (ConnectorMetadataDefinitionV0): A ConnectorMetadataDefinitionV0 object. - Returns: - bool: True if the metadata entry should be on GCS, False otherwise. - """ - if metadata_model.data.supportLevel and metadata_model.data.supportLevel.__root__ == "archived": - logger.info( - f"Skipping. Connector `{metadata_model.data.dockerRepository}` is archived or does not have a support level. Support level: {metadata_model.data.supportLevel.__root__}" - ) - return False - if "-rc" in metadata_model.data.dockerImageTag: - logger.info( - f"Skipping. Connector `{metadata_model.data.dockerRepository}` is a release candidate. Docker image tag: {metadata_model.data.dockerImageTag}" - ) - return False - return True - - -def _get_github_metadata_download_urls() -> list[str]: - """ - Get the download URLs for the metadata files on GitHub. - - Returns: - list[str]: A list of download URLs for the metadata files on GitHub. - """ - github_token = os.getenv("GITHUB_TOKEN") - if not github_token: - raise ValueError("GITHUB_TOKEN is not set") - auth = Auth.Token(github_token) - github_client = Github(auth=auth) - repo = github_client.get_repo(GITHUB_REPO_NAME) - - query = f"repo:{repo.full_name} filename:{METADATA_FILE_NAME}" - file_contents = github_client.search_code(query) - - logger.debug("Getting the download URL for each found metadata file.") - - metadata_download_urls = [] - for file_content in file_contents: - logger.debug(f"File content path: {file_content.path}") - if re.match(r"airbyte-integrations/connectors/(source|destination)-.+/metadata\.yaml$", file_content.path): - logger.debug(f"Getting commits for file: {file_content.path}") - commits = repo.get_commits(path=file_content.path) - last_modified_at = commits[0].commit.author.date - if not _is_younger_than_grace_period(last_modified_at): - metadata_download_urls.append(file_content.download_url) - else: - logger.info( - f"Skipping. Metadata file on Github `{file_content.path}` was modified more recently than the grace period. Last modified at: {last_modified_at}" - ) - logger.debug(f"Found {len(metadata_download_urls)} download URLs") - - return metadata_download_urls - - -def _get_and_parse_metadata_files(metadata_download_urls: list[str]) -> list[ConnectorMetadataDefinitionV0]: - """ - Get and parse the contents of the metadata files. - - Args: - metadata_download_urls (list[str]): A list of download URLs for the metadata files. - Returns: - list[ConnectorMetadataDefinitionV0]: A list of ConnectorMetadataDefinitionV0 objects. - """ - logger.debug("Downloading and parsing the contents of the metadata files.") - connector_metadata_list = [] - for metadata_download_url in metadata_download_urls: - logger.debug(f"Downloading metadata from {metadata_download_url}") - response = requests.get(metadata_download_url) - response.raise_for_status() - metadata_yaml = response.text - metadata_dict = yaml.safe_load(metadata_yaml) - try: - connector_metadata = ConnectorMetadataDefinitionV0.parse_obj(metadata_dict) - connector_metadata_list.append(connector_metadata) - except Exception as e: - logger.info(f"Skipping. Failed to parse metadata for metadata at path: {metadata_download_url}. Exception: {e}") - continue - logger.debug(f"Parsed {len(connector_metadata_list)} metadata files") - return connector_metadata_list - - -def _get_latest_metadata_versions_on_github() -> Mapping[str, Any]: - """ - Get the latest metadata versions on GitHub. - - Returns: - Mapping[str, Any]: A mapping of connector names to their latest metadata versions on GitHub. - """ - logger.info(f"Getting latest metadata versions on GitHub for {GITHUB_REPO_NAME}") - - metadata_download_urls = _get_github_metadata_download_urls() - github_connector_metadata = _get_and_parse_metadata_files(metadata_download_urls) - - latest_metadata_versions_on_github = { - connector_metadata.data.dockerRepository: connector_metadata.data.dockerImageTag - for connector_metadata in github_connector_metadata - if _entry_should_be_on_gcs(connector_metadata) - } - - logger.info(f"Found {len(latest_metadata_versions_on_github)} connectors on GitHub") - - return latest_metadata_versions_on_github - - -def _get_latest_metadata_entries_on_gcs(bucket_name: str) -> Mapping[str, Any]: - """ - Get the latest metadata entries on GCS. - - Args: - bucket_name (str): The name of the GCS bucket to check for stale metadata. - Returns: - Mapping[str, Any]: A mapping of connector names to their latest metadata versions on GCS. - """ - logger.info(f"Getting latest metadata entries on GCS for {bucket_name}") - storage_client = get_gcs_storage_client() - bucket = storage_client.bucket(bucket_name) - - try: - logger.debug(f"Listing blobs in {bucket_name} with prefix {METADATA_FOLDER}/**/latest/{METADATA_FILE_NAME}") - blobs = bucket.list_blobs(match_glob=f"{METADATA_FOLDER}/**/latest/{METADATA_FILE_NAME}") - logger.debug("Found blobs.") - except Exception as e: - logger.error(f"Error getting blobs from GCS: {e}") - raise e - - latest_metadata_entries_on_gcs = {} - for blob in blobs: - metadata_dict = yaml.safe_load(blob.download_as_bytes().decode("utf-8")) - connector_metadata = ConnectorMetadataDefinitionV0.parse_obj(metadata_dict) - latest_metadata_entries_on_gcs[connector_metadata.data.dockerRepository] = connector_metadata.data.dockerImageTag - - logger.info(f"Found {len(latest_metadata_entries_on_gcs)} connectors on GCS") - return latest_metadata_entries_on_gcs - - -def _generate_stale_metadata_report( - latest_metadata_versions_on_github: Mapping[str, Any], latest_metadata_entries_on_gcs: Mapping[str, Any] -) -> pd.DataFrame: - """ - Generate the stale metadata report. - - Args: - latest_metadata_versions_on_github (Mapping[str, Any]): A mapping of connector names to their latest metadata versions on GitHub. - latest_metadata_entries_on_gcs (Mapping[str, Any]): A mapping of connector names to their latest metadata versions on GCS. - Returns: - pd.DataFrame: A DataFrame containing the stale metadata. - """ - stale_connectors = [] - for docker_repository, github_docker_image_tag in latest_metadata_versions_on_github.items(): - gcs_docker_image_tag = latest_metadata_entries_on_gcs.get(docker_repository) - if gcs_docker_image_tag != github_docker_image_tag: - stale_connectors.append( - {"connector": docker_repository, "master_version": github_docker_image_tag, "gcs_version": gcs_docker_image_tag} - ) - - stale_connectors.sort(key=lambda x: x.get("connector")) - return pd.DataFrame(stale_connectors) - - -def _publish_stale_metadata_report( - stale_metadata_report: pd.DataFrame, latest_metadata_versions_on_github_count: int, latest_metadata_versions_on_gcs_count: int -) -> tuple[bool, Optional[str]]: - """ - Publish the stale metadata report to the specified Slack channels. - - Args: - stale_metadata_report (pd.DataFrame): A DataFrame containing the stale metadata. - latest_metadata_versions_on_github_count (int): The number of metadata files on our master branch. - latest_metadata_versions_on_gcs_count (int): The number of latest metadata files hosted in GCS. - Returns: - tuple[bool, Optional[str]]: A tuple containing a boolean indicating whether the report was published and an error message. - """ - any_stale = len(stale_metadata_report) > 0 - if any_stale: - stale_report_md = stale_metadata_report.to_markdown(index=False) - send_slack_message(STALE_REPORT_CHANNEL, f"🚨 Stale metadata detected! (cc. )") - sent, error_message = send_slack_message(STALE_REPORT_CHANNEL, stale_report_md, enable_code_block_wrapping=True) - if not sent: - logger.error(f"Failed to send stale metadata report: {error_message}") - return sent, error_message - if not any_stale: - message = textwrap.dedent( - f""" - Analyzed {latest_metadata_versions_on_github_count} metadata files on our master branch and {latest_metadata_versions_on_gcs_count} latest metadata files hosted in GCS. - All dockerImageTag value on master match the latest metadata files on GCS. - No stale metadata: GCS metadata are up to date with metadata hosted on GCS. - """ - ) - sent, error_message = send_slack_message(PUBLISH_UPDATE_CHANNEL, message) - if not sent: - logger.error(f"Failed to send success message: {error_message}") - return sent, error_message - return True, None - - -def generate_and_publish_stale_metadata_report(bucket_name: str) -> tuple[bool, Optional[str]]: - """ - Generate a stale metadata report and publish it to a Slack channel. - - Args: - bucket_name (str): The name of the GCS bucket to check for stale metadata. - Returns: - tuple[bool, Optional[str]]: A tuple containing a boolean indicating whether the report was published and an optional error message. - """ - latest_metadata_entries_on_gcs = _get_latest_metadata_entries_on_gcs(bucket_name) - latest_metadata_versions_on_github = _get_latest_metadata_versions_on_github() - stale_metadata_report = _generate_stale_metadata_report(latest_metadata_versions_on_github, latest_metadata_entries_on_gcs) - report_published, error_message = _publish_stale_metadata_report( - stale_metadata_report, len(latest_metadata_versions_on_github), len(latest_metadata_entries_on_gcs) - ) - return report_published, error_message diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/base.html b/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/base.html deleted file mode 100644 index 0415d0c0afec..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/base.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - {% block title %}{% endblock %} - - - - - - - {% block content%} {% endblock %} - - diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/connector_registry_locations.html b/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/connector_registry_locations.html deleted file mode 100644 index 9692e4435921..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/connector_registry_locations.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'base.html' %} - -{% block title%}Airbyte Connector Registry{% endblock %} - -{% block content %} -

Airbyte Connector Registries

-

Sources

-
- {{ sources_table_html }} -
- -

Destinations

-
- {{ destinations_table_html }} -
-{% endblock %} \ No newline at end of file diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/render.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/render.py deleted file mode 100644 index 03cccb1f429d..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/templates/render.py +++ /dev/null @@ -1,101 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import json -import urllib.parse -from dataclasses import dataclass -from datetime import timedelta -from typing import Any, Callable, Hashable, List, Optional - -import pandas as pd -from jinja2 import Environment, PackageLoader - -from metadata_service.helpers.object_helpers import deep_copy_params - -# 🔗 HTML Renderers - - -def simple_link_html(url: str) -> str | None: - if not url: - return None - - return f'🔗 Link' - - -def icon_image_html(icon_url: str) -> str | None: - if not icon_url: - return None - - icon_size = "30" - return f'' - - -def test_badge_html(test_summary_url: str) -> str | None: - if not test_summary_url: - return None - - report_url = f"{test_summary_url}/index.html" - image_shield_base = "https://img.shields.io/endpoint" - icon_url = f"{test_summary_url}/badge.json" - icon_url_encoded = urllib.parse.quote(icon_url) - icon_image = f'' - return f'{icon_image}' - - -def internal_level_html(level_value: float) -> str: - # cast to int to remove decimal places - level = int(level_value) - - return f"Level {level}" - - -# Dataframe to HTML - - -@dataclass -class ColumnInfo: - column: str - title: str - formatter: Optional[Callable[[Any], str]] = None - - -def dataframe_to_table_html(df: pd.DataFrame, column_mapping: List[ColumnInfo]) -> str: - """ - Convert a dataframe to an HTML table. - """ - - # convert true and false to checkmarks and x's - df.replace({True: "✅", False: "❌"}, inplace=True) - - title_mapping = {column_info["column"]: column_info["title"] for column_info in column_mapping} - - df.rename(columns=title_mapping, inplace=True) - - # explicit type decl to satisfy the type checker - html_formatters: dict[Hashable, Callable[[Any], str]] = { - column_info["title"]: column_info["formatter"] for column_info in column_mapping if "formatter" in column_info - } - - columns = [column_info["title"] for column_info in column_mapping] - - return df.to_html( - columns=columns, - justify="left", - index=False, - formatters=html_formatters, - escape=False, - classes="styled-table", - na_rep="❌", - render_links=True, - ) - - -# Templates - - -def render_connector_registry_locations_html(destinations_table_html: str, sources_table_html: str) -> str: - # yes, we really are dynamically loading the package - env = Environment(loader=PackageLoader("metadata_service", "templates")) - template = env.get_template("connector_registry_locations.html") - return template.render(destinations_table_html=destinations_table_html, sources_table_html=sources_table_html) diff --git a/airbyte-ci/connectors/metadata_service/lib/metadata_service/validators/metadata_validator.py b/airbyte-ci/connectors/metadata_service/lib/metadata_service/validators/metadata_validator.py deleted file mode 100644 index e4ee389d3a6d..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/metadata_service/validators/metadata_validator.py +++ /dev/null @@ -1,335 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import logging -import pathlib -from dataclasses import dataclass -from typing import Callable, List, Optional, Tuple, Union - -import semver -import yaml -from pydantic import ValidationError -from pydash.objects import get - -from metadata_service.docker_hub import get_latest_version_on_dockerhub, is_image_on_docker_hub -from metadata_service.models.generated.ConnectorMetadataDefinitionV0 import ConnectorMetadataDefinitionV0 - -logger = logging.getLogger(__name__) - - -@dataclass(frozen=True) -class ValidatorOptions: - docs_path: str - prerelease_tag: Optional[str] = None - disable_dockerhub_checks: bool = False - - -ValidationResult = Tuple[bool, Optional[Union[ValidationError, str]]] -Validator = Callable[[ConnectorMetadataDefinitionV0, ValidatorOptions], ValidationResult] - -_SOURCE_DECLARATIVE_MANIFEST_DEFINITION_ID = "64a2f99c-542f-4af8-9a6f-355f1217b436" - - -def validate_metadata_images_in_dockerhub( - metadata_definition: ConnectorMetadataDefinitionV0, validator_opts: ValidatorOptions -) -> ValidationResult: - if validator_opts.disable_dockerhub_checks: - return True, None - - metadata_definition_dict = metadata_definition.dict() - base_docker_image = get(metadata_definition_dict, "data.dockerRepository") - base_docker_version = get(metadata_definition_dict, "data.dockerImageTag") - - oss_docker_image = get(metadata_definition_dict, "data.registryOverrides.oss.dockerRepository", base_docker_image) - oss_docker_version = get(metadata_definition_dict, "data.registryOverrides.oss.dockerImageTag", base_docker_version) - - cloud_docker_image = get(metadata_definition_dict, "data.registryOverrides.cloud.dockerRepository", base_docker_image) - cloud_docker_version = get(metadata_definition_dict, "data.registryOverrides.cloud.dockerImageTag", base_docker_version) - - normalization_docker_image = get(metadata_definition_dict, "data.normalizationConfig.normalizationRepository", None) - normalization_docker_version = get(metadata_definition_dict, "data.normalizationConfig.normalizationTag", None) - - breaking_changes = get(metadata_definition_dict, "data.releases.breakingChanges", None) - breaking_change_versions = breaking_changes.keys() if breaking_changes else [] - - possible_docker_images = [ - (base_docker_image, base_docker_version), - (oss_docker_image, oss_docker_version), - (cloud_docker_image, cloud_docker_version), - (normalization_docker_image, normalization_docker_version), - ] - - if not validator_opts.prerelease_tag: - possible_docker_images.extend([(base_docker_image, version) for version in breaking_change_versions]) - - # Filter out tuples with None and remove duplicates - images_to_check = list(set(filter(lambda x: None not in x, possible_docker_images))) - - print(f"Checking that the following images are on dockerhub: {images_to_check}") - for image, version in images_to_check: - if not is_image_on_docker_hub(image, version, retries=3): - return False, f"Image {image}:{version} does not exist in DockerHub" - - return True, None - - -def validate_at_least_one_language_tag( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - """Ensure that there is at least one tag in the data.tags field that matches language:.""" - tags = get(metadata_definition, "data.tags", []) - if not any([tag.startswith("language:") for tag in tags]): - return False, "At least one tag must be of the form language:" - - return True, None - - -def validate_all_tags_are_keyvalue_pairs( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - """Ensure that all tags are of the form :.""" - tags = get(metadata_definition, "data.tags", []) - for tag in tags: - if ":" not in tag: - return False, f"Tag {tag} is not of the form :" - - return True, None - - -def is_major_version(version: str) -> bool: - """Check whether the version is of format N.0.0""" - semver_version = semver.Version.parse(version) - return semver_version.minor == 0 and semver_version.patch == 0 and semver_version.prerelease is None - - -def validate_major_version_bump_has_breaking_change_entry( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - """Ensure that if the major version is incremented, there is a breaking change entry for that version.""" - metadata_definition_dict = metadata_definition.dict() - image_tag = get(metadata_definition_dict, "data.dockerImageTag") - - if not is_major_version(image_tag): - return True, None - - # We are updating the same version since connector builder projects have a different concept of - # versioning. - # We do not check for breaking changes for source-declarative-connector in the metadata because the conenctor isn't directly used by any workspace. - # Breaking changes are instead tracked at the CDK level - if str(metadata_definition.data.definitionId) == _SOURCE_DECLARATIVE_MANIFEST_DEFINITION_ID: - return True, None - - releases = get(metadata_definition_dict, "data.releases") - if not releases: - return ( - False, - f"When doing a major version bump ({image_tag}), there must be a 'releases' property that contains 'breakingChanges' entries.", - ) - - breaking_changes = get(metadata_definition_dict, "data.releases.breakingChanges") - if breaking_changes is None or image_tag not in breaking_changes.keys(): - return False, f"Major version {image_tag} needs a 'releases.breakingChanges' entry indicating what changed." - - return True, None - - -def validate_docs_path_exists(metadata_definition: ConnectorMetadataDefinitionV0, validator_opts: ValidatorOptions) -> ValidationResult: - """Ensure that the doc_path exists.""" - if not pathlib.Path(validator_opts.docs_path).exists(): - return False, f"Could not find {validator_opts.docs_path}." - - return True, None - - -def validate_metadata_base_images_in_dockerhub( - metadata_definition: ConnectorMetadataDefinitionV0, validator_opts: ValidatorOptions -) -> ValidationResult: - if validator_opts.disable_dockerhub_checks: - return True, None - - metadata_definition_dict = metadata_definition.dict() - - image_address = get(metadata_definition_dict, "data.connectorBuildOptions.baseImage") - if image_address is None: - return True, None - - try: - image_name, tag_with_sha_prefix, digest = image_address.split(":") - # As we query the DockerHub API we need to remove the docker.io prefix - image_name = image_name.replace("docker.io/", "") - except ValueError: - return False, f"Image {image_address} is not in the format :@" - tag = tag_with_sha_prefix.split("@")[0] - - print(f"Checking that the base images is on dockerhub: {image_address}") - - if not is_image_on_docker_hub(image_name, tag, digest, retries=3): - return False, f"Image {image_address} does not exist in DockerHub" - - return True, None - - -def validate_pypi_only_for_python( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - """Ensure that if pypi publishing is enabled for a connector, it has a python language tag.""" - - pypi_enabled = get(metadata_definition, "data.remoteRegistries.pypi.enabled", False) - if not pypi_enabled: - return True, None - - tags = get(metadata_definition, "data.tags", []) - if "language:python" not in tags and "language:low-code" not in tags: - return False, "If pypi publishing is enabled, the connector must have a python language tag." - - return True, None - - -def validate_docker_image_tag_is_not_decremented( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - if _validator_opts and _validator_opts.disable_dockerhub_checks: - return True, None - if _validator_opts and _validator_opts.prerelease_tag: - return True, None - docker_image_name = get(metadata_definition, "data.dockerRepository") - if not docker_image_name: - return False, "The dockerRepository field is not set" - docker_image_tag = get(metadata_definition, "data.dockerImageTag") - if not docker_image_tag: - return False, "The dockerImageTag field is not set." - latest_released_version = get_latest_version_on_dockerhub(docker_image_name) - # This is happening when the connector has never been released to DockerHub - if not latest_released_version: - return True, None - if docker_image_tag == latest_released_version: - return True, None - current_semver_version = semver.Version.parse(docker_image_tag) - latest_released_semver_version = semver.Version.parse(latest_released_version) - if current_semver_version < latest_released_semver_version: - return ( - False, - f"The dockerImageTag value ({current_semver_version}) can't be decremented: it should be equal to or above {latest_released_version}.", - ) - return True, None - - -def check_is_dev_version(version: str) -> bool: - """Check whether the version is a pre-release version.""" - parsed_version = semver.VersionInfo.parse(version) - return parsed_version.prerelease is not None and not "rc" in parsed_version.prerelease - - -def check_is_release_candidate_version(version: str) -> bool: - """Check whether the version is a release candidate version.""" - parsed_version = semver.VersionInfo.parse(version) - return parsed_version.prerelease is not None and "rc" in parsed_version.prerelease - - -def check_is_major_release_candidate_version(version: str) -> bool: - """Check whether the version is a major release candidate version. - Example: 2.0.0-rc.1 - """ - - if not check_is_release_candidate_version(version): - return False - - # The version is a release candidate version - parsed_version = semver.VersionInfo.parse(version) - # No major version exists. - if parsed_version.major == 0: - return False - # The current release candidate is for a major version - if parsed_version.minor == 0 and parsed_version.patch == 0: - return True - - -def validate_rc_suffix_and_rollout_configuration( - metadata_definition: ConnectorMetadataDefinitionV0, _validator_opts: ValidatorOptions -) -> ValidationResult: - # Bypass validation for pre-releases - if _validator_opts and _validator_opts.prerelease_tag: - return True, None - - docker_image_tag = get(metadata_definition, "data.dockerImageTag") - if docker_image_tag is None: - return False, "The dockerImageTag field is not set." - try: - is_major_release_candidate_version = check_is_major_release_candidate_version(docker_image_tag) - is_dev_version = check_is_dev_version(docker_image_tag) - is_rc_version = check_is_release_candidate_version(docker_image_tag) - is_prerelease = is_dev_version or is_rc_version - enabled_progressive_rollout = get(metadata_definition, "data.releases.rolloutConfiguration.enableProgressiveRollout", None) - - # Major release candidate versions are not allowed - if is_major_release_candidate_version: - return ( - False, - "The dockerImageTag has an -rc. suffix for a major version. Release candidates for major version (with breaking changes) are not allowed.", - ) - - # Release candidates must have progressive rollout configuration set (True or False). - # Note: We allow False here because the rollback pipeline intentionally sets - # enableProgressiveRollout: false on RC versions to stop progressive rollout. - # The registry code at registry.py relies on this RC + false combination. - if is_rc_version and enabled_progressive_rollout is None: - return ( - False, - "The dockerImageTag field has an -rc. suffix but the connector is not set to use progressive rollout (releases.rolloutConfiguration.enableProgressiveRollout).", - ) - - # Progressive rollout can be enabled only for release candidates - if enabled_progressive_rollout is True and not is_prerelease: - return ( - False, - "The dockerImageTag field should have an -rc. suffix as the connector is set to use progressive rollout (releases.rolloutConfiguration.enableProgressiveRollout). Example: 2.1.0-rc.1", - ) - except ValueError: - return False, f"The dockerImageTag field is not a valid semver version: {docker_image_tag}." - - return True, None - - -PRE_UPLOAD_VALIDATORS = [ - validate_all_tags_are_keyvalue_pairs, - validate_at_least_one_language_tag, - validate_major_version_bump_has_breaking_change_entry, - validate_docs_path_exists, - validate_metadata_base_images_in_dockerhub, - validate_pypi_only_for_python, - validate_docker_image_tag_is_not_decremented, - validate_rc_suffix_and_rollout_configuration, -] - - -POST_UPLOAD_VALIDATORS = PRE_UPLOAD_VALIDATORS + [ - validate_metadata_images_in_dockerhub, -] - - -def validate_and_load( - file_path: pathlib.Path, - validators_to_run: List[Validator], - validator_opts: ValidatorOptions, -) -> Tuple[Optional[ConnectorMetadataDefinitionV0], Optional[ValidationError]]: - """Load a metadata file from a path (runs jsonschema validation) and run optional extra validators. - - Returns a tuple of (metadata_model, error_message). - If the metadata file is valid, metadata_model will be populated. - Otherwise, error_message will be populated with a string describing the error. - """ - try: - # Load the metadata file - this implicitly runs jsonschema validation - metadata = yaml.safe_load(file_path.read_text()) - metadata_model = ConnectorMetadataDefinitionV0.parse_obj(metadata) - except ValidationError as e: - return None, f"Validation error: {e}" - - for validator in validators_to_run: - print(f"Running validator: {validator.__name__}") - is_valid, error = validator(metadata_model, validator_opts) - if not is_valid: - return None, f"Validation error: {error}" - - return metadata_model, None diff --git a/airbyte-ci/connectors/metadata_service/lib/package-lock.json b/airbyte-ci/connectors/metadata_service/lib/package-lock.json deleted file mode 100644 index aab3f9ca2da3..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/package-lock.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "metadata-service-schema-tools", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "metadata-service-schema-tools", - "version": "1.0.0", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.0.0" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - } - } -} diff --git a/airbyte-ci/connectors/metadata_service/lib/package.json b/airbyte-ci/connectors/metadata_service/lib/package.json deleted file mode 100644 index af159ca7c3cd..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "metadata-service-schema-tools", - "version": "1.0.0", - "description": "Schema bundling tools for Airbyte metadata service", - "private": true, - "scripts": { - "bundle-schemas": "node bin/bundle-schemas.js" - }, - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.0.0" - } -} diff --git a/airbyte-ci/connectors/metadata_service/lib/poetry.lock b/airbyte-ci/connectors/metadata_service/lib/poetry.lock deleted file mode 100644 index 2902507d74dd..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/poetry.lock +++ /dev/null @@ -1,2294 +0,0 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "argcomplete" -version = "3.6.2" -description = "Bash tab completion for argparse" -optional = false -python-versions = ">=3.8" -files = [ - {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, - {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, -] - -[package.extras] -test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] - -[[package]] -name = "attrs" -version = "25.3.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.8" -files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, -] - -[package.extras] -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-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -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 = "beautifulsoup4" -version = "4.13.5" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, - {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, -] - -[package.dependencies] -soupsieve = ">1.2" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "black" -version = "25.1.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "cachetools" -version = "5.5.2" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, - {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, -] - -[[package]] -name = "certifi" -version = "2025.8.3" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, -] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "chardet" -version = "5.2.0" -description = "Universal encoding detector for Python 3" -optional = false -python-versions = ">=3.7" -files = [ - {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, - {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.3" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, -] - -[[package]] -name = "click" -version = "8.2.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "cryptography" -version = "45.0.6" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -files = [ - {file = "cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42"}, - {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05"}, - {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453"}, - {file = "cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159"}, - {file = "cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec"}, - {file = "cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016"}, - {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3"}, - {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9"}, - {file = "cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02"}, - {file = "cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043"}, - {file = "cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719"}, -] - -[package.dependencies] -cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.6)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "datamodel-code-generator" -version = "0.17.2" -description = "Datamodel Code Generator" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "datamodel_code_generator-0.17.2-py3-none-any.whl", hash = "sha256:b42b8b4da4055ea96ddf303efecfa37316bb025e88513fffc4dad960cb56b43c"}, - {file = "datamodel_code_generator-0.17.2.tar.gz", hash = "sha256:e88e000b254b07937a45902e95c08c9edec21eee3492c3a3ad4b6a4115345924"}, -] - -[package.dependencies] -argcomplete = ">=1.10,<4.0" -black = ">=19.10b0" -genson = ">=1.2.1,<2.0" -inflect = ">=4.1.0,<6.0" -isort = ">=4.3.21,<6.0" -jinja2 = ">=2.10.1,<4.0" -openapi-spec-validator = ">=0.2.8,<=0.5.1" -packaging = "*" -prance = ">=0.18.2,<1.0" -pydantic = {version = ">=1.10.0,<2.0", extras = ["email"], markers = "python_version >= \"3.11\""} -PySnooper = ">=0.4.1,<2.0.0" -toml = ">=0.10.0,<1.0.0" -typed-ast = {version = ">=1.5.0", markers = "python_full_version >= \"3.9.8\""} - -[package.extras] -http = ["httpx"] - -[[package]] -name = "dnspython" -version = "2.7.0" -description = "DNS toolkit" -optional = false -python-versions = ">=3.9" -files = [ - {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, - {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=43)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=1.0.0)"] -idna = ["idna (>=3.7)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "dpath" -version = "2.2.0" -description = "Filesystem-like pathing and searching for dictionaries" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576"}, - {file = "dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e"}, -] - -[[package]] -name = "email-validator" -version = "2.3.0" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"}, - {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - -[[package]] -name = "future" -version = "1.0.0" -description = "Clean single-source support for Python 3 and 2" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, - {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, -] - -[[package]] -name = "gax-google-logging-v2" -version = "0.8.3" -description = "GAX library for the Google Logging API" -optional = false -python-versions = "*" -files = [ - {file = "gax-google-logging-v2-0.8.3.tar.gz", hash = "sha256:c36cbb93e070b3b535e9fffff6879efe447e525dff75b62eb4564def515e1a18"}, -] - -[package.dependencies] -google-gax = ">=0.12.5,<0.13.0" -googleapis-common-protos = ">=1.1.0" -grpc-google-logging-v2 = ">=0.8.1,<0.9.0" -oauth2client = ">=1.4.11" - -[[package]] -name = "gax-google-pubsub-v1" -version = "0.8.3" -description = "DEPRECATED" -optional = false -python-versions = "*" -files = [ - {file = "gax-google-pubsub-v1-0.8.3.tar.gz", hash = "sha256:943df4aa05cf0302fa1616197d05e29adb62be2c0f55f80d8345439d72471526"}, -] - -[package.dependencies] -google-gax = ">=0.12.5,<0.13.0" -googleapis-common-protos = ">=1.1.0" -grpc-google-pubsub-v1 = ">=0.8.1,<0.9.0" -oauth2client = ">=1.4.11" - -[[package]] -name = "gcloud" -version = "0.18.3" -description = "API Client library for Google Cloud" -optional = false -python-versions = "*" -files = [ - {file = "gcloud-0.18.3.tar.gz", hash = "sha256:0af2dec59fce20561752f86e42d981c6a255e306a6c5e5d1fa3d358a8857e4fb"}, -] - -[package.dependencies] -gax-google-logging-v2 = ">=0.8.0,<0.9dev" -gax-google-pubsub-v1 = ">=0.8.0,<0.9dev" -google-gax = ">=0.12.3,<0.13dev" -googleapis-common-protos = "*" -grpc-google-logging-v2 = ">=0.8.0,<0.9dev" -grpc-google-pubsub-v1 = ">=0.8.0,<0.9dev" -grpcio = ">=1.0rc1" -httplib2 = ">=0.9.1" -oauth2client = ">=2.0.1" -protobuf = ">=3.0.0b2,<3.0.0.b2.post1 || >3.0.0.b2.post1" -six = "*" - -[package.extras] -grpc = ["gax-google-logging-v2 (>=0.8.0,<0.9dev)", "gax-google-pubsub-v1 (>=0.8.0,<0.9dev)", "google-gax (>=0.12.3,<0.13dev)", "grpc-google-logging-v2 (>=0.8.0,<0.9dev)", "grpc-google-pubsub-v1 (>=0.8.0,<0.9dev)", "grpcio (>=1.0rc1)"] - -[[package]] -name = "genson" -version = "1.3.0" -description = "GenSON is a powerful, user-friendly JSON Schema generator." -optional = false -python-versions = "*" -files = [ - {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, - {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, -] - -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.45" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, - {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] - -[[package]] -name = "google" -version = "3.0.0" -description = "Python bindings to the Google search engine." -optional = false -python-versions = "*" -files = [ - {file = "google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935"}, - {file = "google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - -[[package]] -name = "google-api-core" -version = "2.25.1" -description = "Google API client core library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7"}, - {file = "google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.0" -googleapis-common-protos = ">=1.56.2,<2.0.0" -proto-plus = [ - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, - {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" -requests = ">=2.18.0,<3.0.0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] - -[[package]] -name = "google-auth" -version = "2.40.3" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca"}, - {file = "google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] -enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] -urllib3 = ["packaging", "urllib3"] - -[[package]] -name = "google-cloud-core" -version = "2.4.3" -description = "Google Cloud API client core library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e"}, - {file = "google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-storage" -version = "2.19.0" -description = "Google Cloud Storage API client library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba"}, - {file = "google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2"}, -] - -[package.dependencies] -google-api-core = ">=2.15.0,<3.0.0dev" -google-auth = ">=2.26.1,<3.0dev" -google-cloud-core = ">=2.3.0,<3.0dev" -google-crc32c = ">=1.0,<2.0dev" -google-resumable-media = ">=2.7.2" -requests = ">=2.18.0,<3.0.0dev" - -[package.extras] -protobuf = ["protobuf (<6.0.0dev)"] -tracing = ["opentelemetry-api (>=1.1.0)"] - -[[package]] -name = "google-crc32c" -version = "1.7.1" -description = "A python wrapper of the C library 'Google CRC32C'" -optional = false -python-versions = ">=3.9" -files = [ - {file = "google_crc32c-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b07d48faf8292b4db7c3d64ab86f950c2e94e93a11fd47271c28ba458e4a0d76"}, - {file = "google_crc32c-1.7.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7cc81b3a2fbd932a4313eb53cc7d9dde424088ca3a0337160f35d91826880c1d"}, - {file = "google_crc32c-1.7.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c67ca0a1f5b56162951a9dae987988679a7db682d6f97ce0f6381ebf0fbea4c"}, - {file = "google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc5319db92daa516b653600794d5b9f9439a9a121f3e162f94b0e1891c7933cb"}, - {file = "google_crc32c-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcdf5a64adb747610140572ed18d011896e3b9ae5195f2514b7ff678c80f1603"}, - {file = "google_crc32c-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:754561c6c66e89d55754106739e22fdaa93fafa8da7221b29c8b8e8270c6ec8a"}, - {file = "google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06"}, - {file = "google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9"}, - {file = "google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77"}, - {file = "google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53"}, - {file = "google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d"}, - {file = "google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194"}, - {file = "google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e"}, - {file = "google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337"}, - {file = "google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65"}, - {file = "google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6"}, - {file = "google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35"}, - {file = "google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638"}, - {file = "google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb"}, - {file = "google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6"}, - {file = "google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db"}, - {file = "google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3"}, - {file = "google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9"}, - {file = "google_crc32c-1.7.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:9fc196f0b8d8bd2789352c6a522db03f89e83a0ed6b64315923c396d7a932315"}, - {file = "google_crc32c-1.7.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:bb5e35dcd8552f76eed9461a23de1030920a3c953c1982f324be8f97946e7127"}, - {file = "google_crc32c-1.7.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f2226b6a8da04f1d9e61d3e357f2460b9551c5e6950071437e122c958a18ae14"}, - {file = "google_crc32c-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f2b3522222746fff0e04a9bd0a23ea003ba3cccc8cf21385c564deb1f223242"}, - {file = "google_crc32c-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bda0fcb632d390e3ea8b6b07bf6b4f4a66c9d02dcd6fbf7ba00a197c143f582"}, - {file = "google_crc32c-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:713121af19f1a617054c41f952294764e0c5443d5a5d9034b2cd60f5dd7e0349"}, - {file = "google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8e9afc74168b0b2232fb32dd202c93e46b7d5e4bf03e66ba5dc273bb3559589"}, - {file = "google_crc32c-1.7.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa8136cc14dd27f34a3221c0f16fd42d8a40e4778273e61a3c19aedaa44daf6b"}, - {file = "google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48"}, - {file = "google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82"}, - {file = "google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472"}, -] - -[package.extras] -testing = ["pytest"] - -[[package]] -name = "google-gax" -version = "0.12.5" -description = "Google API Extensions" -optional = false -python-versions = "*" -files = [ - {file = "google-gax-0.12.5.tar.gz", hash = "sha256:63312a04cb87ca50e857245f05c582b2171807a30e61cef85006b983bf659cb9"}, -] - -[package.dependencies] -future = ">=0.15.2" -grpcio = ">=1.0rc1" -oauth2client = ">=1.5.2" -ply = "3.8" -protobuf = ">=3.0.0b3" - -[[package]] -name = "google-resumable-media" -version = "2.7.2" -description = "Utilities for Google Media Downloads and Resumable Uploads" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, - {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, -] - -[package.dependencies] -google-crc32c = ">=1.0,<2.0dev" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.70.0" -description = "Common protobufs used in Google APIs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, - {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, -] - -[package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0)"] - -[[package]] -name = "grpc-google-logging-v2" -version = "0.8.1" -description = "GRPC library for the google-logging-v2 service" -optional = false -python-versions = "*" -files = [ - {file = "grpc-google-logging-v2-0.8.1.tar.gz", hash = "sha256:4b6b4e603860b134b2cb8732bb4d1f2cec963d553497dcf70b3f5ecc99d7fb4f"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.1.0" -grpcio = ">=1.0rc1" -oauth2client = ">=1.4.11" - -[[package]] -name = "grpc-google-pubsub-v1" -version = "0.8.1" -description = "GRPC library for the google-pubsub-v1 service" -optional = false -python-versions = "*" -files = [ - {file = "grpc-google-pubsub-v1-0.8.1.tar.gz", hash = "sha256:ab5a3a239a9678012cdc00a9b9a8fe8c75fca9a0035da41da3078145e9d967b9"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.1.0" -grpcio = ">=1.0rc1" -oauth2client = ">=1.4.11" - -[[package]] -name = "grpcio" -version = "1.74.0" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.9" -files = [ - {file = "grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907"}, - {file = "grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11"}, - {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e"}, - {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82"}, - {file = "grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7"}, - {file = "grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5"}, - {file = "grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31"}, - {file = "grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3"}, - {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f"}, - {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4"}, - {file = "grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b"}, - {file = "grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11"}, - {file = "grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8"}, - {file = "grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49"}, - {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707"}, - {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b"}, - {file = "grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c"}, - {file = "grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc"}, - {file = "grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89"}, - {file = "grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91"}, - {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f"}, - {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20"}, - {file = "grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa"}, - {file = "grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24"}, - {file = "grpcio-1.74.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4bc5fca10aaf74779081e16c2bcc3d5ec643ffd528d9e7b1c9039000ead73bae"}, - {file = "grpcio-1.74.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:6bab67d15ad617aff094c382c882e0177637da73cbc5532d52c07b4ee887a87b"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:655726919b75ab3c34cdad39da5c530ac6fa32696fb23119e36b64adcfca174a"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a2b06afe2e50ebfd46247ac3ba60cac523f54ec7792ae9ba6073c12daf26f0a"}, - {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f251c355167b2360537cf17bea2cf0197995e551ab9da6a0a59b3da5e8704f9"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f7b5882fb50632ab1e48cb3122d6df55b9afabc265582808036b6e51b9fd6b7"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:834988b6c34515545b3edd13e902c1acdd9f2465d386ea5143fb558f153a7176"}, - {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22b834cef33429ca6cc28303c9c327ba9a3fafecbf62fae17e9a7b7163cc43ac"}, - {file = "grpcio-1.74.0-cp39-cp39-win32.whl", hash = "sha256:7d95d71ff35291bab3f1c52f52f474c632db26ea12700c2ff0ea0532cb0b5854"}, - {file = "grpcio-1.74.0-cp39-cp39-win_amd64.whl", hash = "sha256:ecde9ab49f58433abe02f9ed076c7b5be839cf0153883a6d23995937a82392fa"}, - {file = "grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.74.0)"] - -[[package]] -name = "httplib2" -version = "0.22.0" -description = "A comprehensive HTTP client library." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, - {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, -] - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "importlib-resources" -version = "5.13.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-5.13.0-py3-none-any.whl", hash = "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2"}, - {file = "importlib_resources-5.13.0.tar.gz", hash = "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[[package]] -name = "inflect" -version = "5.6.2" -description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words" -optional = false -python-versions = ">=3.7" -files = [ - {file = "inflect-5.6.2-py3-none-any.whl", hash = "sha256:b45d91a4a28a4e617ff1821117439b06eaa86e2a4573154af0149e9be6687238"}, - {file = "inflect-5.6.2.tar.gz", hash = "sha256:aadc7ed73928f5e014129794bbac03058cca35d0a973a5fc4eb45c7fa26005f9"}, -] - -[package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.8" -files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jsonschema" -version = "4.17.3" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, - {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, -] - -[package.dependencies] -attrs = ">=17.4.0" -pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" - -[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)"] - -[[package]] -name = "jsonschema-spec" -version = "0.1.6" -description = "JSONSchema Spec with object-oriented paths" -optional = false -python-versions = ">=3.7.0,<4.0.0" -files = [ - {file = "jsonschema_spec-0.1.6-py3-none-any.whl", hash = "sha256:f2206d18c89d1824c1f775ba14ed039743b41a9167bd2c5bdb774b66b3ca0bbf"}, - {file = "jsonschema_spec-0.1.6.tar.gz", hash = "sha256:90215863b56e212086641956b20127ccbf6d8a3a38343dad01d6a74d19482f76"}, -] - -[package.dependencies] -jsonschema = ">=4.0.0,<4.18.0" -pathable = ">=0.4.1,<0.5.0" -PyYAML = ">=5.1" -requests = ">=2.31.0,<3.0.0" - -[[package]] -name = "lazy-object-proxy" -version = "1.12.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.9" -files = [ - {file = "lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8"}, - {file = "lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa"}, - {file = "lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23"}, - {file = "lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac"}, - {file = "lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5"}, - {file = "lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae575ad9b674d0029fc077c5231b3bc6b433a3d1a62a8c363df96974b5534728"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31020c84005d3daa4cc0fa5a310af2066efe6b0d82aeebf9ab199292652ff036"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800f32b00a47c27446a2b767df7538e6c66a3488632c402b4fb2224f9794f3c0"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:15400b18893f345857b9e18b9bd87bd06aba84af6ed086187add70aeaa3f93f1"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d3964fbd326578bcdfffd017ef101b6fb0484f34e731fe060ba9b8816498c36"}, - {file = "lazy_object_proxy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:424a8ab6695400845c39f13c685050eab69fa0bbac5790b201cd27375e5e41d7"}, - {file = "lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402"}, - {file = "lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61"}, -] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "numpy" -version = "2.3.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -files = [ - {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, - {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, - {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, - {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, - {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, - {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, - {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, - {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, - {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, - {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, - {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, - {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, - {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, - {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, - {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, - {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, - {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, - {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, - {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, - {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, -] - -[[package]] -name = "oauth2client" -version = "4.1.3" -description = "OAuth 2.0 client library" -optional = false -python-versions = "*" -files = [ - {file = "oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac"}, - {file = "oauth2client-4.1.3.tar.gz", hash = "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6"}, -] - -[package.dependencies] -httplib2 = ">=0.9.1" -pyasn1 = ">=0.1.7" -pyasn1-modules = ">=0.0.5" -rsa = ">=3.1.4" -six = ">=1.6.1" - -[[package]] -name = "openapi-schema-validator" -version = "0.3.4" -description = "OpenAPI schema validation for Python" -optional = false -python-versions = ">=3.7.0,<4.0.0" -files = [ - {file = "openapi-schema-validator-0.3.4.tar.gz", hash = "sha256:7cf27585dd7970b7257cefe48e1a3a10d4e34421831bdb472d96967433bc27bd"}, - {file = "openapi_schema_validator-0.3.4-py3-none-any.whl", hash = "sha256:34fbd14b7501abe25e64d7b4624a9db02cde1a578d285b3da6f34b290cdf0b3a"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -jsonschema = ">=4.0.0,<5.0.0" - -[package.extras] -isodate = ["isodate"] -rfc3339-validator = ["rfc3339-validator"] -strict-rfc3339 = ["strict-rfc3339"] - -[[package]] -name = "openapi-spec-validator" -version = "0.5.1" -description = "OpenAPI 2.0 (aka Swagger) and OpenAPI 3 spec validator" -optional = false -python-versions = ">=3.7.0,<4.0.0" -files = [ - {file = "openapi-spec-validator-0.5.1.tar.gz", hash = "sha256:8248634bad1f23cac5d5a34e193ab36e23914057ca69e91a1ede5af75552c465"}, - {file = "openapi_spec_validator-0.5.1-py3-none-any.whl", hash = "sha256:4a8aee1e45b1ac868e07ab25e18828fe9837baddd29a8e20fdb3d3c61c8eea3d"}, -] - -[package.dependencies] -importlib-resources = ">=5.8.0,<6.0.0" -jsonschema = ">=4.0.0,<5.0.0" -jsonschema-spec = ">=0.1.1,<0.2.0" -lazy-object-proxy = ">=1.7.1,<2.0.0" -openapi-schema-validator = ">=0.3.2,<0.4.0" -PyYAML = ">=5.1" - -[package.extras] -requests = ["requests"] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "pandas" -version = "2.3.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"}, - {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"}, - {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"}, - {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"}, - {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"}, - {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"}, - {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"}, - {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "pastel" -version = "0.2.1" -description = "Bring colors to your terminal." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, - {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, -] - -[[package]] -name = "pathable" -version = "0.4.4" -description = "Object-oriented paths" -optional = false -python-versions = "<4.0.0,>=3.7.0" -files = [ - {file = "pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2"}, - {file = "pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2"}, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.4.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.9" -files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "ply" -version = "3.8" -description = "Python Lex & Yacc" -optional = false -python-versions = "*" -files = [ - {file = "ply-3.8.tar.gz", hash = "sha256:e7d1bdff026beb159c9942f7a17e102c375638d9478a7ecd4cc0c76afd8de0b8"}, -] - -[[package]] -name = "poethepoet" -version = "0.20.0" -description = "A task runner that works well with poetry." -optional = false -python-versions = ">=3.8" -files = [ - {file = "poethepoet-0.20.0-py3-none-any.whl", hash = "sha256:cb37be15f3895ccc65ddf188c2e3d8fb79e26cc9d469a6098cb1c6f994659f6f"}, - {file = "poethepoet-0.20.0.tar.gz", hash = "sha256:ca5a2a955f52dfb0a53fad3c989ef0b69ce3d5ec0f6bfa9b1da1f9e32d262e20"}, -] - -[package.dependencies] -pastel = ">=0.2.1,<0.3.0" -tomli = ">=1.2.2" - -[package.extras] -poetry-plugin = ["poetry (>=1.0,<2.0)"] - -[[package]] -name = "prance" -version = "0.22.2.22.0" -description = "Resolving Swagger/OpenAPI 2.0 and 3.0.0 Parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "prance-0.22.2.22.0-py3-none-any.whl", hash = "sha256:57deeb67b7e93ef27c1c17845bf3ccb4af288ccfb5748c7e01779c01a8507f27"}, - {file = "prance-0.22.2.22.0.tar.gz", hash = "sha256:9a83f8a4f5fe0f2d896d238d4bec6b5788b10b94155414b3d88c21c1579b85bf"}, -] - -[package.dependencies] -chardet = ">=3.0" -packaging = ">=21.3" -requests = ">=2.25" -"ruamel.yaml" = ">=0.17.10" -six = ">=1.15,<2.0" - -[package.extras] -cli = ["click (>=7.0)"] -dev = ["bumpversion (>=0.6)", "pytest (>=6.1)", "pytest-cov (>=2.11)", "sphinx (>=3.4)", "towncrier (>=19.2)", "tox (>=3.4)"] -flex = ["flex (>=6.13,<7.0)"] -icu = ["PyICU (>=2.4,<3.0)"] -osv = ["openapi-spec-validator (>=0.5.1,<0.6.0)"] -ssv = ["swagger-spec-validator (>=2.4,<3.0)"] - -[[package]] -name = "proto-plus" -version = "1.26.1" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -files = [ - {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, - {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<7.0.0" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "6.32.0" -description = "" -optional = false -python-versions = ">=3.9" -files = [ - {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"}, - {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"}, - {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"}, - {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"}, - {file = "protobuf-6.32.0-cp39-cp39-win32.whl", hash = "sha256:7db8ed09024f115ac877a1427557b838705359f047b2ff2f2b2364892d19dacb"}, - {file = "protobuf-6.32.0-cp39-cp39-win_amd64.whl", hash = "sha256:15eba1b86f193a407607112ceb9ea0ba9569aed24f93333fe9a497cf2fda37d3"}, - {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"}, - {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"}, -] - -[[package]] -name = "pyasn1" -version = "0.6.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.2" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, - {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, -] - -[package.dependencies] -pyasn1 = ">=0.6.1,<0.7.0" - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pydantic" -version = "1.10.22" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-1.10.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57889565ccc1e5b7b73343329bbe6198ebc472e3ee874af2fa1865cfe7048228"}, - {file = "pydantic-1.10.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90729e22426de79bc6a3526b4c45ec4400caf0d4f10d7181ba7f12c01bb3897d"}, - {file = "pydantic-1.10.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8684d347f351554ec94fdcb507983d3116dc4577fb8799fed63c65869a2d10"}, - {file = "pydantic-1.10.22-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8dad498ceff2d9ef1d2e2bc6608f5b59b8e1ba2031759b22dfb8c16608e1802"}, - {file = "pydantic-1.10.22-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fac529cc654d4575cf8de191cce354b12ba705f528a0a5c654de6d01f76cd818"}, - {file = "pydantic-1.10.22-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4148232aded8dd1dd13cf910a01b32a763c34bd79a0ab4d1ee66164fcb0b7b9d"}, - {file = "pydantic-1.10.22-cp310-cp310-win_amd64.whl", hash = "sha256:ece68105d9e436db45d8650dc375c760cc85a6793ae019c08769052902dca7db"}, - {file = "pydantic-1.10.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e530a8da353f791ad89e701c35787418605d35085f4bdda51b416946070e938"}, - {file = "pydantic-1.10.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:654322b85642e9439d7de4c83cb4084ddd513df7ff8706005dada43b34544946"}, - {file = "pydantic-1.10.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8bece75bd1b9fc1c32b57a32831517943b1159ba18b4ba32c0d431d76a120ae"}, - {file = "pydantic-1.10.22-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eccb58767f13c6963dcf96d02cb8723ebb98b16692030803ac075d2439c07b0f"}, - {file = "pydantic-1.10.22-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7778e6200ff8ed5f7052c1516617423d22517ad36cc7a3aedd51428168e3e5e8"}, - {file = "pydantic-1.10.22-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffe02767d27c39af9ca7dc7cd479c00dda6346bb62ffc89e306f665108317a2"}, - {file = "pydantic-1.10.22-cp311-cp311-win_amd64.whl", hash = "sha256:23bc19c55427091b8e589bc08f635ab90005f2dc99518f1233386f46462c550a"}, - {file = "pydantic-1.10.22-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:92d0f97828a075a71d9efc65cf75db5f149b4d79a38c89648a63d2932894d8c9"}, - {file = "pydantic-1.10.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af5a2811b6b95b58b829aeac5996d465a5f0c7ed84bd871d603cf8646edf6ff"}, - {file = "pydantic-1.10.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cf06d8d40993e79af0ab2102ef5da77b9ddba51248e4cb27f9f3f591fbb096e"}, - {file = "pydantic-1.10.22-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:184b7865b171a6057ad97f4a17fbac81cec29bd103e996e7add3d16b0d95f609"}, - {file = "pydantic-1.10.22-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:923ad861677ab09d89be35d36111156063a7ebb44322cdb7b49266e1adaba4bb"}, - {file = "pydantic-1.10.22-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:82d9a3da1686443fb854c8d2ab9a473251f8f4cdd11b125522efb4d7c646e7bc"}, - {file = "pydantic-1.10.22-cp312-cp312-win_amd64.whl", hash = "sha256:1612604929af4c602694a7f3338b18039d402eb5ddfbf0db44f1ebfaf07f93e7"}, - {file = "pydantic-1.10.22-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b259dc89c9abcd24bf42f31951fb46c62e904ccf4316393f317abeeecda39978"}, - {file = "pydantic-1.10.22-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9238aa0964d80c0908d2f385e981add58faead4412ca80ef0fa352094c24e46d"}, - {file = "pydantic-1.10.22-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8029f05b04080e3f1a550575a1bca747c0ea4be48e2d551473d47fd768fc1b"}, - {file = "pydantic-1.10.22-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5c06918894f119e0431a36c9393bc7cceeb34d1feeb66670ef9b9ca48c073937"}, - {file = "pydantic-1.10.22-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e205311649622ee8fc1ec9089bd2076823797f5cd2c1e3182dc0e12aab835b35"}, - {file = "pydantic-1.10.22-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:815f0a73d5688d6dd0796a7edb9eca7071bfef961a7b33f91e618822ae7345b7"}, - {file = "pydantic-1.10.22-cp313-cp313-win_amd64.whl", hash = "sha256:9dfce71d42a5cde10e78a469e3d986f656afc245ab1b97c7106036f088dd91f8"}, - {file = "pydantic-1.10.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3ecaf8177b06aac5d1f442db1288e3b46d9f05f34fd17fdca3ad34105328b61a"}, - {file = "pydantic-1.10.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb36c2de9ea74bd7f66b5481dea8032d399affd1cbfbb9bb7ce539437f1fce62"}, - {file = "pydantic-1.10.22-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6b8d14a256be3b8fff9286d76c532f1a7573fbba5f189305b22471c6679854d"}, - {file = "pydantic-1.10.22-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:1c33269e815db4324e71577174c29c7aa30d1bba51340ce6be976f6f3053a4c6"}, - {file = "pydantic-1.10.22-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:8661b3ab2735b2a9ccca2634738534a795f4a10bae3ab28ec0a10c96baa20182"}, - {file = "pydantic-1.10.22-cp37-cp37m-win_amd64.whl", hash = "sha256:22bdd5fe70d4549995981c55b970f59de5c502d5656b2abdfcd0a25be6f3763e"}, - {file = "pydantic-1.10.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e3f33d1358aa4bc2795208cc29ff3118aeaad0ea36f0946788cf7cadeccc166b"}, - {file = "pydantic-1.10.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:813f079f9cd136cac621f3f9128a4406eb8abd2ad9fdf916a0731d91c6590017"}, - {file = "pydantic-1.10.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab618ab8dca6eac7f0755db25f6aba3c22c40e3463f85a1c08dc93092d917704"}, - {file = "pydantic-1.10.22-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d128e1aaa38db88caca920d5822c98fc06516a09a58b6d3d60fa5ea9099b32cc"}, - {file = "pydantic-1.10.22-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:cc97bbc25def7025e55fc9016080773167cda2aad7294e06a37dda04c7d69ece"}, - {file = "pydantic-1.10.22-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dda5d7157d543b1fa565038cae6e952549d0f90071c839b3740fb77c820fab8"}, - {file = "pydantic-1.10.22-cp38-cp38-win_amd64.whl", hash = "sha256:a093fe44fe518cb445d23119511a71f756f8503139d02fcdd1173f7b76c95ffe"}, - {file = "pydantic-1.10.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec54c89b2568b258bb30d7348ac4d82bec1b58b377fb56a00441e2ac66b24587"}, - {file = "pydantic-1.10.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8f1d1a1532e4f3bcab4e34e8d2197a7def4b67072acd26cfa60e92d75803a48"}, - {file = "pydantic-1.10.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad83ca35508c27eae1005b6b61f369f78aae6d27ead2135ec156a2599910121"}, - {file = "pydantic-1.10.22-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53cdb44b78c420f570ff16b071ea8cd5a477635c6b0efc343c8a91e3029bbf1a"}, - {file = "pydantic-1.10.22-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:16d0a5ae9d98264186ce31acdd7686ec05fd331fab9d68ed777d5cb2d1514e5e"}, - {file = "pydantic-1.10.22-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8aee040e25843f036192b1a1af62117504a209a043aa8db12e190bb86ad7e611"}, - {file = "pydantic-1.10.22-cp39-cp39-win_amd64.whl", hash = "sha256:7f691eec68dbbfca497d3c11b92a3e5987393174cbedf03ec7a4184c35c2def6"}, - {file = "pydantic-1.10.22-py3-none-any.whl", hash = "sha256:343037d608bcbd34df937ac259708bfc83664dadf88afe8516c4f282d7d471a9"}, - {file = "pydantic-1.10.22.tar.gz", hash = "sha256:ee1006cebd43a8e7158fb7190bb8f4e2da9649719bff65d0c287282ec38dec6d"}, -] - -[package.dependencies] -email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pydash" -version = "6.0.2" -description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydash-6.0.2-py3-none-any.whl", hash = "sha256:6d3ce5cbbc8ca3533c12782ac201c2ec756d1e1703ec3efc88f2b95d1ed2bb31"}, - {file = "pydash-6.0.2.tar.gz", hash = "sha256:35caa588e01d293713655e0870544d25128cd414c5e19477a0d63adc2b2ca03e"}, -] - -[package.extras] -dev = ["Sphinx", "black", "build", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "importlib-metadata (<5)", "invoke", "isort", "pylint", "pytest", "pytest-cov", "sphinx-rtd-theme", "tox", "twine", "wheel"] - -[[package]] -name = "pygithub" -version = "2.7.0" -description = "Use the full Github API v3" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygithub-2.7.0-py3-none-any.whl", hash = "sha256:40ecbfe26dc55cc34ab4b0ffa1d455e6f816ef9a2bc8d6f5ad18ce572f163700"}, - {file = "pygithub-2.7.0.tar.gz", hash = "sha256:7cd6eafabb09b5369afba3586d86b1f1ad6f1326d2ff01bc47bb26615dce4cbb"}, -] - -[package.dependencies] -pyjwt = {version = ">=2.4.0", extras = ["crypto"]} -pynacl = ">=1.4.0" -requests = ">=2.14.0" -typing-extensions = ">=4.5.0" -urllib3 = ">=1.26.0" - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.10.1" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, - {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, -] - -[package.dependencies] -cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} - -[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", "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 = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "pyparsing" -version = "3.2.3" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, - {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyrsistent" -version = "0.20.0" -description = "Persistent/Functional/Immutable data structures" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34"}, - {file = "pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f"}, - {file = "pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7"}, - {file = "pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a"}, - {file = "pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656"}, - {file = "pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee"}, - {file = "pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3"}, - {file = "pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174"}, - {file = "pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d"}, - {file = "pyrsistent-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:79ed12ba79935adaac1664fd7e0e585a22caa539dfc9b7c7c6d5ebf91fb89054"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f920385a11207dc372a028b3f1e1038bb244b3ec38d448e6d8e43c6b3ba20e98"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f5c2d012671b7391803263419e31b5c7c21e7c95c8760d7fc35602353dee714"}, - {file = "pyrsistent-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3992833fbd686ee783590639f4b8343a57f1f75de8633749d984dc0eb16c86"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win32.whl", hash = "sha256:881bbea27bbd32d37eb24dd320a5e745a2a5b092a17f6debc1349252fac85423"}, - {file = "pyrsistent-0.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:6d270ec9dd33cdb13f4d62c95c1a5a50e6b7cdd86302b494217137f760495b9d"}, - {file = "pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022"}, - {file = "pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f"}, - {file = "pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf"}, - {file = "pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b"}, - {file = "pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4"}, -] - -[[package]] -name = "pysnooper" -version = "1.2.3" -description = "A poor man's debugger for Python." -optional = false -python-versions = "*" -files = [ - {file = "PySnooper-1.2.3-py2.py3-none-any.whl", hash = "sha256:546372f0e72da89f8d1b89e758b7c05a478d65288569a1ca2cc1620e7b1b1944"}, - {file = "pysnooper-1.2.3.tar.gz", hash = "sha256:1fa1425444a7af45108aaed860b5ca8b62b25bba25b0b037c059ba353d8f1e74"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pytest" -version = "8.4.1" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-mock" -version = "3.14.1" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, - {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2025.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "requests" -version = "2.32.5" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.9" -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rsa" -version = "4.9.1" -description = "Pure-Python RSA implementation" -optional = false -python-versions = "<4,>=3.6" -files = [ - {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, - {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "ruamel-yaml" -version = "0.18.15" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701"}, - {file = "ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700"}, -] - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""} - -[package.extras] -docs = ["mercurial (>5.7)", "ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.12" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -optional = false -python-versions = ">=3.9" -files = [ - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, - {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, -] - -[[package]] -name = "semver" -version = "3.0.4" -description = "Python helper for Semantic Versioning (https://semver.org)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, - {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, -] - -[[package]] -name = "sentry-sdk" -version = "1.45.1" -description = "Python client for Sentry (https://sentry.io)" -optional = false -python-versions = "*" -files = [ - {file = "sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1"}, - {file = "sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26"}, -] - -[package.dependencies] -certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} - -[package.extras] -aiohttp = ["aiohttp (>=3.5)"] -arq = ["arq (>=0.23)"] -asyncpg = ["asyncpg (>=0.23)"] -beam = ["apache-beam (>=2.12)"] -bottle = ["bottle (>=0.12.13)"] -celery = ["celery (>=3)"] -celery-redbeat = ["celery-redbeat (>=2)"] -chalice = ["chalice (>=1.16.0)"] -clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] -django = ["django (>=1.8)"] -falcon = ["falcon (>=1.4)"] -fastapi = ["fastapi (>=0.79.0)"] -flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] -httpx = ["httpx (>=0.16.0)"] -huey = ["huey (>=2)"] -loguru = ["loguru (>=0.5)"] -openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] -opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] -pymongo = ["pymongo (>=3.1)"] -pyspark = ["pyspark (>=2.4.4)"] -quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] -rq = ["rq (>=0.6)"] -sanic = ["sanic (>=0.8)"] -sqlalchemy = ["sqlalchemy (>=1.2)"] -starlette = ["starlette (>=0.19.1)"] -starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "slack-sdk" -version = "3.36.0" -description = "The Slack API Platform SDK for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "slack_sdk-3.36.0-py2.py3-none-any.whl", hash = "sha256:6c96887d7175fc1b0b2777b73bb65f39b5b8bee9bd8acfec071d64014f9e2d10"}, - {file = "slack_sdk-3.36.0.tar.gz", hash = "sha256:8586022bdbdf9f8f8d32f394540436c53b1e7c8da9d21e1eab4560ba70cfcffa"}, -] - -[package.extras] -optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<16)"] - -[[package]] -name = "smmap" -version = "5.0.2" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - -[[package]] -name = "soupsieve" -version = "2.7" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -files = [ - {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, - {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, -] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "tzdata" -version = "2025.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, - {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.11" -content-hash = "468237967c29b95282f2be2fe3b1f49d09a9939353db086e1f1f3184265f9e81" diff --git a/airbyte-ci/connectors/metadata_service/lib/pyproject.toml b/airbyte-ci/connectors/metadata_service/lib/pyproject.toml deleted file mode 100644 index 90768fa94571..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/pyproject.toml +++ /dev/null @@ -1,56 +0,0 @@ -[tool.poetry] -name = "metadata-service" -version = "0.28.1" -description = "" -authors = ["Airbyte "] -readme = "README.md" -packages = [{ include = "metadata_service" }] - -[tool.poetry.dependencies] -python = "^3.11" -pydantic = "^1.10.6" -click = "^8.1.3" -google = "^3.0.0" -pyyaml = "^6.0" -gcloud = "^0.18.3" -google-cloud-storage = "^2.8.0" -pydash = "^6.0.2" -semver = "^3.0.1" -gitpython = "^3.1.40" -pygithub = "^2.7.0" -pandas = "^2.3.1" -tabulate = "^0.9.0" -slack-sdk = "^3.36.0" -dpath = "^2.2.0" -sentry-sdk = "^1.28.1" -jinja2 = "^3.1.2" - - -[tool.poetry.group.dev.dependencies] -pytest = "^8" -datamodel-code-generator = "^0.17.1" -pytest-mock = "^3.10.0" -poethepoet = "^0.20.0" - -[tool.poetry.scripts] -metadata_service = "metadata_service.commands:metadata_service" - -[tool.poe.tasks] -generate-models = { shell = "npm ci --silent && ./bin/generate-metadata-models.sh" } -replicate-prod = "gsutil -m rsync -r -d gs://prod-airbyte-cloud-connector-metadata-service gs://$TARGET_BUCKET" -copy-connector-from-prod = "gsutil -m rsync -r -d gs://prod-airbyte-cloud-connector-metadata-service/metadata/$CONNECTOR/$VERSION gs://$TARGET_BUCKET/metadata/$CONNECTOR/$VERSION" -promote-connector-to-latest = "gsutil -m rsync -r -d gs://$TARGET_BUCKET/metadata/$CONNECTOR/$VERSION gs://$TARGET_BUCKET/metadata/$CONNECTOR/latest" -test = "pytest tests" - -[tool.airbyte_ci] -python_versions = ["3.11"] -optional_poetry_groups = ["dev"] -poe_tasks = ["test"] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - -[tool.ruff] -target-version = "py311" -line-length = 140 diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/conftest.py b/airbyte-ci/connectors/metadata_service/lib/tests/conftest.py deleted file mode 100644 index ba779e55c2eb..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/conftest.py +++ /dev/null @@ -1,25 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import pytest - -pytest_plugins = [ - "tests.fixtures", -] - - -def pytest_addoption(parser): - parser.addoption("--skipslow", action="store_true", default=False, help="skip slow tests") - - -def pytest_configure(config): - config.addinivalue_line("markers", "slow: mark test as slow to run") - - -def pytest_collection_modifyitems(config, items): - if config.getoption("--skipslow"): - skip_slow = pytest.mark.skip(reason="--skipslow option has been provided and this test is marked as slow") - for item in items: - if "slow" in item.keywords: - item.add_marker(skip_slow) diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/__init__.py b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/__init__.py deleted file mode 100644 index 71d7203499ae..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -import os -from typing import Callable, List - -import pytest -from metadata_service.constants import DOC_FILE_NAME - - -def list_all_paths_in_fixture_directory(folder_name: str) -> List[str]: - file_path = os.path.join(os.path.dirname(__file__), folder_name) - - # If folder_name has subdirectories, os.walk will return a list of tuples, - # one for folder_name and one for each of its subdirectories. - fixture_files = [] - for root, dirs, files in os.walk(file_path): - fixture_files.extend(os.path.join(root, file_name) for file_name in files) - return fixture_files - - -@pytest.fixture(scope="session") -def valid_metadata_yaml_files() -> List[str]: - files = list_all_paths_in_fixture_directory("metadata_validate/valid") - if not files: - pytest.fail("No files found in metadata_validate/valid") - return files - - -@pytest.fixture(scope="session") -def invalid_metadata_yaml_files() -> List[str]: - files = list_all_paths_in_fixture_directory("metadata_validate/invalid") - if not files: - pytest.fail("No files found in metadata_validate/invalid") - return files - - -@pytest.fixture(scope="session") -def valid_metadata_upload_files() -> List[str]: - files = list_all_paths_in_fixture_directory("metadata_upload/valid") - if not files: - pytest.fail("No files found in metadata_upload/valid") - return files - - -@pytest.fixture(scope="session") -def invalid_metadata_upload_files() -> List[str]: - files = list_all_paths_in_fixture_directory("metadata_upload/invalid") - if not files: - pytest.fail("No files found in metadata_upload/invalid") - return files - - -@pytest.fixture(scope="session") -def get_fixture_path() -> Callable[[str], str]: - def _get_fixture_path(fixture_name: str) -> str: - return os.path.join(os.path.dirname(__file__), fixture_name) - - return _get_fixture_path diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/doc.md b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/doc.md deleted file mode 100644 index 3ee6db6d8d87..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/doc.md +++ /dev/null @@ -1 +0,0 @@ -# The test doc for metadata_validate diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_cloud_repo_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_cloud_repo_does_not_exist.yaml deleted file mode 100644 index 5870cbd3c1f4..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_cloud_repo_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/does-not-exist-4 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_main_repo_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_main_repo_does_not_exist.yaml deleted file mode 100644 index ef1221d99316..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_main_repo_does_not_exist.yaml +++ /dev/null @@ -1,23 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/does-not-exist-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_normalization_repo_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_normalization_repo_does_not_exist.yaml deleted file mode 100644 index 6c1d49293fa1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_normalization_repo_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/does-not-exist-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_oss_repo_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_oss_repo_does_not_exist.yaml deleted file mode 100644 index 61c907851da8..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/repo_nonexistent/metadata_oss_repo_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/does-not-exist-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_breaking_change_image_tag_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_breaking_change_image_tag_does_not_exist.yaml deleted file mode 100644 index f646f8eb2995..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_breaking_change_image_tag_does_not_exist.yaml +++ /dev/null @@ -1,25 +0,0 @@ -data: - connectorSubtype: api - connectorType: source - definitionId: bb6afd81-87d5-47e3-97c4-e2c2901b1cf8 - dockerImageTag: 0.0.1 - dockerRepository: airbyte/image-exists1 - githubIssueLabel: source-onesignal - icon: onesignal.svg - license: MIT - name: OneSignal - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: alpha - releases: - breakingChanges: - 0.0.0: # tag does not exist - upgradeDeadline: 2023-08-22 - message: "This version made a change." - documentationUrl: https://docs.airbyte.com/integrations/sources/onesignal - tags: - - language:python -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_cloud_image_tag_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_cloud_image_tag_does_not_exist.yaml deleted file mode 100644 index a41bb35cf8ea..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_cloud_image_tag_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 99.99.99 # tag does not exist - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_main_image_tag_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_main_image_tag_does_not_exist.yaml deleted file mode 100644 index 1007338c64fb..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_main_image_tag_does_not_exist.yaml +++ /dev/null @@ -1,23 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 99.99.99 # tag does not exist - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_normalization_image_tag_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_normalization_image_tag_does_not_exist.yaml deleted file mode 100644 index 7c578cf41a19..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_normalization_image_tag_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 99.99.99 # tag does not exist - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_oss_repo_does_not_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_oss_repo_does_not_exist.yaml deleted file mode 100644 index 29e02dd7d39c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/tag_nonexistent/metadata_oss_repo_does_not_exist.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 99.99.99 # tag does not exist - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_image_tag_does_not_exist_but_is_overrode.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_image_tag_does_not_exist_but_is_overrode.yaml deleted file mode 100644 index 53c80d360c04..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_image_tag_does_not_exist_but_is_overrode.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 99.99.99 # tag does not exist - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_repo_does_not_exist_but_is_overrode.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_repo_does_not_exist_but_is_overrode.yaml deleted file mode 100644 index 95ad1661b3b3..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/invalid/referenced_image_not_in_dockerhub/valid_overrides_but_image_nonexistent/metadata_main_repo_does_not_exist_but_is_overrode.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/does-not-exist-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist.yaml deleted file mode 100644 index 7d1f5ef5da72..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist.yaml +++ /dev/null @@ -1,32 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - registryOverrides: - cloud: - enabled: true - dockerRepository: airbyte/exists-3 - dockerImageTag: 0.0.1 - oss: - enabled: true - dockerRepository: airbyte/exists-4 - dockerImageTag: 0.0.1 - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version made a change." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides.yaml deleted file mode 100644 index d2067be338f0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides_with_normalization.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides_with_normalization.yaml deleted file mode 100644 index 74be91d596ef..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_all_images_exist_no_overrides_with_normalization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - normalizationConfig: - normalizationIntegrationType: postgres - normalizationRepository: airbyte/exists-2 - normalizationTag: 0.0.1 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_base_image_exists.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_base_image_exists.yaml deleted file mode 100644 index 0189b45a2d51..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_base_image_exists.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - ab_internal: - ql: 400 - sl: 200 - allowedHosts: - hosts: - - zopim.com - connectorBuildOptions: - baseImage: docker.io/airbyte/base-repo-exists:1.1.0@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c - connectorSubtype: api - connectorType: source - definitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 - dockerImageTag: 0.2.1 - dockerRepository: airbyte/source-exists-1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - githubIssueLabel: source-alloy-db - icon: alloy-db.svg - license: MIT - name: AlloyDB for PostgreSQL - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - supportLevel: certified - tags: - - language:python -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_release_candidate.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_release_candidate.yaml deleted file mode 100644 index a95f7ae31912..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_upload/valid/referenced_image_in_dockerhub/metadata_release_candidate.yaml +++ /dev/null @@ -1,24 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.1.0-rc.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - rolloutConfiguration: - enableProgressiveRollout: true - initialPercentage: 5 - maxPercentage: 50 - advanceDelayMinutes: 60 - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_extra_data.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_extra_data.yaml deleted file mode 100644 index d412853bb354..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_extra_data.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - someUnknownField: someUnknownValue - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_major_bump_no_breaking_changes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_major_bump_no_breaking_changes.yaml deleted file mode 100644 index 9d26a7480bed..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_major_bump_no_breaking_changes.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: Low-code - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 1.0.0 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_progressive_rollout_no_rc_suffix.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_progressive_rollout_no_rc_suffix.yaml deleted file mode 100644 index 2e68d16a48ab..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_progressive_rollout_no_rc_suffix.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - rolloutConfiguration: - enableProgressiveRollout: true - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_for_major_version.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_for_major_version.yaml deleted file mode 100644 index 7da17b70363b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_for_major_version.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 3.0.0-rc.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - rolloutConfiguration: - enableProgressiveRollout: true - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_no_progressive_rollout.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_no_progressive_rollout.yaml deleted file mode 100644 index 9917b66596fc..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/metadata_release_candidate_no_progressive_rollout.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1-rc.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.json b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.json deleted file mode 100644 index ad869b38385f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.json +++ /dev/null @@ -1 +0,0 @@ -asdsad diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.yaml deleted file mode 100644 index ad869b38385f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/nonsense.yaml +++ /dev/null @@ -1 +0,0 @@ -asdsad diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_digest_does_not_exists.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_digest_does_not_exists.yaml deleted file mode 100644 index 57f5418bc40d..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_digest_does_not_exists.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - ab_internal: - ql: 400 - sl: 200 - allowedHosts: - hosts: - - zopim.com - connectorBuildOptions: - baseImage: docker.io/airbyte/base-repo-exists:1.1.0@sha256:MISSINGSHA - connectorSubtype: api - connectorType: source - definitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 - dockerImageTag: 0.2.1 - dockerRepository: airbyte/source-exists-1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - githubIssueLabel: source-alloy-db - icon: alloy-db.svg - license: MIT - name: AlloyDB for PostgreSQL - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - supportLevel: certified - tags: - - language:python -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_name_does_not_exists.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_name_does_not_exists.yaml deleted file mode 100644 index fdefc79a9b1b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_name_does_not_exists.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - ab_internal: - ql: 400 - sl: 200 - allowedHosts: - hosts: - - zopim.com - connectorBuildOptions: - baseImage: docker.io/airbyte/foobar-connector-base:1.1.0@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c - connectorSubtype: api - connectorType: source - definitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 - dockerImageTag: 0.2.1 - dockerRepository: airbyte/source-exists-1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - githubIssueLabel: source-alloy-db - icon: alloy-db.svg - license: MIT - name: AlloyDB for PostgreSQL - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - supportLevel: certified - tags: - - language:python -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_tag_does_not_exists.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_tag_does_not_exists.yaml deleted file mode 100644 index ba84e959a984..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_base_image_tag_does_not_exists.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - ab_internal: - ql: 400 - sl: 200 - allowedHosts: - hosts: - - zopim.com - connectorBuildOptions: - baseImage: docker.io/airbyte/base-repo-exists:99.99.99@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c - connectorSubtype: api - connectorType: source - definitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 - dockerImageTag: 0.2.1 - dockerRepository: airbyte/source-exists-1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - githubIssueLabel: source-alloy-db - icon: alloy-db.svg - license: MIT - name: AlloyDB for PostgreSQL - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - supportLevel: certified - tags: - - language:python -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_build_base_image_wrong_type.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_build_base_image_wrong_type.yaml deleted file mode 100644 index b58cb21f304b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_build_base_image_wrong_type.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - allowedHosts: - hosts: - - "*.googleapis.com" - connectorBuildOptions: - unexpectedField: additionalProperties are not allowed ('unexpectedField' was unexpected) - connectorSubtype: file - connectorType: source - definitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 - dockerImageTag: 0.3.7 - dockerRepository: airbyte/source-google-sheets - githubIssueLabel: source-google-sheets - icon: google-sheets.svg - license: Elv2 - name: Google Sheets - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets - tags: - - language:python - ab_internal: - sl: 300 - ql: 400 - supportLevel: certified -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_invalid_base_image_no_sha.yml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_invalid_base_image_no_sha.yml deleted file mode 100644 index 91de8734a4bf..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/connector_build_options_invalid/metadata_invalid_base_image_no_sha.yml +++ /dev/null @@ -1,29 +0,0 @@ -data: - allowedHosts: - hosts: - - "*.googleapis.com" - connectorBuildOptions: - baseImage: docker.io/airbyte/base-repo-exists:1.1.0 - connectorSubtype: file - connectorType: source - definitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 - dockerImageTag: 0.3.7 - dockerRepository: airbyte/source-google-sheets - githubIssueLabel: source-google-sheets - icon: google-sheets.svg - license: Elv2 - name: Google Sheets - registries: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets - tags: - - language:python - ab_internal: - sl: 300 - ql: 400 - supportLevel: certified -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_internal_fields.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_internal_fields.yaml deleted file mode 100644 index db4b261bd977..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_internal_fields.yaml +++ /dev/null @@ -1,17 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - ab_internal: - sl: 299 - ql: 699 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_remote_registries.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_remote_registries.yaml deleted file mode 100644 index 1e1d095e6333..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_remote_registries.yaml +++ /dev/null @@ -1,16 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - remoteRegistries: - maven: enabled - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_support_refreshes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_support_refreshes.yaml deleted file mode 100644 index 4e66c6d592ff..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_invalid_support_refreshes.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - supportsRefreshes: 123 - license: MIT - tags: - - language:java \ No newline at end of file diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_unknown_support_level.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_unknown_support_level.yaml deleted file mode 100644 index 9fe7b5ededaf..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_unknown_support_level.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - supportLevel: dne - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_wrong_language_remote_registries.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_wrong_language_remote_registries.yaml deleted file mode 100644 index c77bfde70bd4..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/metadata_wrong_language_remote_registries.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - remoteRegistries: - pypi: - enabled: true - packageName: airbyte-source-alloydb-strict-encrypt - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_id_override.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_id_override.yaml deleted file mode 100644 index f6a46e1870e2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_id_override.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - registryOverrides: - oss: - enabled: true - definitionId: woohoo - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_type_override.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_type_override.yaml deleted file mode 100644 index 6904ca3426e5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_no_type_override.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - registryOverrides: - oss: - enabled: true - connectorType: destination - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_unknown_override.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_unknown_override.yaml deleted file mode 100644 index a6e7f18d73c5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/overrides_invalid/metadata_registry_unknown_override.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - registryOverrides: - oss: - enabled: true - what: is this? - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_empty_impacted_stream.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_empty_impacted_stream.yaml deleted file mode 100644 index 3351eb5ddee5..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_empty_impacted_stream.yaml +++ /dev/null @@ -1,23 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - scopedImpact: - - scopeType: stream - impactedScopes: [] - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_impact_scopes_unknown_scope_type.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_impact_scopes_unknown_scope_type.yaml deleted file mode 100644 index 9b4176acfd5c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/impact_scopes_invalid/metadata_breaking_changes_impact_scopes_unknown_scope_type.yaml +++ /dev/null @@ -1,23 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - scopedImpact: - - type: foo - impactedScopes: ["bar"] - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_change_versions_under_releases.yml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_change_versions_under_releases.yml deleted file mode 100644 index 27e2c17d1abf..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_change_versions_under_releases.yml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/alloydb - connectorSubtype: database - releaseStage: generally_available - license: MIT - releasestests/fixtures/metadata_validate/invalid/metadata_breaking_change_versions_under_releases.yml: - 2.1.3: - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - upgradeDeadline: 2023-08-22 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_changes_not_under_releases.yml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_changes_not_under_releases.yml deleted file mode 100644 index e2b9918cccf0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_breaking_changes_not_under_releases.yml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/alloydb - connectorSubtype: database - releaseStage: generally_available - license: MIT - breakingChanges: - 2.1.3: - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - upgradeDeadline: 2023-08-22 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_additional_property.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_additional_property.yaml deleted file mode 100644 index 03f3579470fa..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_additional_property.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - addition: "hi" - 2.1.3: - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - upgradeDeadline: 2023-08-22 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_invalid_deadline.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_invalid_deadline.yaml deleted file mode 100644 index a4b3891435b1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_invalid_deadline.yaml +++ /dev/null @@ -1,19 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 1.2.3: - upgradeDeadline: 2023-08-22-11 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_deadline.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_deadline.yaml deleted file mode 100644 index 458fdccce291..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_deadline.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 1.2.3: - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_message.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_message.yaml deleted file mode 100644 index 73572c41a5d6..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_no_message.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.1.3: - upgradeDeadline: 2023-08-22 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_version.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_version.yaml deleted file mode 100644 index 76c15e44d7c2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_invalid_breaking_change_version.yaml +++ /dev/null @@ -1,19 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - v2.1.3: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_breaking_change_entry_for_version.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_breaking_change_entry_for_version.yaml deleted file mode 100644 index a7d4e65e96cd..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_breaking_change_entry_for_version.yaml +++ /dev/null @@ -1,25 +0,0 @@ -metadataSpecVersion: "1.0" -data: - connectorSubtype: api - connectorType: source - definitionId: bb6afd81-87d5-47e3-97c4-e2c2901b1cf8 - dockerImageTag: 2.0.0 - dockerRepository: airbyte/source-onesignal - githubIssueLabel: source-onesignal - icon: onesignal.svg - license: MIT - name: OneSignal - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: alpha - releases: - breakingChanges: - 1.0.0: - upgradeDeadline: 2023-08-22 - message: "This version made a change." - documentationUrl: https://docs.airbyte.com/integrations/sources/onesignal - tags: - - language:python diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_releases.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_releases.yaml deleted file mode 100644 index 65ab50b555fe..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/optional_top_level_property_invalid/releases_invalid/metadata_major_version_no_releases.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadataSpecVersion: "1.0" -data: - connectorSubtype: api - connectorType: source - definitionId: bb6afd81-87d5-47e3-97c4-e2c2901b1cf8 - dockerImageTag: 2.0.0 - dockerRepository: airbyte/source-onesignal - githubIssueLabel: source-onesignal - icon: onesignal.svg - license: MIT - name: OneSignal - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: alpha - documentationUrl: https://docs.airbyte.com/integrations/sources/onesignal - tags: - - language:python diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_empty_tags.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_empty_tags.yaml deleted file mode 100644 index 90f558ad64e3..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_empty_tags.yaml +++ /dev/null @@ -1,13 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/source-alloydb-strict-encrypt - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: [] diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_invalid_tag_format.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_invalid_tag_format.yaml deleted file mode 100644 index f5416208fa3e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_invalid_tag_format.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/source-alloydb-strict-encrypt - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - notkv diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_no_lang_tag.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_no_lang_tag.yaml deleted file mode 100644 index a065f3e742c7..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_invalid/tags_invalid/metadata_no_lang_tag.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/source-alloydb-strict-encrypt - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - keyword:test diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_connector_type.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_connector_type.yaml deleted file mode 100644 index 3222f6bdd974..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_connector_type.yaml +++ /dev/null @@ -1,13 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_definition_id.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_definition_id.yaml deleted file mode 100644 index b1eb9f113e32..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_definition_id.yaml +++ /dev/null @@ -1,17 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - connectorType: source - githubIssueLabel: source-alloydb-strict-encrypt - dockerRepository: airbyte/image-exists-1 - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_image_tag.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_image_tag.yaml deleted file mode 100644 index 28b6807ee54a..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_image_tag.yaml +++ /dev/null @@ -1,17 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - githubIssueLabel: source-alloydb-strict-encrypt - dockerRepository: airbyte/image-exists-1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_repo.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_repo.yaml deleted file mode 100644 index 5bc5ca48cdd8..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_docker_repo.yaml +++ /dev/null @@ -1,17 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_tags.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_tags.yaml deleted file mode 100644 index 4b704e001f8c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_tags.yaml +++ /dev/null @@ -1,12 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/source-alloydb-strict-encrypt - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_version.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_version.yaml deleted file mode 100644 index 8142a7133c5c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/invalid/required_top_level_property_missing/metadata_missing_version.yaml +++ /dev/null @@ -1,17 +0,0 @@ -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_declarative_manifest_major_bump_no_breaking_change.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_declarative_manifest_major_bump_no_breaking_change.yaml deleted file mode 100644 index cde8a6cedd85..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_declarative_manifest_major_bump_no_breaking_change.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: Low-code - definitionId: 64a2f99c-542f-4af8-9a6f-355f1217b436 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 1.0.0 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_simple.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_simple.yaml deleted file mode 100644 index c8bd4e079df0..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/metadata_simple.yaml +++ /dev/null @@ -1,14 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_build_base_image.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_build_base_image.yaml deleted file mode 100644 index 1eb69afb6214..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_build_base_image.yaml +++ /dev/null @@ -1,29 +0,0 @@ -data: - allowedHosts: - hosts: - - "*.googleapis.com" - connectorBuildOptions: - baseImage: docker.io/airbyte/base-repo-exists:1.1.0@sha256:bd98f6505c6764b1b5f99d3aedc23dfc9e9af631a62533f60eb32b1d3dbab20c - connectorSubtype: file - connectorType: source - definitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 - dockerImageTag: 0.3.7 - dockerRepository: airbyte/source-google-sheets - githubIssueLabel: source-google-sheets - icon: google-sheets.svg - license: Elv2 - name: Google Sheets - registryOverrides: - cloud: - enabled: true - oss: - enabled: true - releaseStage: generally_available - documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets - tags: - - language:python - ab_internal: - sl: 300 - ql: 400 - supportLevel: certified -metadataSpecVersion: "1.0" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_internal_fields.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_internal_fields.yaml deleted file mode 100644 index 2d788f819f7b..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_internal_fields.yaml +++ /dev/null @@ -1,17 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - ab_internal: - sl: 200 - ql: 600 - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_allowed_hosts.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_allowed_hosts.yaml deleted file mode 100644 index fe9279d3c8de..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_allowed_hosts.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_required_resources.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_required_resources.yaml deleted file mode 100644 index a49ca2841bed..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_registry_required_resources.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - resourceRequirements: - jobSpecific: - - jobType: sync - resourceRequirements: - memory_request: 1Gi - memory_limit: 1Gi - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_remote_registries.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_remote_registries.yaml deleted file mode 100644 index b942106789e2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_remote_registries.yaml +++ /dev/null @@ -1,18 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - remoteRegistries: - pypi: - enabled: true - packageName: airbyte-source-alloydb-strict-encrypt - license: MIT - tags: - - language:python diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level.yaml deleted file mode 100644 index 0fe30754f681..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - supportLevel: community - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level_archived.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level_archived.yaml deleted file mode 100644 index 99486312e56c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_level_archived.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - supportLevel: archived - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_refreshes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_refreshes.yaml deleted file mode 100644 index 13174a76e3ee..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/metadata_support_refreshes.yaml +++ /dev/null @@ -1,15 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - supportsRefreshes: true - license: MIT - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_complex_override.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_complex_override.yaml deleted file mode 100644 index 4d0d8d7dc110..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_complex_override.yaml +++ /dev/null @@ -1,31 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - registryOverrides: - oss: - enabled: true - cloud: - enabled: true - name: "NEWNAME" - dockerRepository: airbyte/source-alloydb - resourceRequirements: - jobSpecific: - - jobType: sync - resourceRequirements: - memory_request: 1Gi - memory_limit: 1Gi - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_enabled.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_enabled.yaml deleted file mode 100644 index 81f17b894eef..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_overrides/metadata_registry_enabled.yaml +++ /dev/null @@ -1,21 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - allowedHosts: - hosts: - - "${host}" - - "${tunnel_method.tunnel_host}" - registryOverrides: - oss: - enabled: true - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_change_prerelease.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_change_prerelease.yaml deleted file mode 100644 index bc356503f1ce..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_change_prerelease.yaml +++ /dev/null @@ -1,19 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.0-preview.cf3628c - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes.yaml deleted file mode 100644 index 230bbf43d09c..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes.yaml +++ /dev/null @@ -1,19 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_impact_scopes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_impact_scopes.yaml deleted file mode 100644 index 215d8f18c578..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_impact_scopes.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - scopedImpact: - - scopeType: stream - impactedScopes: ["affected_stream"] - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_migration_doc_url.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_migration_doc_url.yaml deleted file mode 100644 index 4745efd02a9f..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_migration_doc_url.yaml +++ /dev/null @@ -1,21 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - migrationDocumentationUrl: https://docs.airbyte.com/integrations/sources/existingsource-migrations - breakingChanges: - 2.0.0: - migrationDocumentationUrl: https://docs.airbyte.com/integrations/sources/existingsource-migrations#2.0.0 - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_multipel_impact_scopes.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_multipel_impact_scopes.yaml deleted file mode 100644 index 0c19362d12e1..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_breaking_changes_with_multipel_impact_scopes.yaml +++ /dev/null @@ -1,24 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - scopedImpact: - - scopeType: stream - impactedScopes: ["affected_stream", "one_more"] - - scopeType: stream - impactedScopes: ["another_affected_stream"] - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_major_version_with_breaking_change_for_version.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_major_version_with_breaking_change_for_version.yaml deleted file mode 100644 index 515df162bebb..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_major_version_with_breaking_change_for_version.yaml +++ /dev/null @@ -1,19 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.0 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_disable_progressive_rollout.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_disable_progressive_rollout.yaml deleted file mode 100644 index 684d168789aa..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_disable_progressive_rollout.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1-rc.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - rolloutConfiguration: - enableProgressiveRollout: false - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_enable_progressive_rollout.yaml b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_enable_progressive_rollout.yaml deleted file mode 100644 index 36debbafbe6e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/metadata_validate/valid/with_optional_field/with_releases/metadata_release_candidate_enable_progressive_rollout.yaml +++ /dev/null @@ -1,22 +0,0 @@ -metadataSpecVersion: 1.0 -data: - name: AlloyDB for PostgreSQL - definitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 - connectorType: source - dockerRepository: airbyte/image-exists-1 - githubIssueLabel: source-alloydb-strict-encrypt - dockerImageTag: 2.0.1-rc.1 - documentationUrl: https://docs.airbyte.com/integrations/sources/existingsource - connectorSubtype: database - releaseStage: generally_available - license: MIT - releases: - rolloutConfiguration: - enableProgressiveRollout: true - breakingChanges: - 2.0.0: - upgradeDeadline: 2023-08-22 - message: "This version changes the connector’s authentication method from `ApiKey` to `oAuth`, per the [API guide](https://amazon-sqs.com/api/someguide)." - - tags: - - language:java diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/stale_metadata_report_fixtures.py b/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/stale_metadata_report_fixtures.py deleted file mode 100644 index b2420fc91819..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/fixtures/stale_metadata_report_fixtures.py +++ /dev/null @@ -1,128 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -from unittest.mock import Mock - -import pytest -import yaml - - -@pytest.fixture -def mock_github_files(): - """Create mock GitHub file objects for testing.""" - mock_file_1 = Mock() - mock_file_1.type = "file" - mock_file_1.name = "metadata.yaml" - mock_file_1.path = "airbyte-integrations/connectors/source-test-1/metadata.yaml" - mock_file_1.download_url = "https://github.com/connector-1/metadata.yaml" - - mock_file_2 = Mock() - mock_file_2.type = "file" - mock_file_2.name = "metadata.yaml" - mock_file_2.path = "airbyte-integrations/connectors/source-test-2/metadata.yaml" - mock_file_2.download_url = "https://github.com/connector-2/metadata.yaml" - - mock_file_3 = Mock() - mock_file_3.type = "file" - mock_file_3.name = "metadata.yaml" - mock_file_3.path = "airbyte-integrations/connectors/source-test-3/metadata.yaml" - mock_file_3.download_url = "https://github.com/connector-3/metadata.yaml" - - return [mock_file_1, mock_file_2, mock_file_3] - - -@pytest.fixture -def mock_yaml_responses(): - """Create mock YAML responses for different test scenarios.""" - return { - "https://github.com/connector-1/metadata.yaml": """ -metadataSpecVersion: "1.0" -data: - name: "Test Source 1" - definitionId: "12345678-1234-1234-1234-123456789abc" - connectorType: "source" - dockerRepository: "airbyte/source-test-1" - dockerImageTag: "1.0.0" - license: "MIT" - documentationUrl: "https://docs.airbyte.com/integrations/sources/test-1" - githubIssueLabel: "source-test-1" - connectorSubtype: "api" - releaseStage: "alpha" - supportLevel: "certified" -""", - "https://github.com/connector-2/metadata.yaml": """ -metadataSpecVersion: "1.0" -data: - name: "Test Source 2" - definitionId: "12345678-1234-1234-1234-123456789abd" - connectorType: "source" - dockerRepository: "airbyte/source-test-2" - dockerImageTag: "1.0.0-rc" - license: "MIT" - documentationUrl: "https://docs.airbyte.com/integrations/sources/test-2" - githubIssueLabel: "source-test-2" - connectorSubtype: "api" - releaseStage: "alpha" - supportLevel: "community" -""", - "https://github.com/connector-3/metadata.yaml": """ -metadataSpecVersion: "1.0" -data: - name: "Test Source 3" - definitionId: "12345678-1234-1234-1234-123456789abe" - connectorType: "source" - dockerRepository: "airbyte/source-test-3" - dockerImageTag: "2.0.0" - license: "MIT" - documentationUrl: "https://docs.airbyte.com/integrations/sources/test-3" - githubIssueLabel: "source-test-3" - connectorSubtype: "api" - releaseStage: "alpha" - supportLevel: "archived" -""", - } - - -@pytest.fixture -def mock_gcs_blobs(): - """Create mock GCS blob objects for testing.""" - - def _create_mock_blob(repo, tag): - blob = Mock() - metadata = { - "metadataSpecVersion": "1.0", - "data": { - "name": f"Test {repo.split('/')[-1].replace('-', ' ').title()}", - "definitionId": "12345678-1234-1234-1234-123456789000", - "connectorType": "source" if "source" in repo else "destination", - "dockerRepository": repo, - "dockerImageTag": tag, - "license": "MIT", - "documentationUrl": f"https://docs.airbyte.com/integrations/{repo}", - "githubIssueLabel": repo.split("/")[-1], - "connectorSubtype": "api", - "releaseStage": "alpha", - "supportLevel": "certified", - }, - } - blob.download_as_bytes.return_value = yaml.dump(metadata).encode("utf-8") - return blob - - mock_blob_1 = _create_mock_blob("airbyte/source-gcs-1", "1.2.0") - mock_blob_2 = _create_mock_blob("airbyte/source-gcs-2", "2.1.0") - mock_blob_3 = _create_mock_blob("airbyte/destination-gcs-1", "3.0.0") - - return [mock_blob_1, mock_blob_2, mock_blob_3] - - -@pytest.fixture -def large_dataset_github_mappings(): - """Create large GitHub version mappings for performance testing.""" - return {f"airbyte/connector-{i:04d}": f"{i % 10}.{i % 5}.{i % 3}" for i in range(500)} - - -@pytest.fixture -def large_dataset_gcs_mappings(): - """Create large GCS version mappings for performance testing.""" - return {f"airbyte/connector-{i:04d}": f"{(i - 1) % 10}.{i % 5}.{i % 3}" for i in range(500)} diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/helpers/test_slack.py b/airbyte-ci/connectors/metadata_service/lib/tests/helpers/test_slack.py deleted file mode 100644 index c19f2b6d7d7e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/helpers/test_slack.py +++ /dev/null @@ -1,86 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -from unittest.mock import Mock, patch - -import pytest - -from metadata_service.helpers.slack import send_slack_message - - -@pytest.mark.parametrize( - "slack_token,should_send,description", - [ - ("xoxb-test-token", True, "token present - should send"), - (None, False, "no token - should not send"), - ], -) -def test_send_slack_message_environment_conditions(monkeypatch, slack_token, should_send, description): - """Test send_slack_message behavior with different SLACK_TOKEN configurations.""" - if slack_token: - monkeypatch.setenv("SLACK_TOKEN", slack_token) - else: - monkeypatch.delenv("SLACK_TOKEN", raising=False) - - with patch("metadata_service.helpers.slack.WebClient") as mock_webclient_class: - mock_webclient = Mock() - mock_webclient.chat_postMessage = Mock() - mock_webclient_class.return_value = mock_webclient - - channel = "#test-channel" - message = "Test message" - - success, error_msg = send_slack_message(channel, message) - - if should_send: - mock_webclient_class.assert_called_once_with(token=slack_token) - expected_message = message + "\n" - mock_webclient.chat_postMessage.assert_called_once_with(channel=channel, text=expected_message) - assert success is True - assert error_msg is None - else: - mock_webclient_class.assert_not_called() - mock_webclient.chat_postMessage.assert_not_called() - assert success is True # No error when token not present - expected behavior - assert error_msg is None - - -@pytest.mark.parametrize( - "exception_location,exception_type,description", - [ - ("webclient_constructor", Exception, "WebClient constructor raises generic exception"), - ("webclient_constructor", ConnectionError, "WebClient constructor raises connection error"), - ("chat_post_message", Exception, "chat_postMessage raises generic exception"), - ("chat_post_message", ConnectionError, "chat_postMessage raises connection error"), - ], -) -def test_send_slack_message_error_handling(monkeypatch, exception_location, exception_type, description): - """Test send_slack_message gracefully handles various error scenarios without crashing.""" - monkeypatch.setenv("SLACK_TOKEN", "xoxb-test-token") - - channel = "#test-channel" - message = "Test message" - - if exception_location == "webclient_constructor": - with patch("metadata_service.helpers.slack.WebClient", side_effect=exception_type("Mocked error")): - success, error_msg = send_slack_message(channel, message) - - assert success is False - assert "Mocked error" in error_msg - assert isinstance(error_msg, str) - - elif exception_location == "chat_post_message": - with patch("metadata_service.helpers.slack.WebClient") as mock_webclient_class: - mock_webclient = Mock() - mock_webclient.chat_postMessage = Mock(side_effect=exception_type("Mocked error")) - mock_webclient_class.return_value = mock_webclient - - success, error_msg = send_slack_message(channel, message) - - mock_webclient_class.assert_called_once_with(token="xoxb-test-token") - mock_webclient.chat_postMessage.assert_called_once() - - assert success is False - assert "Mocked error" in error_msg - assert isinstance(error_msg, str) diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_commands.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_commands.py deleted file mode 100644 index ce4adbf2eceb..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_commands.py +++ /dev/null @@ -1,267 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import pathlib - -import pytest -from click.testing import CliRunner -from pydantic import BaseModel, ValidationError, error_wrappers -from test_gcs_upload import stub_is_image_on_docker_hub - -from metadata_service import commands -from metadata_service.gcs_upload import MetadataUploadInfo, UploadedFile -from metadata_service.validators.metadata_validator import ValidatorOptions, validate_docker_image_tag_is_not_decremented - -NOT_TEST_VALIDATORS = [ - # Not testing validate_docker_image_tag_is_not_decremented as its tested independently in test_validators - validate_docker_image_tag_is_not_decremented -] - -PATCHED_VALIDATORS = [v for v in commands.PRE_UPLOAD_VALIDATORS if v not in NOT_TEST_VALIDATORS] - - -# TEST VALIDATE COMMAND -def test_valid_metadata_yaml_files(mocker, valid_metadata_yaml_files, tmp_path): - runner = CliRunner() - - # Mock dockerhub for base image checks - mocker.patch("metadata_service.validators.metadata_validator.is_image_on_docker_hub", side_effect=stub_is_image_on_docker_hub) - mocker.patch("metadata_service.commands.PRE_UPLOAD_VALIDATORS", PATCHED_VALIDATORS) - assert len(valid_metadata_yaml_files) > 0, "No files found" - - for file_path in valid_metadata_yaml_files: - result = runner.invoke(commands.validate, [file_path, str(tmp_path)]) - assert result.exit_code == 0, f"Validation failed for {file_path} with error: {result.output}" - - -def test_invalid_metadata_yaml_files(mocker, invalid_metadata_yaml_files, tmp_path): - runner = CliRunner() - - mocker.patch("metadata_service.validators.metadata_validator.is_image_on_docker_hub", side_effect=stub_is_image_on_docker_hub) - mocker.patch("metadata_service.commands.PRE_UPLOAD_VALIDATORS", PATCHED_VALIDATORS) - - assert len(invalid_metadata_yaml_files) > 0, "No files found" - - for file_path in invalid_metadata_yaml_files: - result = runner.invoke(commands.validate, [file_path, str(tmp_path)]) - assert result.exit_code != 0, f"Validation succeeded (when it should have failed) for {file_path}" - - -def test_metadata_file_not_found_fails(tmp_path): - runner = CliRunner() - result = runner.invoke(commands.validate, ["non_existent_file.yaml", str(tmp_path)]) - assert result.exit_code != 0, "Validation succeeded (when it should have failed) for non_existent_file.yaml" - - -def test_docs_path_not_found_fails(valid_metadata_yaml_files): - runner = CliRunner() - - assert len(valid_metadata_yaml_files) > 0, "No files found" - - result = runner.invoke(commands.validate, [valid_metadata_yaml_files[0], "non_existent_docs_path"]) - assert result.exit_code != 0, "Validation succeeded (when it should have failed) for non_existent_docs_path" - - -def mock_metadata_upload_info( - latest_uploaded: bool, - version_uploaded: bool, - icon_uploaded: bool, - versioned_doc_uploaded: bool, - versioned_doc_inapp_uploaded: bool, - latest_doc_uploaded: bool, - latest_doc_inapp_uploaded: bool, - metadata_file_path: str, -) -> MetadataUploadInfo: - return MetadataUploadInfo( - metadata_uploaded=(latest_uploaded or version_uploaded), - metadata_file_path=metadata_file_path, - uploaded_files=[ - UploadedFile( - id="versioned_metadata", - uploaded=version_uploaded, - blob_id="version_blob_id" if version_uploaded else None, - ), - UploadedFile( - id="latest_metadata", - uploaded=latest_uploaded, - blob_id="latest_blob_id" if latest_uploaded else None, - ), - UploadedFile( - id="latest_icon", - uploaded=icon_uploaded, - blob_id="icon_blob_id" if icon_uploaded else None, - ), - UploadedFile( - id="versioned_doc", - uploaded=versioned_doc_uploaded, - blob_id="versioned_doc_blob_id" if versioned_doc_uploaded else None, - ), - UploadedFile( - id="latest_doc", - uploaded=latest_doc_uploaded, - blob_id="latest_doc_blob_id" if latest_doc_uploaded else None, - ), - UploadedFile( - id="versioned_doc_inapp", - uploaded=versioned_doc_inapp_uploaded, - blob_id="versioned_doc_inapp_blob_id" if versioned_doc_inapp_uploaded else None, - ), - UploadedFile( - id="latest_doc_inapp", - uploaded=latest_doc_inapp_uploaded, - blob_id="latest_doc_inapp_blob_id" if latest_doc_inapp_uploaded else None, - ), - ], - ) - - -# TEST UPLOAD COMMAND -@pytest.mark.parametrize( - "latest_uploaded, version_uploaded, icon_uploaded, versioned_doc_uploaded, versioned_doc_inapp_uploaded, latest_doc_uploaded, latest_doc_inapp_uploaded", - [ - (False, False, False, False, False, False, False), - (True, False, False, False, False, False, False), - (False, True, False, False, False, False, False), - (False, False, True, False, False, False, False), - (True, True, False, False, False, False, False), - (True, False, True, False, False, False, False), - (False, True, True, False, False, False, False), - (True, True, True, False, False, False, False), - (True, True, True, True, True, True, True), - ], -) -def test_upload( - mocker, - tmp_path, - valid_metadata_yaml_files, - latest_uploaded, - version_uploaded, - icon_uploaded, - versioned_doc_uploaded, - versioned_doc_inapp_uploaded, - latest_doc_uploaded, - latest_doc_inapp_uploaded, -): - runner = CliRunner() - mocker.patch.object(commands.click, "secho") - mocker.patch.object(commands, "upload_metadata_to_gcs") - metadata_file_path = valid_metadata_yaml_files[0] - upload_info = mock_metadata_upload_info( - latest_uploaded, - version_uploaded, - icon_uploaded, - versioned_doc_uploaded, - versioned_doc_inapp_uploaded, - latest_doc_uploaded, - latest_doc_inapp_uploaded, - metadata_file_path, - ) - commands.upload_metadata_to_gcs.return_value = upload_info - result = runner.invoke( - commands.upload, [metadata_file_path, str(tmp_path), "my-bucket"] - ) # Using valid_metadata_yaml_files[0] as SA because it exists... - - if latest_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_metadata for {metadata_file_path} was uploaded to latest_blob_id.", fg="green")] - ) - assert result.exit_code == 0 - else: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_metadata for {metadata_file_path} was not uploaded.", fg="yellow")] - ) - - if version_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:versioned_metadata for {metadata_file_path} was uploaded to version_blob_id.", fg="green")] - ) - assert result.exit_code == 0 - else: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:versioned_metadata for {metadata_file_path} was not uploaded.", fg="yellow")] - ) - - if icon_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_icon for {metadata_file_path} was uploaded to icon_blob_id.", fg="green")] - ) - else: - commands.click.secho.assert_has_calls([mocker.call(f"File:latest_icon for {metadata_file_path} was not uploaded.", fg="yellow")]) - - if versioned_doc_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:versioned_doc for {metadata_file_path} was uploaded to versioned_doc_blob_id.", fg="green")] - ) - else: - commands.click.secho.assert_has_calls([mocker.call(f"File:versioned_doc for {metadata_file_path} was not uploaded.", fg="yellow")]) - - if versioned_doc_inapp_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:versioned_doc_inapp for {metadata_file_path} was uploaded to versioned_doc_inapp_blob_id.", fg="green")] - ) - else: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:versioned_doc_inapp for {metadata_file_path} was not uploaded.", fg="yellow")] - ) - - if latest_doc_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_doc for {metadata_file_path} was uploaded to latest_doc_blob_id.", fg="green")] - ) - else: - commands.click.secho.assert_has_calls([mocker.call(f"File:latest_doc for {metadata_file_path} was not uploaded.", fg="yellow")]) - - if latest_doc_inapp_uploaded: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_doc_inapp for {metadata_file_path} was uploaded to latest_doc_inapp_blob_id.", fg="green")] - ) - else: - commands.click.secho.assert_has_calls( - [mocker.call(f"File:latest_doc_inapp for {metadata_file_path} was not uploaded.", fg="yellow")] - ) - - if not (latest_uploaded or version_uploaded): - # We exit with 5 status code to share with the CI pipeline that the upload was skipped. - assert result.exit_code == 5 - - -def test_upload_prerelease(mocker, valid_metadata_yaml_files, tmp_path): - runner = CliRunner() - mocker.patch.object(commands.click, "secho") - mocker.patch.object(commands, "upload_metadata_to_gcs") - - prerelease_tag = "0.3.0-preview.6d33165" - bucket = "my-bucket" - metadata_file_path = valid_metadata_yaml_files[0] - validator_opts = ValidatorOptions(docs_path=str(tmp_path), prerelease_tag=prerelease_tag) - - upload_info = mock_metadata_upload_info(False, True, False, True, False, False, False, metadata_file_path) - commands.upload_metadata_to_gcs.return_value = upload_info - result = runner.invoke( - commands.upload, [metadata_file_path, str(tmp_path), bucket, "--prerelease", prerelease_tag] - ) # Using valid_metadata_yaml_files[0] as SA because it exists... - - commands.upload_metadata_to_gcs.assert_has_calls([mocker.call(bucket, pathlib.Path(metadata_file_path), validator_opts)]) - assert result.exit_code == 0 - - -@pytest.mark.parametrize( - "error, handled", - [ - (ValidationError([error_wrappers.ErrorWrapper(Exception("Boom!"), "foo")], BaseModel), True), - (FileNotFoundError("Boom!"), True), - (ValueError("Boom!"), False), - ], -) -def test_upload_with_errors(mocker, valid_metadata_yaml_files, tmp_path, error, handled): - runner = CliRunner() - mocker.patch.object(commands.click, "secho") - mocker.patch.object(commands, "upload_metadata_to_gcs") - commands.upload_metadata_to_gcs.side_effect = error - result = runner.invoke( - commands.upload, [valid_metadata_yaml_files[0], str(tmp_path), "my-bucket"] - ) # Using valid_metadata_yaml_files[0] as SA because it exists... - assert result.exit_code == 1 - if handled: - commands.click.secho.assert_called_with(f"The metadata file could not be uploaded: {str(error)}", fg="red") diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_docker_hub.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_docker_hub.py deleted file mode 100644 index 9f49d3ac29e9..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_docker_hub.py +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -import warnings - -import pytest - -from metadata_service import docker_hub - - -@pytest.fixture -def image_name(): - return "airbyte/source-faker" - - -@pytest.mark.slow -def test_get_docker_hub_tags_and_digests(image_name): - warnings.warn(f"This test can be flaky as its results depends on the current state of {image_name} dockerhub image.", UserWarning) - tags_and_digests = docker_hub.get_docker_hub_tags_and_digests(image_name) - assert isinstance(tags_and_digests, dict) - assert "latest" in tags_and_digests, "The latest tag is not in the returned dict" - assert "0.1.0" in tags_and_digests, f"The first {image_name} version is not in the returned dict" - assert len(tags_and_digests) > 10, f"Pagination is likely not working as we expect more than 10 version of {image_name} to be released" - - -@pytest.mark.slow -def test_get_latest_version_on_dockerhub(image_name): - warnings.warn(f"This test can be flaky as its results depends on the current state of {image_name} dockerhub image.", UserWarning) - assert ( - docker_hub.get_latest_version_on_dockerhub(image_name) is not None - ), f"No latest version found for {image_name}. We expect one to exist." diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_gcs_upload.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_gcs_upload.py deleted file mode 100644 index b2424ad8ec66..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_gcs_upload.py +++ /dev/null @@ -1,868 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from datetime import datetime -from pathlib import Path -from typing import Optional - -import pytest -import yaml -from pydash.objects import get - -from metadata_service import gcs_upload -from metadata_service.constants import ( - COMPONENTS_PY_FILE_NAME, - DOC_FILE_NAME, - LATEST_GCS_FOLDER_NAME, - MANIFEST_FILE_NAME, - METADATA_FILE_NAME, - RELEASE_CANDIDATE_GCS_FOLDER_NAME, -) -from metadata_service.models.generated.ConnectorMetadataDefinitionV0 import ConnectorMetadataDefinitionV0 -from metadata_service.models.generated.GitInfo import GitInfo -from metadata_service.models.transform import to_json_sanitized_dict -from metadata_service.validators.metadata_validator import ValidatorOptions - -MOCK_VERSIONS_THAT_DO_NOT_EXIST = ["99.99.99", "0.0.0"] -MISSING_SHA = "MISSINGSHA" -DOCS_PATH = "/docs" -MOCK_DOC_URL_PATH = "integrations/sources/existingsource.md" -VALID_DOC_FILE_PATH = Path(DOCS_PATH) / MOCK_DOC_URL_PATH - -# Helpers - - -def stub_is_image_on_docker_hub(image_name: str, version: str, digest: Optional[str] = None, retries: int = 0, wait_sec: int = 30) -> bool: - image_repo_exists = "exists" in image_name - version_exists = version not in MOCK_VERSIONS_THAT_DO_NOT_EXIST - sha_is_valid = (digest != MISSING_SHA) if digest is not None else True - image_exists = all([image_repo_exists, version_exists, sha_is_valid]) - return image_exists - - -# Fixtures - - -@pytest.fixture(autouse=True) -def mock_local_doc_path_exists(monkeypatch): - original_exists = Path.exists - mocked_doc_path = VALID_DOC_FILE_PATH - - def fake_exists(self): - if self == Path(VALID_DOC_FILE_PATH) or self == mocked_doc_path: - return True - return original_exists(self) - - monkeypatch.setattr(Path, "exists", fake_exists) - - -@pytest.fixture -def temp_manifest_content(): - """Sample manifest.yaml content for testing.""" - return """ -# Temporary manifest file for testing -description: Test manifest -author: Test Author -version: 1.0.0 -""".strip() - - -@pytest.fixture -def temp_components_content(): - """Sample components.py content for testing.""" - return """ -# Temporary components.py file for testing -def test_component(): - pass -""".strip() - - -@pytest.fixture -def temp_metadata_directory( - tmp_path, valid_metadata_upload_files, manifest_exists, components_py_exists, temp_manifest_content, temp_components_content -): - """Create a temporary directory structure with optional manifest and components files based on test parameters.""" - # Copy base metadata.yaml from existing fixture to temp directory - base_metadata_file = valid_metadata_upload_files[0] - temp_metadata_path = tmp_path / "metadata.yaml" - - # Copy the content from the base metadata file - with open(base_metadata_file, "r") as f: - temp_metadata_path.write_text(f.read()) - - # Conditionally create manifest.yaml based on test parameter - if manifest_exists: - manifest_path = tmp_path / MANIFEST_FILE_NAME - manifest_path.write_text(temp_manifest_content) - - # Conditionally create components.py based on test parameter - if components_py_exists: - components_path = tmp_path / COMPONENTS_PY_FILE_NAME - components_path.write_text(temp_components_content) - - return temp_metadata_path - - -@pytest.fixture -def mock_git_operations(mocker): - """Mock Git operations to avoid repository issues with temporary files.""" - - # Create a proper GitInfo Pydantic model instance - mock_git_info = GitInfo( - commit_sha="abc123def456", - commit_timestamp=datetime(2024, 1, 1, 0, 0, 0), - commit_author="Test Author", - commit_author_email="test@example.com", - ) - mocker.patch("metadata_service.gcs_upload._get_git_info_for_file", return_value=mock_git_info) - return mock_git_info - - -# Custom Assertions - - -def assert_upload_invalid_metadata_fails_correctly(metadata_file_path: Path, expected_error_match: str, validate_success_error_match: str): - """ - When attempting to upload invalid metadata, we expect it to fail in a predictable way, depending on what is exactly invalid - about the file. This helper aims to make it easier for a developer who is adding new test cases to figure out that their test - is failing because the test data that should be invalid is passing all of the validation steps. - - Because we don't exit the uploading process if validation fails, this case often looks like a weird error message that is hard to - grok. - """ - try: - with pytest.raises(ValueError, match=expected_error_match) as exc_info: - gcs_upload.upload_metadata_to_gcs( - "my_bucket", - metadata_file_path, - validator_opts=ValidatorOptions(docs_path=VALID_DOC_FILE_PATH), - ) - print(f"Upload raised {exc_info.value}") - except AssertionError as e: - if validate_success_error_match in str(e): - raise AssertionError(f"Validation succeeded (when it should have failed) for {metadata_file_path}") from e - else: - raise e - - -def assert_blob_upload(upload_info, upload_info_file_key, blob_mock, should_upload, file_path, failure_message): - """ - Assert that the blob upload occurred (or not) as expected. - """ - file_uploaded = next((file.uploaded for file in upload_info.uploaded_files if file.id == upload_info_file_key), False) - if should_upload: - blob_mock.upload_from_filename.assert_called_with(file_path) - assert file_uploaded, failure_message - else: - blob_mock.upload_from_filename.assert_not_called() - assert not file_uploaded, failure_message - - -# Mocks - - -def setup_upload_mocks( - mocker, - version_blob_md5_hash, - latest_blob_md5_hash, - local_file_md5_hash, - doc_local_file_md5_hash, - doc_version_blob_md5_hash, - doc_latest_blob_md5_hash, - metadata_file_path, - doc_file_path, -): - # Mock dockerhub - mocker.patch("metadata_service.validators.metadata_validator.is_image_on_docker_hub", side_effect=stub_is_image_on_docker_hub) - - # Mock GCS - now using the abstracted get_gcs_storage_client - mock_storage_client = mocker.Mock() - - latest_blob_exists = latest_blob_md5_hash is not None - version_blob_exists = version_blob_md5_hash is not None - doc_version_blob_exists = doc_version_blob_md5_hash is not None - doc_latest_blob_exists = doc_latest_blob_md5_hash is not None - release_candidate_blob_exists = False - - mock_version_blob = mocker.Mock(exists=mocker.Mock(return_value=version_blob_exists), md5_hash=version_blob_md5_hash) - mock_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=latest_blob_exists), md5_hash=latest_blob_md5_hash) - - mock_doc_version_blob = mocker.Mock(exists=mocker.Mock(return_value=doc_version_blob_exists), md5_hash=doc_version_blob_md5_hash) - mock_doc_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=doc_latest_blob_exists), md5_hash=doc_latest_blob_md5_hash) - - mock_release_candidate_blob = mocker.Mock(exists=mocker.Mock(return_value=release_candidate_blob_exists), md5_hash="rc_hash") - - mock_zip_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="zip_hash") - mock_zip_version_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="zip_hash") - - mock_sha_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="sha_hash") - mock_sha_version_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="sha_hash") - - mock_components_py_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="components_py_hash") - mock_components_py_version_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="components_py_hash") - - mock_manifest_latest_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="manifest_hash") - mock_manifest_version_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="manifest_hash") - - mock_other_file_blob = mocker.Mock(exists=mocker.Mock(return_value=True), md5_hash="other_hash") - mock_bucket = mock_storage_client.bucket.return_value - - # Mock the get_gcs_storage_client function directly - mocker.patch("metadata_service.gcs_upload.get_gcs_storage_client", return_value=mock_storage_client) - - # Mock bucket blob - - def side_effect_bucket_blob(file_path): - # if file path ends in latest/metadata.yaml, return mock_latest_blob - file_path_str = str(file_path) - is_latest = f"{LATEST_GCS_FOLDER_NAME}/" in file_path_str - - if file_path_str.endswith(f"{RELEASE_CANDIDATE_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}"): - return mock_release_candidate_blob - - if file_path_str.endswith(METADATA_FILE_NAME): - if is_latest: - return mock_latest_blob - else: - return mock_version_blob - - if file_path_str.endswith(DOC_FILE_NAME): - if is_latest: - return mock_doc_latest_blob - else: - return mock_doc_version_blob - - if file_path_str.endswith(f"{MANIFEST_FILE_NAME}"): - if is_latest: - return mock_manifest_latest_blob - else: - return mock_manifest_version_blob - - if file_path_str.endswith(f"{COMPONENTS_PY_FILE_NAME}"): - if is_latest: - return mock_components_py_latest_blob - else: - return mock_components_py_version_blob - - if file_path_str.endswith(".sha256"): - if is_latest: - return mock_sha_latest_blob - else: - return mock_sha_version_blob - - if file_path_str.endswith(".zip"): - if is_latest: - return mock_zip_latest_blob - else: - return mock_zip_version_blob - - else: - return mock_other_file_blob - - mock_bucket.blob.side_effect = side_effect_bucket_blob - - # Mock md5 hash - def side_effect_compute_gcs_md5(file_path): - if str(file_path) == str(metadata_file_path): - return local_file_md5_hash - elif str(file_path) == str(doc_file_path): - return doc_local_file_md5_hash - else: - return f"mock_md5_hash_{file_path}" - - mocker.patch.object(gcs_upload, "compute_gcs_md5", side_effect=side_effect_compute_gcs_md5) - - return { - "mock_storage_client": mock_storage_client, - "mock_bucket": mock_bucket, - "mock_version_blob": mock_version_blob, - "mock_latest_blob": mock_latest_blob, - "mock_release_candidate_blob": mock_release_candidate_blob, - "mock_doc_version_blob": mock_doc_version_blob, - "mock_doc_latest_blob": mock_doc_latest_blob, - "mock_manifest_latest_blob": mock_manifest_latest_blob, - "mock_components_py_latest_blob": mock_components_py_latest_blob, - "mock_manifest_version_blob": mock_manifest_version_blob, - "mock_components_py_version_blob": mock_components_py_version_blob, - "mock_sha_latest_blob": mock_sha_latest_blob, - "mock_sha_version_blob": mock_sha_version_blob, - "mock_zip_latest_blob": mock_zip_latest_blob, - "mock_zip_version_blob": mock_zip_version_blob, - "mock_other_file_blob": mock_other_file_blob, - } - - -@pytest.mark.parametrize( - "version_blob_md5_hash, latest_blob_md5_hash, local_file_md5_hash, local_doc_file_md5_hash, doc_version_blob_md5_hash, doc_latest_blob_md5_hash", - [ - pytest.param( - None, - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Version blob does not exist: Version blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - None, - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Latest blob does not exist: Latest blob should be uploaded.", - ), - pytest.param( - None, - None, - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Latest blob and Version blob does not exist: both should be uploaded.", - ), - pytest.param( - "different_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Version blob does not match: Version blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Version blob and Latest blob match, and version and latest doc blobs match: no upload should happen.", - ), - pytest.param( - "same_md5_hash", - "different_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Latest blob does not match: Latest blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "different_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - id="Latest blob and Version blob does not match: both should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - None, - "same_doc_md5_hash", - id="Version doc blob does not exist: Doc version blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - None, - id="Latest doc blob does not exist: Doc latest blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "different_doc_md5_hash", - "same_doc_md5_hash", - id="Version doc blob does not match: Doc version blob should be uploaded.", - ), - pytest.param( - "same_md5_hash", - "same_md5_hash", - "same_md5_hash", - "same_doc_md5_hash", - "same_doc_md5_hash", - "different_doc_md5_hash", - id="Latest doc blob does not match: Doc version blob should be uploaded.", - ), - ], -) -def test_upload_metadata_to_gcs_valid_metadata( - mocker, - valid_metadata_upload_files, - version_blob_md5_hash, - latest_blob_md5_hash, - local_file_md5_hash, - local_doc_file_md5_hash, - doc_version_blob_md5_hash, - doc_latest_blob_md5_hash, -): - mocker.spy(gcs_upload, "_file_upload") - mocker.spy(gcs_upload, "upload_file_if_changed") - for valid_metadata_upload_file in valid_metadata_upload_files: - metadata_file_path = Path(valid_metadata_upload_file) - metadata = ConnectorMetadataDefinitionV0.parse_obj(yaml.safe_load(metadata_file_path.read_text())) - mocks = setup_upload_mocks( - mocker, - version_blob_md5_hash, - latest_blob_md5_hash, - local_file_md5_hash, - local_doc_file_md5_hash, - doc_version_blob_md5_hash, - doc_latest_blob_md5_hash, - metadata_file_path, - VALID_DOC_FILE_PATH, - ) - mocker.patch.object(gcs_upload, "_write_metadata_to_tmp_file", mocker.Mock(return_value=metadata_file_path)) - - expected_version_key = f"metadata/{metadata.data.dockerRepository}/{metadata.data.dockerImageTag}/{METADATA_FILE_NAME}" - expected_latest_key = f"metadata/{metadata.data.dockerRepository}/{LATEST_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}" - expected_release_candidate_key = ( - f"metadata/{metadata.data.dockerRepository}/{RELEASE_CANDIDATE_GCS_FOLDER_NAME}/{METADATA_FILE_NAME}" - ) - expected_version_doc_key = f"metadata/{metadata.data.dockerRepository}/{metadata.data.dockerImageTag}/{DOC_FILE_NAME}" - expected_latest_doc_key = f"metadata/{metadata.data.dockerRepository}/latest/{DOC_FILE_NAME}" - - is_release_candidate = "-rc" in metadata.data.dockerImageTag - - # Call function under tests - - upload_info = gcs_upload.upload_metadata_to_gcs( - "my_bucket", metadata_file_path, validator_opts=ValidatorOptions(docs_path=VALID_DOC_FILE_PATH) - ) - - # Assert correct file upload attempts were made - - expected_calls = [ - # Always upload the versioned metadata - mocker.call( - local_file_path=metadata_file_path, bucket=mocks["mock_bucket"], blob_path=expected_version_key, disable_cache=True - ), - # Always upload the versioned doc - mocker.call( - local_file_path=VALID_DOC_FILE_PATH, - bucket=mocks["mock_bucket"], - blob_path=expected_version_doc_key, - disable_cache=False, - ), - ] - - if is_release_candidate: - expected_calls.append( - mocker.call( - local_file_path=metadata_file_path, - bucket=mocks["mock_bucket"], - blob_path=expected_release_candidate_key, - disable_cache=True, - ) - ) - else: - expected_calls.append( - mocker.call( - local_file_path=VALID_DOC_FILE_PATH, bucket=mocks["mock_bucket"], blob_path=expected_latest_doc_key, disable_cache=False - ) - ) - expected_calls.append( - mocker.call( - local_file_path=metadata_file_path, bucket=mocks["mock_bucket"], blob_path=expected_latest_key, disable_cache=True - ) - ) - - gcs_upload.upload_file_if_changed.assert_has_calls(expected_calls, any_order=True) - - # Assert correct files were uploaded - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_metadata", - blob_mock=mocks["mock_latest_blob"], - should_upload=latest_blob_md5_hash != local_file_md5_hash and not is_release_candidate, - file_path=metadata_file_path, - failure_message="Latest blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_release_candidate", - blob_mock=mocks["mock_release_candidate_blob"], - should_upload=is_release_candidate, - file_path=metadata_file_path, - failure_message="Release candidate blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_metadata", - blob_mock=mocks["mock_version_blob"], - should_upload=version_blob_md5_hash != local_file_md5_hash, - file_path=metadata_file_path, - failure_message="Version blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_doc", - blob_mock=mocks["mock_doc_version_blob"], - should_upload=doc_version_blob_md5_hash != local_doc_file_md5_hash, - file_path=VALID_DOC_FILE_PATH, - failure_message="Doc version blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_doc", - blob_mock=mocks["mock_doc_latest_blob"], - should_upload=doc_latest_blob_md5_hash != local_doc_file_md5_hash and not is_release_candidate, - file_path=VALID_DOC_FILE_PATH, - failure_message="Doc latest blob should be uploaded.", - ) - - # clear the call count - gcs_upload.upload_file_if_changed.reset_mock() - - -def test_upload_metadata_to_gcs_non_existent_metadata_file(): - metadata_file_path = Path("./i_dont_exist.yaml") - with pytest.raises(ValueError, match="No such file or directory"): - gcs_upload.upload_metadata_to_gcs( - "my_bucket", - metadata_file_path, - validator_opts=ValidatorOptions(docs_path=VALID_DOC_FILE_PATH), - ) - - -def test_upload_invalid_metadata_to_gcs(mocker, invalid_metadata_yaml_files): - # Mock dockerhub - mocker.patch("metadata_service.validators.metadata_validator.is_image_on_docker_hub", side_effect=stub_is_image_on_docker_hub) - - # Test that all invalid metadata files throw a ValueError - for invalid_metadata_file in invalid_metadata_yaml_files: - metadata_file_path = Path(invalid_metadata_file) - - error_match_if_validation_fails_as_expected = "Validation error" - - # If validation succeeds, it goes on to upload any new/changed files. - # We don't mock the gcs stuff in this test, so it fails trying to - # mock compute the md5 hash. - error_match_if_validation_succeeds = "Please set the GCS_CREDENTIALS env var." - - assert_upload_invalid_metadata_fails_correctly( - metadata_file_path, error_match_if_validation_fails_as_expected, error_match_if_validation_succeeds - ) - - -def test_upload_metadata_to_gcs_invalid_docker_images(mocker, invalid_metadata_upload_files): - setup_upload_mocks(mocker, None, None, "new_md5_hash", None, None, None, None, None) - - # Test that valid metadata files that reference invalid docker images throw a ValueError - for invalid_metadata_file in invalid_metadata_upload_files: - metadata_file_path = Path(invalid_metadata_file) - - error_match_if_validation_fails_as_expected = "does not exist in DockerHub" - - # If validation succeeds, it goes on to upload any new/changed files. - # We mock gcs stuff in this test, so it fails trying to compare the md5 hashes. - error_match_if_validation_succeeds = "Unexpected path" - - assert_upload_invalid_metadata_fails_correctly( - metadata_file_path, error_match_if_validation_fails_as_expected, error_match_if_validation_succeeds - ) - - -def test_upload_metadata_to_gcs_with_prerelease(mocker, valid_metadata_upload_files, tmp_path): - mocker.spy(gcs_upload, "_file_upload") - mocker.spy(gcs_upload, "upload_file_if_changed") - prerelease_image_tag = "1.5.6-preview.f80318f" - - for valid_metadata_upload_file in valid_metadata_upload_files: - tmp_metadata_file_path = tmp_path / "metadata.yaml" - if tmp_metadata_file_path.exists(): - tmp_metadata_file_path.unlink() - - metadata_file_path = Path(valid_metadata_upload_file) - metadata = ConnectorMetadataDefinitionV0.parse_obj(yaml.safe_load(metadata_file_path.read_text())) - expected_version_key = f"metadata/{metadata.data.dockerRepository}/{prerelease_image_tag}/{METADATA_FILE_NAME}" - - mocks = setup_upload_mocks(mocker, "new_md5_hash1", "new_md5_hash2", "new_md5_hash3", None, None, None, metadata_file_path, None) - - # Mock tempfile to have a deterministic path - mocker.patch.object( - gcs_upload.tempfile, - "NamedTemporaryFile", - mocker.Mock( - return_value=mocker.Mock(__enter__=mocker.Mock(return_value=tmp_metadata_file_path.open("w")), __exit__=mocker.Mock()) - ), - ) - - upload_info = gcs_upload.upload_metadata_to_gcs( - "my_bucket", - metadata_file_path, - ValidatorOptions(docs_path=VALID_DOC_FILE_PATH, prerelease_tag=prerelease_image_tag), - ) - - # Assert that the metadata is overrode - tmp_metadata, error = gcs_upload.validate_and_load( - tmp_metadata_file_path, [], validator_opts=ValidatorOptions(docs_path=VALID_DOC_FILE_PATH) - ) - tmp_metadata_dict = to_json_sanitized_dict(tmp_metadata, exclude_none=True) - assert tmp_metadata_dict["data"]["dockerImageTag"] == prerelease_image_tag - for registry in get(tmp_metadata_dict, "data.registryOverrides", {}).values(): - if "dockerImageTag" in registry: - assert registry["dockerImageTag"] == prerelease_image_tag - - # Assert uploads attempted - - expected_calls = [ - mocker.call( - local_file_path=tmp_metadata_file_path, bucket=mocks["mock_bucket"], blob_path=expected_version_key, disable_cache=True - ), - ] - - gcs_upload.upload_file_if_changed.assert_has_calls(expected_calls, any_order=True) - - # Assert versioned uploads happened - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_metadata", - blob_mock=mocks["mock_version_blob"], - should_upload=True, - file_path=tmp_metadata_file_path, - failure_message="Latest blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_doc", - blob_mock=mocks["mock_doc_version_blob"], - should_upload=True, - file_path=VALID_DOC_FILE_PATH, - failure_message="Latest blob should be uploaded.", - ) - - # Assert latest uploads did not happen - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_metadata", - blob_mock=mocks["mock_latest_blob"], - should_upload=False, - file_path=metadata_file_path, - failure_message="Latest blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_doc", - blob_mock=mocks["mock_doc_latest_blob"], - should_upload=False, - file_path=VALID_DOC_FILE_PATH, - failure_message="Latest blob should be uploaded.", - ) - - # clear the call count - gcs_upload._file_upload.reset_mock() - gcs_upload.upload_file_if_changed.reset_mock() - - -@pytest.mark.parametrize("prerelease", [True, False]) -def test_upload_metadata_to_gcs_release_candidate(mocker, get_fixture_path, tmp_path, prerelease): - mocker.spy(gcs_upload, "_file_upload") - mocker.spy(gcs_upload, "upload_file_if_changed") - release_candidate_metadata_file = get_fixture_path( - "metadata_upload/valid/referenced_image_in_dockerhub/metadata_release_candidate.yaml" - ) - release_candidate_metadata_file_path = Path(release_candidate_metadata_file) - metadata = ConnectorMetadataDefinitionV0.parse_obj(yaml.safe_load(release_candidate_metadata_file_path.read_text())) - - tmp_metadata_file_path = tmp_path / "metadata.yaml" - if tmp_metadata_file_path.exists(): - tmp_metadata_file_path.unlink() - - mocks = setup_upload_mocks( - mocker, "new_md5_hash1", "new_md5_hash2", "new_md5_hash3", None, None, None, release_candidate_metadata_file_path, None - ) - - # Mock tempfile to have a deterministic path - mocker.patch.object( - gcs_upload.tempfile, - "NamedTemporaryFile", - mocker.Mock(return_value=mocker.Mock(__enter__=mocker.Mock(return_value=tmp_metadata_file_path.open("w")), __exit__=mocker.Mock())), - ) - assert metadata.data.releases.rolloutConfiguration.enableProgressiveRollout - - prerelease_tag = "1.5.6-preview.f80318f" if prerelease else None - - upload_info = gcs_upload.upload_metadata_to_gcs( - "my_bucket", - release_candidate_metadata_file_path, - ValidatorOptions(docs_path=VALID_DOC_FILE_PATH, prerelease_tag=prerelease_tag), - ) - - # Assert versioned uploads happened - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_release_candidate", - blob_mock=mocks["mock_release_candidate_blob"], - should_upload=not prerelease, - file_path=tmp_metadata_file_path, - failure_message="Latest blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_doc", - blob_mock=mocks["mock_doc_version_blob"], - should_upload=True, - file_path=VALID_DOC_FILE_PATH, - failure_message="Latest blob should be uploaded.", - ) - - # Assert latest uploads did not happen - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_metadata", - blob_mock=mocks["mock_latest_blob"], - should_upload=False, - file_path=tmp_metadata_file_path, - failure_message="Latest blob should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_doc", - blob_mock=mocks["mock_doc_latest_blob"], - should_upload=False, - file_path=VALID_DOC_FILE_PATH, - failure_message="Latest blob should be uploaded.", - ) - - -@pytest.mark.parametrize( - "manifest_exists, components_py_exists", - [ - (True, True), # Both files exist - (True, False), # Only manifest exists - (False, True), # Only components.py exists - (False, False), # Neither file exists - ], -) -def test_upload_metadata_to_gcs_with_manifest_files( - mocker, temp_metadata_directory, tmp_path, manifest_exists, components_py_exists, mock_git_operations -): - mocker.spy(gcs_upload, "_file_upload") - mocker.spy(gcs_upload, "upload_file_if_changed") - - # Use the temporary metadata file created by the fixture - metadata_file_path = temp_metadata_directory - expected_manifest_file_path = metadata_file_path.parent / MANIFEST_FILE_NAME - expected_components_py_file_path = metadata_file_path.parent / COMPONENTS_PY_FILE_NAME - - # No more Path.exists mocking needed - files either exist or don't based on fixture creation! - # Git operations are mocked by the mock_git_operations fixture! - - # Mock the _safe_load_metadata_file function to bypass YAML parsing issues in test environment - sample_metadata = { - "metadataSpecVersion": "1.0", - "data": { - "name": "Test Connector", - "definitionId": "12345678-1234-1234-1234-123456789012", - "connectorType": "source", - "dockerRepository": "airbyte/source-exists-test", - "dockerImageTag": "0.1.0", - "documentationUrl": "https://docs.airbyte.com/test", - "license": "MIT", - "githubIssueLabel": "source-test", - "connectorSubtype": "api", - "releaseStage": "alpha", - "tags": ["language:python"], - }, - } - mocker.patch("metadata_service.gcs_upload._safe_load_metadata_file", return_value=sample_metadata) - - # mock create_zip_and_get_sha256 - mocker.patch.object(gcs_upload, "create_zip_and_get_sha256", mocker.Mock(return_value="fake_zip_sha256")) - - tmp_metadata_file_path = tmp_path / "metadata.yaml" - tmp_zip_file_path = tmp_path / "components.zip" - tmp_sha256_file_path = tmp_path / "components.sha256" - - def mock_tmp_files(*args, **kwargs): - file_to_return = tmp_metadata_file_path.open("w") - if kwargs.get("suffix") == ".zip": - file_to_return = tmp_zip_file_path.open("w") - if kwargs.get("suffix") == ".sha256": - file_to_return = tmp_sha256_file_path.open("w") - - return mocker.Mock(__enter__=mocker.Mock(return_value=file_to_return), __exit__=mocker.Mock()) - - # Mock tempfile to have a deterministic path - mocker.patch.object( - gcs_upload.tempfile, - "NamedTemporaryFile", - mocker.Mock(side_effect=mock_tmp_files), - ) - - mocks = setup_upload_mocks(mocker, "new_md5_hash1", "new_md5_hash2", "new_md5_hash3", None, None, None, metadata_file_path, None) - - upload_info = gcs_upload.upload_metadata_to_gcs( - "my_bucket", - metadata_file_path, - validator_opts=ValidatorOptions(docs_path=VALID_DOC_FILE_PATH), - ) - - # Latest Uploads - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_manifest", - blob_mock=mocks["mock_manifest_latest_blob"], - should_upload=manifest_exists, - file_path=expected_manifest_file_path, - failure_message="Latest manifest should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="latest_components_zip", - blob_mock=mocks["mock_zip_latest_blob"], - should_upload=components_py_exists, - file_path=tmp_zip_file_path, - failure_message="Latest components.py should be uploaded.", - ) - - # Versioned Uploads - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_manifest", - blob_mock=mocks["mock_manifest_version_blob"], - should_upload=manifest_exists, - file_path=expected_manifest_file_path, - failure_message="Versioned manifest should be uploaded.", - ) - - assert_blob_upload( - upload_info=upload_info, - upload_info_file_key="versioned_components_zip", - blob_mock=mocks["mock_zip_version_blob"], - should_upload=components_py_exists, - file_path=tmp_zip_file_path, - failure_message="Versioned components.py should be uploaded.", - ) - - # clear the call count - gcs_upload._file_upload.reset_mock() - gcs_upload.upload_file_if_changed.reset_mock() diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_registry.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_registry.py deleted file mode 100644 index 91b2e948b5bc..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_registry.py +++ /dev/null @@ -1,328 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import json -import os -from unittest.mock import Mock, patch - -import pytest - -from metadata_service.constants import REGISTRIES_FOLDER -from metadata_service.models.generated import ConnectorRegistryV0 -from metadata_service.registry import ( - ConnectorTypePrimaryKey, - ConnectorTypes, - PolymorphicRegistryEntry, - _apply_metrics_to_registry_entry, - _build_connector_registry, - _convert_json_to_metrics_dict, - _get_connector_type_from_registry_entry, - _persist_registry, -) - - -class TestGetConnectorTypeFromRegistryEntry: - """Tests for _get_connector_type_from_registry_entry function.""" - - @pytest.fixture - def mock_source_entry(self): - """Create a mock source registry entry.""" - entry = Mock(spec=PolymorphicRegistryEntry) - setattr(entry, ConnectorTypePrimaryKey.SOURCE.value, "test-source-id") - return entry - - @pytest.fixture - def mock_destination_entry(self): - """Create a mock destination registry entry.""" - entry = Mock(spec=PolymorphicRegistryEntry) - setattr(entry, ConnectorTypePrimaryKey.DESTINATION.value, "test-destination-id") - return entry - - @pytest.fixture - def mock_invalid_entry(self): - """Create a mock entry that is neither source nor destination.""" - entry = Mock(spec=PolymorphicRegistryEntry) - return entry - - @pytest.mark.parametrize( - "entry_fixture,expected_type,description", - [ - ("mock_source_entry", ConnectorTypes.SOURCE, "source entry"), - ("mock_destination_entry", ConnectorTypes.DESTINATION, "destination entry"), - ], - ) - def test_get_connector_type_from_registry_entry_types(self, entry_fixture, expected_type, description, request): - """Test connector type detection from registry entries.""" - registry_entry = request.getfixturevalue(entry_fixture) - - result = _get_connector_type_from_registry_entry(registry_entry) - - assert result == expected_type - assert isinstance(result, ConnectorTypes) - - @pytest.mark.parametrize( - "entry_fixture,expected_type,has_attribute,not_has_attribute,description", - [ - ( - "mock_source_entry", - ConnectorTypes.SOURCE, - ConnectorTypePrimaryKey.SOURCE.value, - ConnectorTypePrimaryKey.DESTINATION.value, - "source entry", - ), - ( - "mock_destination_entry", - ConnectorTypes.DESTINATION, - ConnectorTypePrimaryKey.DESTINATION.value, - ConnectorTypePrimaryKey.SOURCE.value, - "destination entry", - ), - ], - ) - def test_get_connector_type_from_registry_entry_has_correct_attribute( - self, entry_fixture, expected_type, has_attribute, not_has_attribute, description, request - ): - registry_entry = request.getfixturevalue(entry_fixture) - - assert hasattr(registry_entry, has_attribute) - assert not hasattr(registry_entry, not_has_attribute) - - result = _get_connector_type_from_registry_entry(registry_entry) - - assert result == expected_type - - def test_get_connector_type_from_registry_entry_invalid_raises_error(self, mock_invalid_entry): - """Test that invalid entry raises ValueError.""" - assert not hasattr(mock_invalid_entry, ConnectorTypePrimaryKey.SOURCE.value) - assert not hasattr(mock_invalid_entry, ConnectorTypePrimaryKey.DESTINATION.value) - - with pytest.raises(ValueError) as exc_info: - _get_connector_type_from_registry_entry(mock_invalid_entry) - - assert "Registry entry is not a source or destination" in str(exc_info.value) - - -class TestConvertJsonToMetricsDict: - """Tests for _convert_json_to_metrics_dict function.""" - - @pytest.mark.parametrize( - "jsonl_input,expected_output,description", - [ - ( - '{"_airbyte_data": {"connector_definition_id": "conn-123", "airbyte_platform": "cloud", "usage": 100}}', - {"conn-123": {"cloud": {"connector_definition_id": "conn-123", "airbyte_platform": "cloud", "usage": 100}}}, - "single connector", - ), - ( - '{"_airbyte_data": {"connector_definition_id": "conn-123", "airbyte_platform": "cloud", "usage": 100}}\n{"_airbyte_data": {"connector_definition_id": "conn-456", "airbyte_platform": "oss", "usage": 50}}', - { - "conn-123": {"cloud": {"connector_definition_id": "conn-123", "airbyte_platform": "cloud", "usage": 100}}, - "conn-456": {"oss": {"connector_definition_id": "conn-456", "airbyte_platform": "oss", "usage": 50}}, - }, - "multiple connectors", - ), - ("", {}, "empty input"), - ], - ) - def test_convert_json_to_metrics_dict_valid_jsonl(self, jsonl_input, expected_output, description): - """Test JSONL string conversion to metrics dictionary.""" - result = _convert_json_to_metrics_dict(jsonl_input) - - assert result == expected_output - - -class TestApplyMetricsToRegistryEntry: - """Tests for _apply_metrics_to_registry_entry function.""" - - @pytest.mark.parametrize( - "connector_type,registry_entry,metrics_dict,expected_metrics,description", - [ - ( - ConnectorTypes.SOURCE, - {"sourceDefinitionId": "source-123", "name": "Test Source"}, - {"source-123": {"cloud": {"usage": 100}}}, - {"cloud": {"usage": 100}}, - "source with matching metrics", - ), - ( - ConnectorTypes.DESTINATION, - {"destinationDefinitionId": "dest-456", "name": "Test Destination"}, - {"dest-456": {"oss": {"usage": 50}}}, - {"oss": {"usage": 50}}, - "destination with matching metrics", - ), - ( - ConnectorTypes.SOURCE, - {"sourceDefinitionId": "source-999", "name": "No Metrics Source"}, - {}, - {}, - "entry with no matching metrics", - ), - ], - ) - def test_apply_metrics_to_registry_entry_scenarios(self, connector_type, registry_entry, metrics_dict, expected_metrics, description): - """Test metrics application to registry entries.""" - result = _apply_metrics_to_registry_entry(registry_entry, connector_type, metrics_dict) - - assert result["generated"]["metrics"] == expected_metrics - assert result["name"] == registry_entry["name"] - - def test_apply_metrics_to_registry_entry_preserves_existing_structure(self): - """Test that existing registry entry structure is preserved.""" - registry_entry = {"sourceDefinitionId": "source-123", "name": "Test Source", "existing_field": "value"} - metrics_dict = {"source-123": {"cloud": {"usage": 100}}} - - result = _apply_metrics_to_registry_entry(registry_entry, ConnectorTypes.SOURCE, metrics_dict) - - assert result["existing_field"] == "value" - assert result["name"] == "Test Source" - assert result["generated"]["metrics"] == {"cloud": {"usage": 100}} - - -class TestBuildConnectorRegistry: - """Tests for _build_connector_registry function.""" - - @pytest.mark.parametrize( - "entry_dicts,expected_sources_count,expected_destinations_count,description", - [ - ( - [ - { - "sourceDefinitionId": "550e8400-e29b-41d4-a716-446655440001", - "name": "Source 1", - "dockerRepository": "test/source", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.test.com", - "spec": {}, - }, - { - "destinationDefinitionId": "550e8400-e29b-41d4-a716-446655440002", - "name": "Destination 1", - "dockerRepository": "test/dest", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.test.com", - "spec": {}, - }, - ], - 1, - 1, - "mixed sources and destinations", - ), - ( - [ - { - "sourceDefinitionId": "550e8400-e29b-41d4-a716-446655440001", - "name": "Source 1", - "dockerRepository": "test/source1", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.test.com", - "spec": {}, - }, - { - "sourceDefinitionId": "550e8400-e29b-41d4-a716-446655440002", - "name": "Source 2", - "dockerRepository": "test/source2", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.test.com", - "spec": {}, - }, - ], - 2, - 0, - "sources only", - ), - ([], 0, 0, "empty entries"), - ], - ) - def test_build_connector_registry_scenarios(self, entry_dicts, expected_sources_count, expected_destinations_count, description): - """Test registry building with different entry combinations.""" - entries = [] - for entry_dict in entry_dicts: - entry = Mock(spec=PolymorphicRegistryEntry) - if "sourceDefinitionId" in entry_dict: - setattr(entry, ConnectorTypePrimaryKey.SOURCE.value, entry_dict["sourceDefinitionId"]) - if "destinationDefinitionId" in entry_dict: - setattr(entry, ConnectorTypePrimaryKey.DESTINATION.value, entry_dict["destinationDefinitionId"]) - entries.append(entry) - - with ( - patch("metadata_service.registry.to_json_sanitized_dict") as mock_sanitize, - patch("metadata_service.registry._apply_metrics_to_registry_entry") as mock_apply_metrics, - patch("metadata_service.registry._apply_release_candidate_entries") as mock_apply_rc, - ): - mock_sanitize.side_effect = entry_dicts - mock_apply_metrics.side_effect = lambda x, *args: x - mock_apply_rc.side_effect = lambda x, *args: x - - result = _build_connector_registry(entries, {}, {}) - - assert isinstance(result, ConnectorRegistryV0) - assert len(result.sources) == expected_sources_count - assert len(result.destinations) == expected_destinations_count - - def test_build_connector_registry_applies_metrics_and_rc(self): - """Test that metrics and release candidates are properly applied.""" - source_entry = Mock(spec=PolymorphicRegistryEntry) - setattr(source_entry, ConnectorTypePrimaryKey.SOURCE.value, "550e8400-e29b-41d4-a716-446655440001") - - entry_dict = { - "sourceDefinitionId": "550e8400-e29b-41d4-a716-446655440001", - "name": "Test Source", - "dockerRepository": "test/source", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.test.com", - "spec": {}, - } - - with ( - patch("metadata_service.registry.to_json_sanitized_dict") as mock_sanitize, - patch("metadata_service.registry._apply_metrics_to_registry_entry") as mock_apply_metrics, - patch("metadata_service.registry._apply_release_candidate_entries") as mock_apply_rc, - ): - mock_sanitize.return_value = entry_dict - mock_apply_metrics.return_value = entry_dict - mock_apply_rc.return_value = entry_dict - - result = _build_connector_registry([source_entry], {}, {}) - - mock_apply_metrics.assert_called_once() - mock_apply_rc.assert_called_once() - assert len(result.sources) == 1 - - -class TestPersistRegistryToJson: - """Tests for _persist_registry_to_json function.""" - - @pytest.fixture - def mock_registry(self): - """Create a mock registry object.""" - registry = Mock(spec=ConnectorRegistryV0) - registry.json.return_value = '{"sources": [], "destinations": []}' - return registry - - @pytest.mark.parametrize( - "registry_type,expected_filename,description", - [ - ("cloud", "cloud_registry.json", "cloud registry"), - ("oss", "oss_registry.json", "oss registry"), - ], - ) - def test_persist_registry_success(self, mock_registry, registry_type, expected_filename, description): - """Test successful registry persistence to GCS.""" - with ( - patch("metadata_service.registry.storage.Client") as mock_client_class, - ): - mock_client = Mock() - mock_client_class.return_value = mock_client - - mock_bucket = Mock() - mock_client.bucket.return_value = mock_bucket - - mock_blob = Mock() - mock_bucket.blob.return_value = mock_blob - - _persist_registry(mock_registry, registry_type, mock_bucket) - - mock_bucket.blob.assert_called_once_with(f"{REGISTRIES_FOLDER}/{expected_filename}") - mock_blob.upload_from_string.assert_called_once() diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_registry_entry.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_registry_entry.py deleted file mode 100644 index 34164ab0ced7..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_registry_entry.py +++ /dev/null @@ -1,550 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import datetime -import json -import os -import pathlib -import tempfile -from unittest.mock import MagicMock, Mock, patch - -import pytest -import yaml - -from metadata_service.registry_entry import generate_and_persist_registry_entry - - -@pytest.fixture -def sample_spec_dict(): - """Sample spec dictionary for testing.""" - return { - "documentationUrl": "https://docs.airbyte.com/integrations/sources/test", - "connectionSpecification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Test Source Spec", - "type": "object", - "properties": {"api_key": {"type": "string", "title": "API Key", "description": "API key for authentication"}}, - "required": ["api_key"], - }, - } - - -@pytest.fixture -def sample_dependencies_dict(): - """Sample dependencies dictionary for testing.""" - return {"dependencies": [{"package_name": "airbyte-cdk", "version": "0.50.0"}, {"package_name": "requests", "version": "2.28.0"}]} - - -@pytest.fixture( - params=[ - # (registry_type, enabled, version_type) - ("oss", True, "latest"), - ("cloud", True, "latest"), - ("oss", True, "rc"), - ("cloud", True, "rc"), - ("oss", True, "dev"), - ("cloud", True, "dev"), - ("oss", False, "latest"), - ("cloud", False, "latest"), - ("oss", False, "rc"), - ("cloud", False, "rc"), - ("oss", False, "dev"), - ("cloud", False, "dev"), - ] -) -def registry_scenario(request, sample_spec_dict): - """Parameterized fixture providing different registry scenarios with temp files.""" - registry_type, enabled, version_type = request.param - - # Create metadata dict based on enabled/disabled status - docker_tag = "1.0.0" - if version_type == "rc": - docker_tag = "1.0.0-rc" - elif version_type == "dev": - docker_tag = "1.0.0-dev" - - metadata_dict = { - "metadataSpecVersion": "1.0", - "data": { - "name": f"Test Source {'Enabled' if enabled else 'Disabled'}", - "definitionId": "12345678-1234-1234-1234-123456789012", - "connectorType": "source", - "dockerRepository": f"airbyte/source-test-{'enabled' if enabled else 'disabled'}", - "dockerImageTag": docker_tag, - "documentationUrl": f"https://docs.airbyte.com/integrations/sources/test-{'enabled' if enabled else 'disabled'}", - "connectorSubtype": "api", - "releaseStage": "beta", - "license": "MIT", - "registryOverrides": {"oss": {"enabled": enabled}, "cloud": {"enabled": enabled}}, - "tags": ["language:python"], - }, - } - - # Create temporary files - with tempfile.TemporaryDirectory() as temp_dir: - # Create metadata file - metadata_path = pathlib.Path(temp_dir) / "metadata.yaml" - with open(metadata_path, "w") as f: - yaml.dump(metadata_dict, f) - - # Create spec file - spec_path = pathlib.Path(temp_dir) / "spec.json" - with open(spec_path, "w") as f: - json.dump(sample_spec_dict, f) - - yield { - "registry_type": registry_type, - "enabled": enabled, - "version_type": version_type, - "metadata_path": metadata_path, - "spec_path": spec_path, - "metadata_dict": metadata_dict, - "docker_image_tag": docker_tag, - "is_prerelease": version_type == "dev", - } - - -@patch("metadata_service.registry_entry.send_slack_message") -@patch("metadata_service.registry_entry._persist_connector_registry_entry") -@patch("metadata_service.registry_entry._get_icon_blob_from_gcs") -@patch("metadata_service.registry_entry.safe_read_gcs_file") -@patch("metadata_service.registry_entry.get_gcs_storage_client") -@patch("metadata_service.registry.storage.Client") -@patch("metadata_service.registry_entry.SpecCache") -def test_generate_and_persist_registry_entry( - mock_spec_cache_class, - mock_storage_client_class, - mock_gcs_client, - mock_safe_read_gcs_file, - mock_get_icon_blob, - mock_persist_entry, - mock_send_slack, - registry_scenario, - sample_dependencies_dict, -): - """Test registry entry generation for all scenarios: enabled/disabled, all registry types, and all version types.""" - # Arrange - scenario = registry_scenario - bucket_name = "test-bucket" - - # Mock GCS client setup - mock_storage_client = Mock() - mock_storage_client_class.return_value = mock_storage_client - mock_bucket = Mock() - mock_blob = Mock() - mock_icon_blob = Mock() - - mock_gcs_client.return_value = mock_storage_client - mock_storage_client.bucket.return_value = mock_bucket - mock_bucket.blob.return_value = mock_blob - mock_bucket.delete_blob = Mock() - - mock_spec_cache = Mock() - mock_spec_cache_class.return_value = mock_spec_cache - mock_spec_cache.download_spec.return_value = json.loads('{"fake": "spec"}') - - mock_blob.download_as_string.return_value = yaml.dump(scenario["metadata_dict"]) - mock_blob.name = "fake/blob/path.yaml" - mock_blob.updated.isoformat.return_value = "2025-01-23T12:34:56Z" - - if scenario["enabled"]: - # Mock successful operations for enabled scenarios - mock_safe_read_gcs_file.return_value = json.dumps(sample_dependencies_dict) - mock_icon_blob.bucket.name = bucket_name - mock_icon_blob.name = f"metadata/{scenario['metadata_dict']['data']['dockerRepository']}/latest/icon.svg" - mock_get_icon_blob.return_value = mock_icon_blob - else: - # Mock existing blob for deletion scenarios (latest versions only) - mock_blob.exists.return_value = True - - # Act - generate_and_persist_registry_entry( - bucket_name=bucket_name, - repo_metadata_file_path=scenario["metadata_path"], - registry_type=scenario["registry_type"], - docker_image_tag=scenario["docker_image_tag"], - is_prerelease=scenario["is_prerelease"], - ) - - # Assert based on enabled/disabled status - if scenario["enabled"]: - # ENABLED SCENARIO ASSERTIONS - - # Verify GCS operations were called - mock_gcs_client.assert_called() - # For "latest" version type (normal publishes), safe_read_gcs_file and _get_icon_blob_from_gcs - # are called twice because we generate two sets of metadata: one for versioned entry (actual - # published version), one for latest entry (with version pinning applied). - if scenario["version_type"] == "latest": - assert mock_safe_read_gcs_file.call_count == 2 - assert mock_get_icon_blob.call_count == 2 - else: - mock_safe_read_gcs_file.assert_called_once() - mock_get_icon_blob.assert_called_once_with(bucket_name, scenario["metadata_dict"]["data"]) - - # Verify registry entry was persisted - mock_persist_entry.assert_called() - persist_call_args = mock_persist_entry.call_args_list - - # Check number of persist calls based on version type - if scenario["version_type"] == "latest" or scenario["version_type"] == "rc": - # in "latest" mode, we write to versioned + `/latest` - # in "rc" mode, we write to versioned + `/release_candidate` - assert len(persist_call_args) == 2 - else: # dev - assert len(persist_call_args) == 1 # only one path - - # Verify the registry entry model was created correctly - for call_args in persist_call_args: - bucket_name_arg, registry_entry_model, registry_path = call_args[0] - assert bucket_name_arg == bucket_name - assert scenario["registry_type"] in registry_path - - # Verify the model has expected fields - registry_entry_dict = json.loads(registry_entry_model.json(exclude_none=True)) - - # Check core field transformations - assert "sourceDefinitionId" in registry_entry_dict - assert registry_entry_dict["sourceDefinitionId"] == scenario["metadata_dict"]["data"]["definitionId"] - - # Verify required fields were added - assert registry_entry_dict["tombstone"] is False - assert registry_entry_dict["custom"] is False - assert registry_entry_dict["public"] is True - assert "iconUrl" in registry_entry_dict - assert "generated" in registry_entry_dict - assert "packageInfo" in registry_entry_dict - assert registry_entry_dict["packageInfo"]["cdk_version"] == "python:0.50.0" - assert "spec" in registry_entry_dict - - # Verify fields were removed - assert "registryOverrides" not in registry_entry_dict - assert "connectorType" not in registry_entry_dict - assert "definitionId" not in registry_entry_dict - - registry_entry_paths = set() - for kall in persist_call_args: - args, _ = kall - # blob path is the last positional arg - registry_entry_paths.add(args[-1]) - if scenario["version_type"] == "rc": - assert registry_entry_paths == { - f'metadata/airbyte/source-test-enabled/{scenario["docker_image_tag"]}/{scenario["registry_type"]}.json', - f'metadata/airbyte/source-test-enabled/release_candidate/{scenario["registry_type"]}.json', - } - elif scenario["version_type"] == "latest": - assert registry_entry_paths == { - f'metadata/airbyte/source-test-enabled/{scenario["docker_image_tag"]}/{scenario["registry_type"]}.json', - f'metadata/airbyte/source-test-enabled/latest/{scenario["registry_type"]}.json', - } - elif scenario["version_type"] == "dev": - assert registry_entry_paths == { - f'metadata/airbyte/source-test-enabled/{scenario["docker_image_tag"]}/{scenario["registry_type"]}.json', - } - else: - raise Exception(f'Unexpected scenario: {scenario["version_type"]}') - - # Verify Slack notifications - slack_calls = mock_send_slack.call_args_list - assert len(slack_calls) >= 2 # start + success notifications - - start_call = slack_calls[0][0] - assert "Registry Entry Generation_ STARTED" in start_call[1] - - success_calls = [call for call in slack_calls if "SUCCESS" in call[0][1]] - assert len(success_calls) == len(persist_call_args) - - else: - # DISABLED SCENARIO ASSERTIONS - - # Verify NO generation/persistence operations were called - mock_safe_read_gcs_file.assert_not_called() - mock_get_icon_blob.assert_not_called() - mock_persist_entry.assert_not_called() - - if scenario["version_type"] == "latest": - # For latest versions, should check for deletion - mock_gcs_client.assert_called() - expected_latest_path = ( - f"metadata/{scenario['metadata_dict']['data']['dockerRepository']}/latest/{scenario['registry_type']}.json" - ) - mock_bucket.blob.assert_called_with(expected_latest_path) - mock_blob.exists.assert_called_once() - mock_bucket.delete_blob.assert_called_once_with(expected_latest_path) - else: - # For rc/dev versions, no deletion should occur - mock_bucket.delete_blob.assert_not_called() - - # Verify NOOP Slack notification - slack_calls = mock_send_slack.call_args_list - assert len(slack_calls) == 2 # STARTED + NOOP notifications - - # Check STARTED notification - start_call = slack_calls[0][0] - assert "Registry Entry Generation_ STARTED" in start_call[1] - assert scenario["registry_type"] in start_call[1] - - # Check NOOP notification - noop_call = slack_calls[1][0] - assert "Registry Entry Generation_ NOOP" in noop_call[1] - assert scenario["registry_type"] in noop_call[1] - assert "not enabled" in noop_call[1] - - -@pytest.mark.parametrize("registry_type", ["oss", "cloud"]) -@patch("metadata_service.registry_entry.send_slack_message") -def test_invalid_metadata_file_path(mock_send_slack, registry_type): - """Test exception handling when metadata file path doesn't exist or is invalid.""" - - # Arrange - invalid_metadata_path = pathlib.Path("/nonexistent/metadata.yaml") - spec_path = pathlib.Path("/some/spec.json") # This won't be reached - bucket_name = "test-bucket" - - # Act & Assert - with pytest.raises(FileNotFoundError): - generate_and_persist_registry_entry( - bucket_name=bucket_name, - repo_metadata_file_path=invalid_metadata_path, - registry_type=registry_type, - docker_image_tag="irrelevant", - is_prerelease=False, - ) - - -@pytest.mark.parametrize( - "failure_stage,registry_type", - [ - ("gcs_client", "oss"), - ("gcs_client", "cloud"), - ("dependencies_read", "oss"), - ("dependencies_read", "cloud"), - ("icon_blob", "oss"), - ("icon_blob", "cloud"), - ("persist", "oss"), - ("persist", "cloud"), - ], -) -@patch("metadata_service.registry_entry.send_slack_message") -@patch("metadata_service.registry_entry._persist_connector_registry_entry") -@patch("metadata_service.registry_entry._get_icon_blob_from_gcs") -@patch("metadata_service.registry_entry.safe_read_gcs_file") -@patch("metadata_service.registry_entry.get_gcs_storage_client") -def test_gcs_operations_failure( - mock_gcs_client, - mock_safe_read_gcs_file, - mock_get_icon_blob, - mock_persist_entry, - mock_send_slack, - temp_files, - sample_dependencies_dict, - failure_stage, - registry_type, -): - """Test exception handling when various GCS operations fail.""" - # Arrange - metadata_path, spec_path = temp_files - bucket_name = "test-bucket" - - # Mock the failure based on stage - if failure_stage == "gcs_client": - mock_gcs_client.side_effect = Exception("GCS client connection failed") - elif failure_stage == "dependencies_read": - mock_gcs_client.return_value = Mock() - mock_safe_read_gcs_file.side_effect = Exception("Failed to read dependencies") - elif failure_stage == "icon_blob": - mock_gcs_client.return_value = Mock() - mock_safe_read_gcs_file.return_value = json.dumps(sample_dependencies_dict) - mock_get_icon_blob.side_effect = Exception("Failed to get icon blob") - elif failure_stage == "persist": - # Mock successful setup until persistence - mock_storage_client = Mock() - mock_bucket = Mock() - mock_icon_blob = Mock() - - mock_gcs_client.return_value = mock_storage_client - mock_storage_client.bucket.return_value = mock_bucket - mock_bucket.blob.return_value = Mock() - - mock_safe_read_gcs_file.return_value = json.dumps(sample_dependencies_dict) - mock_icon_blob.bucket.name = bucket_name - mock_icon_blob.name = "metadata/airbyte/source-test/latest/icon.svg" - mock_get_icon_blob.return_value = mock_icon_blob - - # Fail at persistence - mock_persist_entry.side_effect = Exception("Failed to persist registry entry") - - # Act & Assert - with pytest.raises(Exception): - generate_and_persist_registry_entry( - bucket_name=bucket_name, - repo_metadata_file_path=metadata_path, - registry_type=registry_type, - docker_image_tag="irrelevant", - is_prerelease=False, - ) - - # Verify error Slack notification was sent for non-client failures - if failure_stage != "gcs_client": - slack_calls = mock_send_slack.call_args_list - error_calls = [call for call in slack_calls if "FAILED" in call[0][1]] - assert len(error_calls) >= 1 - - -@pytest.mark.parametrize("registry_type", ["oss", "cloud"]) -@patch("metadata_service.registry_entry.send_slack_message") -@patch("metadata_service.registry_entry._apply_metadata_overrides") -def test_metadata_override_application_failure(mock_apply_overrides, mock_send_slack, temp_files, registry_type): - """Test exception handling when _apply_metadata_overrides fails.""" - - # Arrange - metadata_path, spec_path = temp_files - bucket_name = "test-bucket" - - # Mock failure during metadata override application - mock_apply_overrides.side_effect = Exception("Failed to apply metadata overrides") - - # Act & Assert - with pytest.raises(Exception): - generate_and_persist_registry_entry( - bucket_name=bucket_name, - repo_metadata_file_path=metadata_path, - registry_type=registry_type, - docker_image_tag="irrelevant", - is_prerelease=False, - ) - - # Verify error Slack notification was sent - slack_calls = mock_send_slack.call_args_list - error_calls = [call for call in slack_calls if "FAILED" in call[0][1]] - assert len(error_calls) >= 1 - - -@pytest.mark.parametrize("registry_type", ["oss", "cloud"]) -@patch("metadata_service.registry_entry.send_slack_message") -@patch("metadata_service.registry_entry._persist_connector_registry_entry") -@patch("metadata_service.registry_entry._get_icon_blob_from_gcs") -@patch("metadata_service.registry_entry.safe_read_gcs_file") -@patch("metadata_service.registry_entry.get_gcs_storage_client") -@patch("metadata_service.registry_entry._get_connector_type_from_registry_entry") -def test_registry_entry_model_parsing_failure( - mock_get_connector_type, - mock_gcs_client, - mock_safe_read_gcs_file, - mock_get_icon_blob, - mock_persist_entry, - mock_send_slack, - temp_files, - sample_dependencies_dict, - registry_type, -): - """Test exception handling when pydantic model parsing fails due to invalid data.""" - - # Arrange - metadata_path, spec_path = temp_files - bucket_name = "test-bucket" - - # Mock successful operations until model parsing - mock_storage_client = Mock() - mock_bucket = Mock() - mock_icon_blob = Mock() - - mock_gcs_client.return_value = mock_storage_client - mock_storage_client.bucket.return_value = mock_bucket - mock_bucket.blob.return_value = Mock() - - mock_safe_read_gcs_file.return_value = json.dumps(sample_dependencies_dict) - mock_icon_blob.bucket.name = bucket_name - mock_icon_blob.name = "metadata/airbyte/source-test/latest/icon.svg" - mock_get_icon_blob.return_value = mock_icon_blob - - # Mock model parsing failure - from metadata_service.models.generated import ConnectorRegistrySourceDefinition - - mock_get_connector_type.return_value = (Mock(), ConnectorRegistrySourceDefinition) - - # Create a mock that fails when parse_obj is called - mock_model_class = Mock() - mock_model_class.parse_obj.side_effect = Exception("Invalid model data") - mock_get_connector_type.return_value = (Mock(), mock_model_class) - - # Act & Assert - with pytest.raises(Exception): - generate_and_persist_registry_entry( - bucket_name=bucket_name, - repo_metadata_file_path=metadata_path, - registry_type=registry_type, - docker_image_tag="irrelevant", - is_prerelease=False, - ) - - -# Re-create the temp_files_disabled fixture since it was removed in refactoring -@pytest.fixture -def temp_files_disabled(sample_spec_dict): - """Create temporary metadata and spec files for disabled registry testing.""" - metadata_dict_disabled = { - "metadataSpecVersion": "1.0", - "data": { - "name": "Test Source Disabled", - "definitionId": "12345678-1234-1234-1234-123456789012", - "connectorType": "source", - "dockerRepository": "airbyte/source-test-disabled", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.airbyte.com/integrations/sources/test-disabled", - "connectorSubtype": "api", - "releaseStage": "beta", - "license": "MIT", - "registryOverrides": {"oss": {"enabled": False}, "cloud": {"enabled": False}}, - "tags": ["language:python"], - }, - } - - with tempfile.TemporaryDirectory() as temp_dir: - # Create metadata file - metadata_path = pathlib.Path(temp_dir) / "metadata.yaml" - with open(metadata_path, "w") as f: - yaml.dump(metadata_dict_disabled, f) - - # Create spec file - spec_path = pathlib.Path(temp_dir) / "spec.json" - with open(spec_path, "w") as f: - json.dump(sample_spec_dict, f) - - yield metadata_path, spec_path - - -# Re-create the temp_files fixture since it was removed in refactoring -@pytest.fixture -def temp_files(sample_spec_dict): - """Create temporary metadata and spec files for testing.""" - metadata_dict = { - "metadataSpecVersion": "1.0", - "data": { - "name": "Test Source", - "definitionId": "12345678-1234-1234-1234-123456789012", - "connectorType": "source", - "dockerRepository": "airbyte/source-test", - "dockerImageTag": "1.0.0", - "documentationUrl": "https://docs.airbyte.com/integrations/sources/test", - "connectorSubtype": "api", - "releaseStage": "beta", - "license": "MIT", - "registryOverrides": {"oss": {"enabled": True}, "cloud": {"enabled": True}}, - "tags": ["language:python"], - }, - } - - with tempfile.TemporaryDirectory() as temp_dir: - # Create metadata file - metadata_path = pathlib.Path(temp_dir) / "metadata.yaml" - with open(metadata_path, "w") as f: - yaml.dump(metadata_dict, f) - - # Create spec file - spec_path = pathlib.Path(temp_dir) / "spec.json" - with open(spec_path, "w") as f: - json.dump(sample_spec_dict, f) - - yield metadata_path, spec_path diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_spec_cache.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_spec_cache.py deleted file mode 100644 index eac4bfd2cde2..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_spec_cache.py +++ /dev/null @@ -1,102 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from unittest.mock import patch - -import pytest - -from metadata_service.spec_cache import CachedSpec, Registries, SpecCache, get_docker_info_from_spec_cache_path - - -@pytest.fixture -def mock_spec_cache(): - with ( - patch("google.cloud.storage.Client.create_anonymous_client") as MockClient, - patch("google.cloud.storage.Client.bucket") as MockBucket, - ): - # Create stub mock client and bucket - MockClient.return_value - MockBucket.return_value - - # Create a list of 4 test CachedSpecs - test_specs = [ - CachedSpec("image1", "tag-has-override", "path1", Registries.OSS), - CachedSpec("image1", "tag-has-override", "path2", Registries.CLOUD), - CachedSpec("image2", "tag-no-override", "path3", Registries.OSS), - CachedSpec("image3", "tag-no-override", "path4", Registries.CLOUD), - ] - - # Mock get_all_cached_specs to return test_specs - with patch.object(SpecCache, "get_all_cached_specs", return_value=test_specs): - yield SpecCache() - - -@pytest.mark.parametrize( - "image,tag,given_registry,expected_registry", - [ - ("image1", "tag-has-override", "OSS", Registries.OSS), - ("image1", "tag-has-override", "CLOUD", Registries.CLOUD), - ("image2", "tag-no-override", "OSS", Registries.OSS), - ("image2", "tag-no-override", "CLOUD", Registries.OSS), - ("image3", "tag-no-override", "OSS", None), - ("image3", "tag-no-override", "CLOUD", Registries.CLOUD), - ("nonexistent", "tag", "OSS", None), - ("nonexistent", "tag", "CLOUD", None), - ], -) -def test_find_spec_cache_with_fallback(mock_spec_cache, image, tag, given_registry, expected_registry): - spec = mock_spec_cache.find_spec_cache_with_fallback(image, tag, given_registry) - if expected_registry == None: - assert spec == None - else: - assert spec.docker_repository == image - assert spec.docker_image_tag == tag - assert spec.registry == expected_registry - - -@pytest.mark.parametrize( - "spec_cache_path,expected_spec", - [ - ( - "specs/airbyte/destination-azure-blob-storage/0.1.1/spec.json", - CachedSpec( - "airbyte/destination-azure-blob-storage", - "0.1.1", - "specs/airbyte/destination-azure-blob-storage/0.1.1/spec.json", - Registries.OSS, - ), - ), - ( - "specs/airbyte/destination-azure-blob-storage/0.1.1/spec.cloud.json", - CachedSpec( - "airbyte/destination-azure-blob-storage", - "0.1.1", - "specs/airbyte/destination-azure-blob-storage/0.1.1/spec.cloud.json", - Registries.CLOUD, - ), - ), - ( - "specs/airbyte/source-azure-blob-storage/1.1.1/spec.json", - CachedSpec( - "airbyte/source-azure-blob-storage", "1.1.1", "specs/airbyte/source-azure-blob-storage/1.1.1/spec.json", Registries.OSS - ), - ), - ( - "specs/faros/some-name/1.1.1/spec.json", - CachedSpec("faros/some-name", "1.1.1", "specs/faros/some-name/1.1.1/spec.json", Registries.OSS), - ), - ], -) -def test_get_docker_info_from_spec_cache_path(spec_cache_path, expected_spec): - actual_spec = get_docker_info_from_spec_cache_path(spec_cache_path) - - assert actual_spec.docker_repository == expected_spec.docker_repository - assert actual_spec.docker_image_tag == expected_spec.docker_image_tag - assert actual_spec.spec_cache_path == expected_spec.spec_cache_path - assert actual_spec.registry == expected_spec.registry - - -def test_get_docker_info_from_spec_cache_path_invalid(): - with pytest.raises(Exception): - get_docker_info_from_spec_cache_path("specs/airbyte/destination-azure-blob-storage/0.1.1/spec") diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_specs_secrets_mask.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_specs_secrets_mask.py deleted file mode 100644 index 9451117ca68e..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_specs_secrets_mask.py +++ /dev/null @@ -1,311 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import json -import os -from unittest.mock import Mock, patch - -import pytest -import yaml -from google.cloud import storage - -from metadata_service.constants import REGISTRIES_FOLDER, SPECS_SECRETS_MASK_FILE_NAME, VALID_REGISTRIES -from metadata_service.models.generated import ConnectorRegistryV0 -from metadata_service.registry import PolymorphicRegistryEntry -from metadata_service.specs_secrets_mask import _get_registries_from_gcs, _get_specs_secrets_from_registry_entries, _persist_secrets_to_gcs - - -class TestGetRegistriesFromGcs: - """Tests for _get_registries_from_gcs function.""" - - @pytest.fixture - def mock_bucket(self): - """Create a mock GCS bucket.""" - return Mock(spec=storage.Bucket) - - @pytest.fixture - def valid_registry_data(self): - """Sample valid registry data.""" - return { - "sources": [], - "destinations": [], - } - - @pytest.fixture - def mock_blob(self): - """Create a mock GCS blob.""" - return Mock(spec=storage.Blob) - - def test_get_registries_from_gcs_success(self, mock_bucket, mock_blob, valid_registry_data): - """Test successful retrieval of all valid registries from GCS.""" - mock_bucket.blob.return_value = mock_blob - - with ( - patch("metadata_service.specs_secrets_mask.safe_read_gcs_file") as mock_safe_read, - patch("metadata_service.specs_secrets_mask.ConnectorRegistryV0.parse_obj") as mock_parse, - ): - mock_safe_read.return_value = json.dumps(valid_registry_data) - mock_registry = Mock(spec=ConnectorRegistryV0) - mock_parse.return_value = mock_registry - - result = _get_registries_from_gcs(mock_bucket) - - assert len(result) == len(VALID_REGISTRIES) - assert all(registry == mock_registry for registry in result) - - expected_calls = [f"{REGISTRIES_FOLDER}/{registry}_registry.json" for registry in VALID_REGISTRIES] - actual_calls = [call[0][0] for call in mock_bucket.blob.call_args_list] - assert actual_calls == expected_calls - - assert mock_safe_read.call_count == len(VALID_REGISTRIES) - - def test_get_registries_from_gcs_multiple_registries(self, mock_bucket, mock_blob, valid_registry_data): - """Test that the function correctly processes all registries in VALID_REGISTRIES.""" - mock_bucket.blob.return_value = mock_blob - - with ( - patch("metadata_service.specs_secrets_mask.safe_read_gcs_file") as mock_safe_read, - patch("metadata_service.specs_secrets_mask.ConnectorRegistryV0.parse_obj") as mock_parse, - ): - mock_safe_read.return_value = json.dumps(valid_registry_data) - mock_registry = Mock(spec=ConnectorRegistryV0) - mock_parse.return_value = mock_registry - - result = _get_registries_from_gcs(mock_bucket) - - assert len(result) == 2 - assert mock_parse.call_count == 2 - assert mock_safe_read.call_count == 2 - - -class TestGetSpecsSecretsFromRegistryEntries: - """Tests for _get_specs_secrets_from_registry_entries function.""" - - @pytest.fixture - def mock_registry_entry(self): - """Create a mock registry entry.""" - return Mock(spec=PolymorphicRegistryEntry) - - @pytest.fixture - def single_secret_entry_data(self): - """Sample entry data with a single secret property.""" - return { - "spec": { - "connectionSpecification": { - "properties": { - "password": {"type": "string", "airbyte_secret": True}, - "username": {"type": "string", "airbyte_secret": False}, - } - } - } - } - - @pytest.fixture - def multiple_secrets_entry_data(self): - """Sample entry data with multiple secret properties.""" - return { - "spec": { - "connectionSpecification": { - "properties": { - "password": {"type": "string", "airbyte_secret": True}, - "api_key": {"type": "string", "airbyte_secret": True}, - "username": {"type": "string", "airbyte_secret": False}, - } - } - } - } - - @pytest.fixture - def nested_secrets_entry_data(self): - """Sample entry data with nested secret properties.""" - return { - "spec": { - "connectionSpecification": { - "properties": { - "oauth": { - "type": "object", - "properties": { - "client_secret": {"type": "string", "airbyte_secret": True}, - "client_id": {"type": "string", "airbyte_secret": False}, - }, - }, - "username": {"type": "string", "airbyte_secret": False}, - } - } - } - } - - @pytest.fixture - def deeply_nested_secrets_entry_data(self): - """Sample entry data with deeply nested secret properties.""" - return { - "spec": { - "connectionSpecification": { - "properties": { - "connection": { - "type": "object", - "properties": { - "auth": { - "type": "object", - "properties": { - "credentials": { - "type": "object", - "properties": {"secret_token": {"type": "string", "airbyte_secret": True}}, - } - }, - } - }, - } - } - } - } - } - - @pytest.fixture - def no_secrets_entry_data(self): - """Sample entry data with no secret properties.""" - return { - "spec": { - "connectionSpecification": { - "properties": {"username": {"type": "string", "airbyte_secret": False}, "host": {"type": "string"}} - } - } - } - - @pytest.mark.parametrize( - "entry_data_fixture,expected_secrets,description", - [ - ("single_secret_entry_data", {"password"}, "single secret property"), - ("multiple_secrets_entry_data", {"password", "api_key"}, "multiple secret properties"), - ("nested_secrets_entry_data", {"client_secret"}, "nested secret property"), - ("deeply_nested_secrets_entry_data", {"secret_token"}, "deeply nested secret property"), - ("no_secrets_entry_data", set(), "no secret properties"), - ], - ) - def test_get_specs_secrets_valid_structures(self, mock_registry_entry, entry_data_fixture, expected_secrets, description, request): - """Test extraction from various valid entry structures.""" - entry_data = request.getfixturevalue(entry_data_fixture) - - with patch("metadata_service.specs_secrets_mask.to_json_sanitized_dict") as mock_sanitize: - mock_sanitize.return_value = entry_data - - result = _get_specs_secrets_from_registry_entries([mock_registry_entry]) - - assert result == expected_secrets, f"Failed for {description}" - mock_sanitize.assert_called_once_with(mock_registry_entry) - - def test_get_specs_secrets_multiple_entries_aggregation(self, mock_registry_entry, single_secret_entry_data, nested_secrets_entry_data): - """Test that secrets from multiple entries are properly aggregated.""" - entry1 = Mock(spec=PolymorphicRegistryEntry) - entry2 = Mock(spec=PolymorphicRegistryEntry) - - with patch("metadata_service.specs_secrets_mask.to_json_sanitized_dict") as mock_sanitize: - mock_sanitize.side_effect = [single_secret_entry_data, nested_secrets_entry_data] - - result = _get_specs_secrets_from_registry_entries([entry1, entry2]) - - assert result == {"password", "client_secret"} - assert mock_sanitize.call_count == 2 - - def test_get_specs_secrets_duplicate_secrets_handling(self, single_secret_entry_data): - """Test that duplicate secret names from different entries are handled correctly.""" - entry1 = Mock(spec=PolymorphicRegistryEntry) - entry2 = Mock(spec=PolymorphicRegistryEntry) - - with patch("metadata_service.specs_secrets_mask.to_json_sanitized_dict") as mock_sanitize: - mock_sanitize.return_value = single_secret_entry_data - - result = _get_specs_secrets_from_registry_entries([entry1, entry2]) - - assert result == {"password"} - assert mock_sanitize.call_count == 2 - - def test_get_specs_secrets_empty_entries_list(self): - """Test behavior with empty entries list.""" - result = _get_specs_secrets_from_registry_entries([]) - - assert result == set() - - def test_get_specs_secrets_complex_real_world_structure(self, mock_registry_entry): - """Test with realistic connector specification structure.""" - complex_entry_data = { - "spec": { - "connectionSpecification": { - "properties": { - "host": {"type": "string"}, - "port": {"type": "integer"}, - "database": {"type": "string"}, - "credentials": { - "type": "object", - "oneOf": [ - { - "properties": { - "auth_type": {"type": "string", "const": "username_password"}, - "username": {"type": "string"}, - "password": {"type": "string", "airbyte_secret": True}, - } - }, - { - "properties": { - "auth_type": {"type": "string", "const": "oauth2"}, - "client_id": {"type": "string"}, - "client_secret": {"type": "string", "airbyte_secret": True}, - "refresh_token": {"type": "string", "airbyte_secret": True}, - } - }, - ], - }, - "ssl_config": { - "type": "object", - "properties": {"ssl_mode": {"type": "string"}, "client_key": {"type": "string", "airbyte_secret": True}}, - }, - } - } - } - } - - with patch("metadata_service.specs_secrets_mask.to_json_sanitized_dict") as mock_sanitize: - mock_sanitize.return_value = complex_entry_data - - result = _get_specs_secrets_from_registry_entries([mock_registry_entry]) - - expected_secrets = {"password", "client_secret", "refresh_token", "client_key"} - assert result == expected_secrets - - -class TestPersistSecretsToGcs: - """Tests for _persist_secrets_to_gcs function.""" - - @pytest.fixture - def mock_bucket(self): - """Create a mock GCS bucket.""" - return Mock(spec=storage.Bucket) - - @pytest.fixture - def mock_blob(self): - """Create a mock GCS blob.""" - mock_blob = Mock() - mock_blob.name = f"{REGISTRIES_FOLDER}/{SPECS_SECRETS_MASK_FILE_NAME}" - return mock_blob - - @pytest.mark.parametrize( - "secrets_set,expected_yaml_content,description", - [ - (set(), {"properties": []}, "empty secrets set"), - ({"password"}, {"properties": ["password"]}, "single secret"), - ({"password", "api_key", "token"}, {"properties": ["api_key", "password", "token"]}, "multiple secrets sorted"), - ({"z_secret", "a_secret", "m_secret"}, {"properties": ["a_secret", "m_secret", "z_secret"]}, "secrets sorted alphabetically"), - ], - ) - def test_persist_secrets_to_gcs_various_secret_sets(self, mock_bucket, mock_blob, secrets_set, expected_yaml_content, description): - """Test persistence with different secret set sizes and contents.""" - mock_bucket.blob.return_value = mock_blob - - _persist_secrets_to_gcs(secrets_set, mock_bucket) - - mock_bucket.blob.assert_called_once_with(f"{REGISTRIES_FOLDER}/{SPECS_SECRETS_MASK_FILE_NAME}") - mock_blob.upload_from_string.assert_called_once() - uploaded_content = mock_blob.upload_from_string.call_args[0][0] - parsed_yaml = yaml.safe_load(uploaded_content) - assert parsed_yaml == expected_yaml_content, f"Failed for {description}" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_stale_metadata_report.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_stale_metadata_report.py deleted file mode 100644 index c5e294dafd53..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_stale_metadata_report.py +++ /dev/null @@ -1,238 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -import datetime -from unittest.mock import Mock, patch - -import pytest -import yaml as real_yaml - -from metadata_service.constants import PUBLISH_GRACE_PERIOD -from metadata_service.stale_metadata_report import ( - _generate_stale_metadata_report, - _get_latest_metadata_entries_on_gcs, - _get_latest_metadata_versions_on_github, - _is_younger_than_grace_period, - generate_and_publish_stale_metadata_report, -) - -pytest_plugins = ["tests.fixtures.stale_metadata_report_fixtures"] - - -def _mock_commits(old_datetime): - commit = Mock() - commit.commit = Mock() - commit.commit.author = Mock() - commit.commit.author.date = old_datetime - return [commit] - - -def mock_get(url, yaml_responses): - mock_response = Mock() - mock_response.text = yaml_responses[url] - mock_response.raise_for_status = Mock() - return mock_response - - -def mock_safe_load(yaml_text: str): - return real_yaml.safe_load(yaml_text) - - -@pytest.mark.parametrize( - "hours_offset,expected_result", - [ - (-1, False), - (0, False), - (1, True), - ], -) -def test_is_younger_than_grace_period_parameterized(hours_offset, expected_result): - """Test _is_younger_than_grace_period with different time offsets relative to grace period.""" - grace_period_marker = datetime.datetime.now(datetime.timezone.utc) - PUBLISH_GRACE_PERIOD - test_datetime = grace_period_marker + datetime.timedelta(hours=hours_offset) - - mock_last_modified_at_date_time = test_datetime - - result = _is_younger_than_grace_period(mock_last_modified_at_date_time) - assert result == expected_result - - -@pytest.mark.parametrize( - "github_versions,gcs_versions,expected_stale_count,description", - [ - ({}, {}, 0, "empty mappings"), - ({"connector-a": "1.0.0"}, {"connector-a": "1.0.0"}, 0, "identical versions"), - ({"connector-a": "1.1.0"}, {"connector-a": "1.0.0"}, 1, "github newer than gcs"), - ({"connector-a": "1.0.0"}, {}, 1, "missing from gcs"), - ({}, {"connector-a": "1.0.0"}, 0, "missing from github - not reported"), - ({"connector-a": "1.1.0", "connector-b": "2.0.0"}, {"connector-a": "1.0.0", "connector-b": "2.0.0"}, 1, "mixed scenarios"), - ], -) -def test_generate_stale_metadata_report_parameterized(github_versions, gcs_versions, expected_stale_count, description): - """Test _generate_stale_metadata_report with different version mapping scenarios.""" - result_df = _generate_stale_metadata_report(github_versions, gcs_versions) - - assert len(result_df) == expected_stale_count, f"Failed for {description}" - - if expected_stale_count > 0: - expected_columns = ["connector", "master_version", "gcs_version"] - assert list(result_df.columns) == expected_columns, f"Incorrect columns for {description}" - - for _, row in result_df.iterrows(): - connector = row["connector"] - github_version = row["master_version"] - gcs_version = row["gcs_version"] - - assert connector in github_versions, f"Connector {connector} not in GitHub versions" - assert github_versions[connector] == github_version, f"GitHub version mismatch for {connector}" - assert github_version != gcs_version, f"Versions should be different for stale connector {connector}" - - -def test_get_latest_metadata_versions_on_github_success(mock_github_files, mock_yaml_responses): - """Test _get_latest_metadata_versions_on_github successfully retrieves and filters metadata.""" - with ( - patch("os.getenv") as mock_getenv, - patch("metadata_service.stale_metadata_report.Auth") as mock_auth, - patch("metadata_service.stale_metadata_report.Github") as mock_github, - patch("metadata_service.stale_metadata_report.requests") as mock_requests, - patch("metadata_service.stale_metadata_report.yaml") as mock_yaml, - ): - mock_getenv.return_value = "test-github-token" - mock_auth.Token.return_value = Mock() - - mock_github_client = Mock() - mock_repo = Mock() - mock_github.return_value = mock_github_client - mock_github_client.get_repo.return_value = mock_repo - - # Configure search_code to return our mock file list - mock_github_client.search_code.return_value = mock_github_files - - # Each call to repo.get_commits(path=...) should return a commit list with an old datetime - old_datetime = datetime.datetime.now(datetime.timezone.utc) - PUBLISH_GRACE_PERIOD - datetime.timedelta(hours=1) - - mock_repo.full_name = "airbyte/airbyte" - mock_repo.get_commits.side_effect = lambda path: _mock_commits(old_datetime) - - mock_requests.get.side_effect = lambda url: mock_get(url, mock_yaml_responses) - - mock_yaml.safe_load = mock_safe_load - - result = _get_latest_metadata_versions_on_github() - - expected_result = {"airbyte/source-test-1": "1.0.0"} - - assert result == expected_result - mock_getenv.assert_called_once_with("GITHUB_TOKEN") - mock_github_client.get_repo.assert_called_once() - mock_github_client.search_code.assert_called_once() - assert mock_requests.get.call_count == 3 - - -def test_get_latest_metadata_entries_on_gcs_success(mock_gcs_blobs): - """Test _get_latest_metadata_entries_on_gcs successfully retrieves metadata from GCS.""" - with ( - patch("metadata_service.stale_metadata_report.get_gcs_storage_client") as mock_get_client, - patch("metadata_service.stale_metadata_report.yaml") as mock_yaml, - ): - mock_storage_client = Mock() - mock_bucket = Mock() - mock_get_client.return_value = mock_storage_client - mock_storage_client.bucket.return_value = mock_bucket - mock_bucket.list_blobs.return_value = mock_gcs_blobs - - mock_yaml.safe_load = mock_safe_load - - result = _get_latest_metadata_entries_on_gcs("test-bucket") - - expected_result = {"airbyte/source-gcs-1": "1.2.0", "airbyte/source-gcs-2": "2.1.0", "airbyte/destination-gcs-1": "3.0.0"} - - assert result == expected_result - mock_get_client.assert_called_once() - mock_storage_client.bucket.assert_called_once_with("test-bucket") - mock_bucket.list_blobs.assert_called_once() - assert all(blob.download_as_bytes.called for blob in mock_gcs_blobs) - - -def test_generate_and_publish_stale_metadata_report_with_stale_data(): - """Test main workflow when stale metadata is detected.""" - with ( - patch("metadata_service.stale_metadata_report.STALE_REPORT_CHANNEL", "123456789"), - patch("metadata_service.stale_metadata_report.PUBLISH_UPDATE_CHANNEL", "987654321"), - patch("metadata_service.stale_metadata_report._get_latest_metadata_versions_on_github") as mock_github, - patch("metadata_service.stale_metadata_report._get_latest_metadata_entries_on_gcs") as mock_gcs, - patch("metadata_service.stale_metadata_report.send_slack_message", return_value=(True, None)) as mock_slack, - patch("pandas.DataFrame.to_markdown") as mock_to_markdown, - ): - mock_github.return_value = {"connector-a": "2.0.0", "connector-b": "1.5.0"} - mock_gcs.return_value = {"connector-a": "1.0.0", "connector-b": "1.5.0"} - mock_to_markdown.return_value = ( - "| connector | master_version | gcs_version |\n|-----------|----------------|-------------|\n| connector-a | 2.0.0 | 1.0.0 |" - ) - - result = generate_and_publish_stale_metadata_report("test-bucket") - - assert result == (True, None) - assert mock_slack.call_count == 2 - - alert_call = mock_slack.call_args_list[0] - assert "123456789" in str(alert_call) - assert "Stale metadata detected" in alert_call[0][1] - - report_call = mock_slack.call_args_list[1] - assert "123456789" in str(report_call) - assert report_call[1]["enable_code_block_wrapping"] is True - - -def test_generate_and_publish_stale_metadata_report_no_stale_data(): - """Test main workflow when no stale metadata is detected.""" - with ( - patch("metadata_service.stale_metadata_report.STALE_REPORT_CHANNEL", "123456789"), - patch("metadata_service.stale_metadata_report.PUBLISH_UPDATE_CHANNEL", "987654321"), - patch("metadata_service.stale_metadata_report._get_latest_metadata_versions_on_github") as mock_github, - patch("metadata_service.stale_metadata_report._get_latest_metadata_entries_on_gcs") as mock_gcs, - patch("metadata_service.stale_metadata_report.send_slack_message", return_value=(True, None)) as mock_slack, - ): - mock_github.return_value = {"connector-a": "1.0.0", "connector-b": "1.5.0"} - mock_gcs.return_value = {"connector-a": "1.0.0", "connector-b": "1.5.0"} - - result = generate_and_publish_stale_metadata_report("test-bucket") - - assert result == (True, None) - mock_slack.assert_called_once() - - success_call = mock_slack.call_args_list[0] - assert "987654321" in str(success_call) - assert "No stale metadata" in success_call[0][1] - - -def test_generate_and_publish_stale_metadata_report_large_stale_report(): - """Test main workflow with a large number of stale connectors.""" - large_github_data = {f"airbyte/connector-{i:04d}": f"2.{i % 10}.0" for i in range(100)} - large_gcs_data = {f"airbyte/connector-{i:04d}": f"1.{i % 10}.0" for i in range(100)} - - with ( - patch("metadata_service.stale_metadata_report._get_latest_metadata_versions_on_github") as mock_github, - patch("metadata_service.stale_metadata_report._get_latest_metadata_entries_on_gcs") as mock_gcs, - patch("metadata_service.stale_metadata_report.send_slack_message", return_value=(True, None)) as mock_slack, - patch("pandas.DataFrame.to_markdown") as mock_to_markdown, - patch("metadata_service.stale_metadata_report.STALE_REPORT_CHANNEL", "123456789"), - patch("metadata_service.stale_metadata_report.PUBLISH_UPDATE_CHANNEL", "987654321"), - ): - mock_github.return_value = large_github_data - mock_gcs.return_value = large_gcs_data - mock_to_markdown.return_value = "Large markdown table with 100 stale connectors..." - - result = generate_and_publish_stale_metadata_report("test-bucket") - - assert result == (True, None) - assert mock_slack.call_count == 2 - - alert_call = mock_slack.call_args_list[0] - assert "123456789" in str(alert_call) - assert "Stale metadata detected" in alert_call[0][1] - - report_call = mock_slack.call_args_list[1] - assert "123456789" in str(report_call) - assert report_call[1]["enable_code_block_wrapping"] is True diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_transform.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_transform.py deleted file mode 100644 index 47d056808104..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_transform.py +++ /dev/null @@ -1,89 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import pathlib - -import yaml - -from metadata_service.models import transform -from metadata_service.models.generated.ConnectorMetadataDefinitionV0 import ConnectorMetadataDefinitionV0 - - -def get_all_dict_key_paths(dict_to_traverse, key_path=""): - """Get all paths to keys in a dict. - - Args: - dict_to_traverse (dict): A dict. - - Returns: - list: List of paths to keys in the dict. e.g ["data.name", "data.version", "data.meta.url""] - """ - if not isinstance(dict_to_traverse, dict): - return [key_path] - - key_paths = [] - for key, value in dict_to_traverse.items(): - new_key_path = f"{key_path}.{key}" if key_path else key - key_paths += get_all_dict_key_paths(value, new_key_path) - - return key_paths - - -def have_same_keys(dict1, dict2, omitted_keys=None): - """Check if two dicts have the same keys, ignoring specified keys in the second dict. - - Args: - dict1 (dict): A dict. - dict2 (dict): A dict. - omitted_keys (list, optional): List of keys to ignore in dict2. - - Returns: - tuple: (bool, list) - True if the dicts have the same keys (considering omissions), - and a list of keys that are different or omitted. - """ - if omitted_keys is None: - omitted_keys = [] - - keys1 = set(get_all_dict_key_paths(dict1)) - keys2 = set(get_all_dict_key_paths(dict2)) - - # Determine the difference in keys - different_keys = list(keys1.symmetric_difference(keys2)) - - # Remove omitted keys - different_keys = [key for key in different_keys if key not in omitted_keys] - - return len(different_keys) == 0, different_keys - - -def test_transform_to_json_does_not_mutate_keys(valid_metadata_upload_files, valid_metadata_yaml_files): - all_valid_metadata_files = valid_metadata_upload_files + valid_metadata_yaml_files - - fields_with_defaults = [ - "data.supportsRefreshes", - "data.supportsFileTransfer", - "data.supportsDataActivation", - "data.releases.rolloutConfiguration.enableProgressiveRollout", - "data.releases.rolloutConfiguration.initialPercentage", - "data.releases.rolloutConfiguration.maxPercentage", - "data.releases.rolloutConfiguration.advanceDelayMinutes", - "data.releases.breakingChanges.2.0.0.deadlineAction", - "data.ab_internal.isEnterprise", - "data.ab_internal.requireVersionIncrementsInPullRequests", - ] - - for file_path in all_valid_metadata_files: - metadata_file_path = pathlib.Path(file_path) - original_yaml_text = metadata_file_path.read_text() - - metadata_yaml_dict = yaml.safe_load(original_yaml_text) - metadata = ConnectorMetadataDefinitionV0.parse_obj(metadata_yaml_dict) - metadata_json_dict = transform.to_json_sanitized_dict(metadata) - - new_yaml_text = yaml.safe_dump(metadata_json_dict, sort_keys=False) - new_yaml_dict = yaml.safe_load(new_yaml_text) - - # assert same keys in both dicts, deep compare, and that the values are the same - is_same, different_keys = have_same_keys(metadata_yaml_dict, new_yaml_dict, fields_with_defaults) - assert is_same, f"Different keys found: {different_keys}" diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_validators/__init__.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_validators/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/metadata_service/lib/tests/test_validators/test_metadata_validators.py b/airbyte-ci/connectors/metadata_service/lib/tests/test_validators/test_metadata_validators.py deleted file mode 100644 index 29c686def430..000000000000 --- a/airbyte-ci/connectors/metadata_service/lib/tests/test_validators/test_metadata_validators.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import pytest -import requests -import semver -import yaml - -from metadata_service.docker_hub import get_latest_version_on_dockerhub -from metadata_service.models.generated.ConnectorMetadataDefinitionV0 import ConnectorMetadataDefinitionV0 -from metadata_service.validators import metadata_validator - - -@pytest.fixture -def metadata_definition(): - return ConnectorMetadataDefinitionV0.parse_obj( - { - "data": { - "ab_internal": {"ql": 300, "sl": 100}, - "allowedHosts": {"hosts": []}, - "connectorBuildOptions": { - "baseImage": "docker.io/airbyte/python-connector-base:2.0.0@sha256:c44839ba84406116e8ba68722a0f30e8f6e7056c726f447681bb9e9ece8bd916" - }, - "connectorSubtype": "api", - "connectorType": "source", - "definitionId": "dfd88b22-b603-4c3d-aad7-3701784586b1", - "dockerImageTag": "6.2.18-rc.1", - "dockerRepository": "airbyte/source-faker", - "documentationUrl": "https://docs.airbyte.com/integrations/sources/faker", - "githubIssueLabel": "source-faker", - "icon": "faker.svg", - "license": "MIT", - "name": "Sample Data (Faker)", - "registryOverrides": {"cloud": {"enabled": True}, "oss": {"enabled": True}}, - "releaseStage": "beta", - "releases": { - "rolloutConfiguration": {"enableProgressiveRollout": True}, - "breakingChanges": { - "4.0.0": {"message": "This is a breaking change message", "upgradeDeadline": "2023-07-19"}, - "5.0.0": { - "message": "ID and products.year fields are changing to be integers instead of floats.", - "upgradeDeadline": "2023-08-31", - }, - "6.0.0": {"message": "Declare 'id' columns as primary keys.", "upgradeDeadline": "2024-04-01"}, - }, - }, - "remoteRegistries": {"pypi": {"enabled": True, "packageName": "airbyte-source-faker"}}, - "resourceRequirements": { - "jobSpecific": [{"jobType": "sync", "resourceRequirements": {"cpu_limit": "4.0", "cpu_request": "1.0"}}] - }, - "suggestedStreams": {"streams": ["users", "products", "purchases"]}, - "supportLevel": "community", - "tags": ["language:python", "cdk:python"], - "connectorTestSuitesOptions": [ - { - "suite": "liveTests", - "testConnections": [{"name": "faker_config_dev_null", "id": "73abc3a9-3fea-4e7c-b58d-2c8236464a95"}], - }, - {"suite": "unitTests"}, - { - "suite": "acceptanceTests", - "testSecrets": [ - { - "name": "SECRET_SOURCE-FAKER_CREDS", - "fileName": "config.json", - "secretStore": {"type": "GSM", "alias": "airbyte-connector-testing-secret-store"}, - } - ], - }, - ], - }, - "metadataSpecVersion": "1.0", - } - ) - - -@pytest.mark.slow -@pytest.mark.parametrize( - "latest_version, current_version,should_pass_validation", - [("1.0.0", "0.1.0", False), ("1.0.0", "1.0.0", True), ("1.0.0", "1.1.0", True)], -) -def test_validate_docker_image_tag_is_not_decremented(mocker, metadata_definition, latest_version, current_version, should_pass_validation): - mocker.patch.object(metadata_validator, "get_latest_version_on_dockerhub", return_value=latest_version) - metadata_definition.data.dockerImageTag = current_version - passed_validation, _ = metadata_validator.validate_docker_image_tag_is_not_decremented(metadata_definition, None) - assert passed_validation == should_pass_validation - - -@pytest.fixture -def current_version(metadata_definition): - return metadata_definition.data.dockerImageTag - - -@pytest.fixture -def decremented_version(current_version): - version_info = semver.VersionInfo.parse(current_version) - if version_info.major > 0: - patched_version_info = version_info.replace(major=version_info.major - 1) - elif version_info.minor > 0: - patched_version_info = version_info.replace(major=version_info.minor - 1) - elif version_info.patch > 0: - patched_version_info = version_info.replace(patch=version_info.patch - 1) - else: - raise ValueError(f"Version {version_info} can't be decremented to prepare our test") - return str(patched_version_info) - - -@pytest.fixture -def incremented_version(current_version): - version_info = semver.VersionInfo.parse(current_version) - if version_info.major > 0: - patched_version_info = version_info.replace(major=version_info.major + 1) - elif version_info.minor > 0: - patched_version_info = version_info.replace(major=version_info.minor + 1) - elif version_info.patch > 0: - patched_version_info = version_info.replace(patch=version_info.patch + 1) - else: - raise ValueError(f"Version {version_info} can't be incremented to prepare our test") - return str(patched_version_info) - - -@pytest.mark.slow -def test_validation_fail_on_docker_image_tag_decrement(metadata_definition, decremented_version): - current_version = metadata_definition.data.dockerImageTag - - metadata_definition.data.dockerImageTag = decremented_version - success, error_message = metadata_validator.validate_docker_image_tag_is_not_decremented(metadata_definition, None) - assert not success - expected_prefix = f"The dockerImageTag value ({decremented_version}) can't be decremented: it should be equal to or above" - assert error_message.startswith(expected_prefix), error_message - - -@pytest.mark.slow -def test_validation_pass_on_docker_image_tag_increment(metadata_definition, incremented_version): - metadata_definition.data.dockerImageTag = incremented_version - success, error_message = metadata_validator.validate_docker_image_tag_is_not_decremented(metadata_definition, None) - assert success - assert error_message is None - - -@pytest.mark.slow -def test_validation_pass_on_same_docker_image_tag(mocker, metadata_definition): - mocker.patch.object(metadata_validator, "get_latest_version_on_dockerhub", return_value=metadata_definition.data.dockerImageTag) - success, error_message = metadata_validator.validate_docker_image_tag_is_not_decremented(metadata_definition, None) - assert success - assert error_message is None - - -@pytest.mark.slow -def test_validation_pass_on_docker_image_no_latest(capsys, metadata_definition): - metadata_definition.data.dockerRepository = "airbyte/unreleased" - success, error_message = metadata_validator.validate_docker_image_tag_is_not_decremented(metadata_definition, None) - captured = capsys.readouterr() - assert ( - "https://registry.hub.docker.com/v2/repositories/airbyte/unreleased/tags returned a 404. The connector might not be released yet." - in captured.out - ) - assert success - assert error_message is None diff --git a/airbyte-ci/connectors/pipelines/.gitignore b/airbyte-ci/connectors/pipelines/.gitignore deleted file mode 100644 index d17bbbefa193..000000000000 --- a/airbyte-ci/connectors/pipelines/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -pipeline_reports -.venv diff --git a/airbyte-ci/connectors/pipelines/CONTRIBUTING.md b/airbyte-ci/connectors/pipelines/CONTRIBUTING.md deleted file mode 100644 index 5b0549ca0d85..000000000000 --- a/airbyte-ci/connectors/pipelines/CONTRIBUTING.md +++ /dev/null @@ -1,366 +0,0 @@ -## What is `airbyte-ci`? - -`airbyte-ci` is a CLI written as a python package which is made to execute CI operations on the `airbyte` repo. It is heavily using the [Dagger](https://dagger.cloud/) library to build and orchestrate Docker containers programatically. It enables a centralized and programmatic approach at executing CI logics which can seamlessly run both locally and in remote CI environments. - -You can read more why we are using Dagger and the benefit it has provided in this [blog post](https://dagger.io/blog/airbyte-use-case) - -## When is a contribution to `airbyte-ci` a good fit for your use case? - -- When you want to make global changes to connectors artifacts and build logic. -- When you want to execute something made to run both in CI or for local development. As airbyte-ci logic relies on container orchestration you can have reproducible environment and execution both locally and in a remote CI environment. -- When you want to orchestrate the tests and release of an internal package in CI. - -## Who can I ask help from? - -The tool has been maintained by multiple Airbyters. -Our top contributors who can help you figuring the best approach to implement your use case are: - -- [@alafanechere](https://github.com/alafanechere). -- [@postamar](https://github.com/postamar) -- [@erohmensing](https://github.com/erohmensing) -- [@bnchrch](https://github.com/bnchrch) -- [@stephane-airbyte](https://github.com/stephane-airbyte) - -## Where is the code? - -The code is currently available in the `airbytehq/airbyte` repo under [ `airbyte-ci/connectors/pipelines` ](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines) - -## What use cases it currently supports - -According to your need you might want to introduce a new logic to an existing flow or create a new one. -Here are the currently supported use cases. Feel free to grab them as example if you want to craft a new flow, or modify an existing one. If you are not sure about which direction to take feel free to ask advices (see [*Who Can I ask help?*](## Who can I ask help from?) from section). - -| Command group | Feature | Command | Entrypoint path | -| ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Running test suites connectors | `airbyte-ci connectors test` | [`airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Building connectors | `airbyte-ci connectors build` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/build_image/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Publishing connectors | `airbyte-ci connectors publish` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/publish/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Bumping connectors versions | `airbyte-ci connectors bump_version` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/bump_version/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Listing connectors | `airbyte-ci connectors list` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/list/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Migrate a connector to use our base image | `airbyte-ci connectors migrate_to_base_image` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/migrate_to_base_image/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_base_image/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Migrate a connector to use `poetry` as a package manager | `airbyte-ci connectors migrate_to_poetry` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/migrate_to_poetry/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_poetry/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Upgrade the base image used by a connector | `airbyte-ci connectors upgrade_base_image` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/upgrade_base_image/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_base_image/commands.py) | -| [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) | Upgrade the CDK version used by a connector | `airbyte-ci connectors upgrade_cdk` | [`airbyte-ci/connectors/pipelines/airbyte_ci/connectors/upgrade_cdk/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/commands.py) | -| [`format`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py#L32) | Check that the full repo is correctly formatted | `airbyte-ci format check all` | [`airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py#L78) | -| [`format`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py#L32) | Format the whole repo | `airbyte-ci format fix all` | [`airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/format/commands.py#L101) | -| [`test`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py#L107) | Run tests on internal poetry packages | `airbyte-ci test` | [`airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py#L107) | -| [`poetry`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/commands.py#L33) | Publish a poetry package to PyPi | `airbyte-ci poetry publish` | [`airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/commands.py`](https:github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/commands.py#L69) | - -## How to install the package for development - -There are multiple way to have dev install of the tool. Feel free to grab the one you prefer / which works for you. -**Please note that all the install mode lead to an editable install. There's no need to re-install the tool following a code change**. - -### System requirements - -- `Python` > 3.10 -- [`Poetry`](https://python-poetry.org/) or [`pipx`](https://github.com/pypa/pipx) - -### Installation options - -There are many ways to install Python tools / packages. - -For most users we recommend you use `make` but `pipx` and `poetry` are also viable options - -#### With `make` - -```bash - # From airbyte repo root: - make tools.airbyte-ci-dev.install -``` - -#### With `pipx` - -```bash -# From airbyte-ci/connectors/pipelines: -pipx install --editable --force . -``` - -#### With `poetry` - -⚠️ This places you in a python environment specific to airbyte-ci. This can be a problem if you are developing airbyte-ci and testing/using your changes in another python project. - -```bash -# From airbyte-ci/connectors/pipelines -poetry install -poetry shell -``` - -## Main libraries used in the tool - -### [Click](https://click.palletsprojects.com/en/8.1.x/) - -This is a python light CLI framework we use to declare entrypoint. You'll interact with it if you have to deal with commands, command groups, option, arguments etc. - -### [Dagger](https://dagger-io.readthedocs.io/en/sdk-python-v0.9.6/) - -This is an SDK to build, execute and interact with Docker containers in Python. It's basically a nice API on top of [BuildKit](https://docs.docker.com/build/buildkit/). We use containers to wrap the majority of `airbyte-ci` operations as it allows us to: - -- Execute language agnostic operations: you can execute bash commands, gradle tasks, etc. in containers with Python. Pure magic! -- Benefit from caching by default. You can consider a Dagger operation a "line in a Dockerfile". Each operation is cached by BuildKit if the inputs of the operation did not change. -- As Dagger exposes async APIs we can easily implement concurrent logic. This is great for performance. - -**Please note that we are currently using v0.9.6 of Dagger. The library is under active development so please refer to [this specific version documentation](https://dagger-io.readthedocs.io/en/sdk-python-v0.9.6/) if you want an accurate view of the available APIs.** - -### [anyio](https://anyio.readthedocs.io/en/stable/basics.html) / [asyncer](https://asyncer.tiangolo.com/) - -As Dagger exposes async APIs we use `anyio` (and the `asyncer` wrapper sometimes) to benefit from [structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency). -**Reading the docs of these libraries is a must if you want to declare concurrent logics.** - -## Design principles - -_The principles set out below are ideals, but the first iterations on the project did not always respect them. Don't be surprised if you see code that contradicts what we're about to say (tech debt...)._ - -### `airbyte-ci` is _just_ an orchestrator - -Ideally the steps declared in airbyte-ci pipeline do not contain any business logic themselves. They call external projects, within containers, which contains the business logic. - -Following this principles will help in decoupling airbyte-ci from other project and make it agnostic from business logics that can quickly evolve. Not introducing business logic to the tool encourages abstraction efforts that can lead to future leverage. - -Maintaining business logic in smaller projects also increases velocity, as introducing a new logic would not require changing airbyte-ci and, which is already a big project in terms of code lines. - -#### Good examples of this principle - -- `connectors-qa`: We want to run specific static checks on all our connectors: we introduced a specific python package ([`connectors-qa`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/connectors_qa/README.md#L1))which declares and run the checks on connectors. We orchestrate the run of this package inside the [QaChecks](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/common.py#L122) step. This class is just aware of the tool location, its entry point, and what has to be mounted to the container for the command to run. -- Internal package testing: We expose an `airbyte-ci test` command which can run a CI pipeline on an internal poetry package. The pipeline logic is declared at the package level with `poe` tasks in the package `pyproject.toml`. `airbyte-ci` is made aware about what is has to run by parsing the content of the `[tool.airbyte_ci]` section of the `pyproject.toml`file. [Example](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/ci_credentials/pyproject.toml#L35) - -### No command or pipeline should be language specific - -We oftentimes have to introduce new flows for connectors / CDK. Even if the need for this flow is currently only existing for a specific connector language (Python / Java), we should build language agnostic command and pipelines. The language specific implementation should come at the most downstream level of the pipeline and we should leverage factory like patterns to get language agnostic pipelines. - -#### Good example of this principle: our build command - -The `airbyte-ci connectors build` command can build multiple connectors of different languages in a single execution. -The higher level [`run_connector_build_pipeline` function](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/__init__.py#L36) is connector language agnostic and calls connector language specific sub pipelines according to the connector language. -We have per-language submodules in which language specific `BuildConnectorImages` classes are implemented: - -- [`python_connectors.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/python_connectors.py) -- [`java_connectors.py`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/java_connectors.py#L14) - -### Pipelines are functions, steps are classes - -A pipeline is a function: - -- instantiating and running steps -- collecting step results and acting according to step results -- returning a report - -A step is a class which inheriting from the `Step` base class: - -- Can be instantiated with parameters -- Has a `_run` method which: - - Performs one or multiple operations according to input parameter and context values - - Returns a `StepResult` which can have a `succeeded`, `failed` or `skipped` `StepStatus` - -**Steps should ideally not call other steps and the DAG of steps can be understand by reading the pipeline function.** - -#### Step examples: - -- [`PytestStep`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/python_connectors.py#L29) -- [`GradleTask`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/gradle.py#L21) - -#### Pipelines examples: - -- [`run_connector_publish_pipeline`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/pipeline.py#L296) -- [`run_connector_test_pipeline`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py#L48) - -## Main classes - -### [`PipelineContext`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/models/contexts/pipeline_context.py#L33) (and [`ConnectorContext`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/context.py#L33), [`PublishConnectorContext`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/context.py#L19)) - -Pipeline contexts are instantiated on each command execution and produced according to the CLI inputs. We populate this class with global configuration, helpers and attributes that are accessed during pipeline and step execution. - -It has, for instance, the following attributes: - -- The dagger client -- The list of modified files on the branch -- A `connector` attribute -- A `get_connector_dir` method to interact with the connector -- Global secrets to connect to protected resources -- A `is_ci` attribute to know if the current execution is a local or CI one. - -We use `PipelineContext` with context managers so that we can easily handle setup and teardown logic of context (like producing a `Report`) - -### [`Step`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/models/steps.py#L189) - -`Step` is an abstract class. It is meant to be inherited for implementation of pipeline steps which are use case specific. `Step` exposes a public `run` method which calls a private `_run` method wrapped with progress logger and a retry mechanism. - -When declaring a `Step` child class you are expected to: - -- declare a `title` attribute or `property` -- implement the `_run` method which should return a `StepResult` object. You are free to override the `Step` methods if needed. - -### [`Result` / `StepResult`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/models/steps.py#L86) - -The `Result` class (and its subclasses) are meant to characterize the result of a `Step` execution. -`Result` objects are build with: - -- `StepStatus` (success/failure/skipped) -- `stderr`: The standard error of the operation execution -- `stdout` : The standard output of the operation execution -- `excinfo`: An Exception instance if you want to handle an operation error -- `output`: Any object you'd like to attach to the result for reuse in other Steps -- `artifacts`: Any object produced by the Step that you'd like to attach to the `Report` - -### [`Report`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/models/reports.py#L34) - -A `Report` object is instantiated on `PipelineContext` teardown with a collection of step results. It is meant to persists execution results as json / html locally and in remote storage to share them with users or other automated processes. - -## Github Action orchestration - -A benefit of declaring CI logic in a centralized python package is that our CI logic can be agnostic from the CI platform it runs on. We are currently using GitHub actions. This section will explain how we run `airbyte-ci` in GitHub actions. - -### Multiple workflows re-using the same actions - -Each CI use case has its own Github Action worfklow: - -- [Connector testing](https://github.com/airbytehq/airbyte/blob/master/.github/workflows/connectors_tests.yml#L1) -- [Connector publish](https://github.com/airbytehq/airbyte/blob/master/.github/workflows/publish_connectors.yml#L1) -- [Internal package testing](https://github.com/airbytehq/airbyte/blob/master/.github/workflows/airbyte-ci-tests.yml#L1) -- etc. - -They all use the [`run-airbyte-ci` re-usable action](https://github.com/airbytehq/airbyte/blob/master/.github/actions/run-airbyte-ci/action.yml#L1)to which they provide the `airbyte-ci` command the workflow should run and other environment specific options. - -The `run-airbyte-ci` action does the following: - -- [Pull Dagger image and install airbyte-ci from binary (or sources if the tool was changed on the branch)](https://github.com/airbytehq/airbyte/blob/master/.github/actions/run-airbyte-ci/action.yml#L105) -- [Run the airbyte-ci command passed as an input with other options also passed as inputs](https://github.com/airbytehq/airbyte/blob/main/.github/actions/run-airbyte-ci/action.yml#L111) - -## A full example: breaking down the execution flow of a connector test pipeline - -Let's describe and follow what happens when we run: -`airbyte-ci connectors --modified test` - -**This command is meant to run tests on connectors that were modified on the branch.** -Let's assume I modified the `source-faker` connector. - -### 1. The `airbyte-ci` command group - -On command execution the [`airbyte-ci` command group](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/cli/airbyte_ci.py#L186) acts as the main entrypoint. It is: - -- Provisioning the click context object with options values, that can be accessed in downstream commands. -- Checking if the local docker configuration is correct -- Wrapping the command execution with `dagger run` to get their nice terminal UI (unless `--disable-dagger-run` is passed) - -### 2. The `connectors` command subgroup - -After passing through the top level command group, click dispatches the command execution to the [`connectors`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py#L237) command subgroup. -It continues to populate the click context with other connectors specific options values which will be consumed by the final `test` command. -**It also computes the list of modified files on the branch and attach this list to the click context.** The `get_modified_files` function basically performs a `git diff` between the current branch and the `--diffed-branch` . - -### 3. Reaching the `test` command - -After going through the command groups we finally reach the actual command the user wants to execute: the [`test` command](https://github.com/airbytehq/airbyte/blob/main/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py#L72). - -This function: - -- Sends a pending commit status check to Github when we are running in CI -- Determines which steps should be skipped or kept according to user inputs (by building a `RunStepOptions` object) -- Instantiate one `ConnectorContext` per connector under test: we only modified `source-faker` so we'll have a single `ConnectorContext` to work with. -- Call `run_connectors_pipelines` with the `ConnectorContext`s and - -#### 4. Globally dispatching pipeline logic in `run_connectors_pipeline` - -[`run_connectors_pipeline`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pipeline.py#L83) gets called with all the `ConnectorContext` produced according to the user inputs and a callable which captures the pipeline logic: `run_connector_test_pipeline`. -`run_connectors_pipeline`, as its taking a pipeline callable, it has no specific pipeline logic. - -This function: - -- Instantiates the dagger client -- Create a task group to concurrently run the pipeline callable: we'd concurrently run test pipeline on multiple connectors if multiple connectors were modified. -- The concurrency of the pipeline is control via a semaphore object. - -#### 5. Actually running the pipeline in [`run_connector_test_pipeline`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py#L48) - -_Reminder: this function is called for each connector selected for testing. It takes a `ConnectorContext` and a `Semaphore` as inputs._ - -The specific steps to run in the pipeline for a connector is determined by the output of the [`get_test_steps`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py#L32) function which is building a step tree according to the connector language. - -**You can for instance check the declared step tree for python connectors [here](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/python_connectors.py#L249).**: - -```python -def get_test_steps(context: ConnectorContext) -> STEP_TREE: - """ - Get all the tests steps for a Python connector. - """ - return [ - [StepToRun(id=CONNECTOR_TEST_STEP_ID.BUILD, step=BuildConnectorImages(context))], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.UNIT, - step=UnitTests(context), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ) - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INTEGRATION, - step=IntegrationTests(context), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.PYTHON_CLI_VALIDATION, - step=PyAirbyteValidation(context), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.ACCEPTANCE, - step=AcceptanceTests(context, context.concurrent_cat), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ], - ] -``` - -After creating the step tree (a.k.a a _DAG_) it enters the `Semaphore` and `PipelineContext` context manager to execute the steps to run with `run_steps`. `run_steps` executes steps concurrently according to their dependencies. - -Once the steps are executed we get step results. We can build a `ConnectorReport` from these results. The report is finally attached to the `context` so that it gets persisted on `context` teardown. - -```python -async def run_connector_test_pipeline(context: ConnectorContext, semaphore: anyio.Semaphore) -> ConnectorReport: - """ - Compute the steps to run for a connector test pipeline. - """ - all_steps_to_run: STEP_TREE = [] - - all_steps_to_run += get_test_steps(context) - - if not context.code_tests_only: - static_analysis_steps_to_run = [ - [ - StepToRun(id=CONNECTOR_TEST_STEP_ID.VERSION_INC_CHECK, step=VersionIncrementCheck(context)), - StepToRun(id=CONNECTOR_TEST_STEP_ID.QA_CHECKS, step=QaChecks(context)), - ] - ] - all_steps_to_run += static_analysis_steps_to_run - - async with semaphore: - async with context: - result_dict = await run_steps( - runnables=all_steps_to_run, - options=context.run_step_options, - ) - - results = list(result_dict.values()) - report = ConnectorReport(context, steps_results=results, name="TEST RESULTS") - context.report = report - - return report -``` - -#### 6. `ConnectorContext` teardown - -Once the context manager is exited (when we exit the `async with context` block) the [`ConnectorContext.__aexit__` function is executed](https://github.com/airbytehq/airbyte/blob/main/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/context.py#L237) - -This function: - -- Determines the global success or failure state of the pipeline according to the StepResults -- Uploads connector secrets back to GSM if they got updated -- Persists the report to disk -- Prints the report to the console -- Uploads the report to remote storage if we're in CI -- Updates the per connector commit status check diff --git a/airbyte-ci/connectors/pipelines/README.md b/airbyte-ci/connectors/pipelines/README.md deleted file mode 100644 index 9df54aa7443b..000000000000 --- a/airbyte-ci/connectors/pipelines/README.md +++ /dev/null @@ -1,1249 +0,0 @@ -# Airbyte CI CLI - -## What is it? - -`airbyte-ci` is a command line interface to run CI/CD pipelines. The goal of this CLI is to offer -developers a tool to run these pipelines locally and in a CI context with the same guarantee. It can -prevent unnecessary commit -> push cycles developers typically go through when they when to test -their changes against a remote CI. This is made possible thanks to the use of -[Dagger](https://dagger.io), a CI/CD engine relying on Docker Buildkit to provide reproducible -builds. Our pipeline are declared with Python code, the main entrypoint is -[here](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connector_ops/connector_ops/pipelines/commands/airbyte_ci.py). -This documentation should be helpful for both local and CI use of the CLI. We indeed -[power connector testing in the CI with this CLI](https://github.com/airbytehq/airbyte/blob/master/.github/workflows/connector_integration_test_single_dagger.yml#L78). - -## How to install - -### Requirements - -- A running Docker engine with version >= 20.10.23 - -## Install or Update - -The recommended way to install `airbyte-ci` is using the [Makefile](../../../Makefile). - -```sh -# from the root of the airbyte repository -make tools.airbyte-ci.install -``` - -### Setting up connector secrets access - -If you plan to use Airbyte CI to run CAT (Connector Acceptance Tests), we recommend setting up GSM -access so that Airbyte CI can pull remote secrets from GSM. For setup instructions, see the CI -Credentials package (which Airbyte CI uses under the hood) README's -[Get GSM Access](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/ci_credentials/README.md#get-gsm-access) -instructions. - -### Updating the airbyte-ci tool - -To reinstall airbyte-ci, run the following command: - -```sh -airbyte-ci update -``` - -or if that fails, you can reinstall it with the following command: - -```sh -# from the root of the airbyte repository -make tools.airbyte-ci.install -``` - -## Checking the airbyte-ci install - -To check that airbyte-ci is installed correctly, run the following command: - -```sh -make tools.airbyte-ci.check -``` - -## Cleaning the airbyte-ci install - -To clean the airbyte-ci install, run the following command: - -```sh -make tools.airbyte-ci.clean -``` - -## Disabling telemetry - -We collect anonymous usage data to help improve the tool. If you would like to disable this, you can -set the `AIRBYTE_CI_DISABLE_TELEMETRY` environment variable to `true`. - -## Installation for development - -#### Pre-requisites - -- Poetry >= 1.1.8 -- Python >= 3.10 - -#### Installation - -If you are developing on pipelines, we recommend installing airbyte-ci with poetry: - -```bash -cd airbyte-ci/connectors/pipelines/ -poetry install -poetry env activate -cd ../../ -``` - -**Alternatively**, you can install airbyte-ci with pipx so that the entrypoint is available in your -PATH: - -```bash -make tools.airbyte-ci.install -``` - -However, this will not automatically install the dependencies for the local dependencies of -airbyte-ci, or respect the lockfile. - -Its often best to use the `poetry` steps instead. - -#### Running Tests - -From `airbyte-ci/connectors/pipelines`: - -```bash -poetry run pytest tests -``` - -You can also run a subset of tests: - -```bash -poetry run pytest pipelines/models/steps.py -``` - -More options, such as running test by keyword matching, are available - see the -[pytest CLI documentation](https://docs.pytest.org/en/6.2.x/usage.html) for all the available -options.``` - -#### Checking Code Format (Pipelines) - -```bash -poetry run ruff check pipelines -``` - -## Commands reference - -At this point you can run `airbyte-ci` commands. - -- [Airbyte CI CLI](#airbyte-ci-cli) - - [What is it?](#what-is-it) - - [How to install](#how-to-install) - - [Requirements](#requirements) - - [Install or Update](#install-or-update) - - [Setting up connector secrets access](#setting-up-connector-secrets-access) - - [Updating the airbyte-ci tool](#updating-the-airbyte-ci-tool) - - [Checking the airbyte-ci install](#checking-the-airbyte-ci-install) - - [Cleaning the airbyte-ci install](#cleaning-the-airbyte-ci-install) - - [Disabling telemetry](#disabling-telemetry) - - [Installation for development](#installation-for-development) - - [Pre-requisites](#pre-requisites) - - [Installation](#installation) - - [Running Tests](#running-tests) - - [Checking Code Format (Pipelines)](#checking-code-format-pipelines) - - [Commands reference](#commands-reference) - - [`airbyte-ci` command group](#airbyte-ci-command-group) - - [Options](#options) - - [`connectors` command subgroup](#connectors-command-subgroup) - - [Options](#options-1) - - [`connectors list` command](#connectors-list-command) - - [Examples](#examples) - - [`connectors test` command](#connectors-test-command) - - [Examples](#examples-1) - - [What it runs](#what-it-runs) - - [Options](#options-2) - - [Extra parameters](#extra-parameters) - - [`connectors build` command](#connectors-build-command) - - [What it runs](#what-it-runs-1) - - [Options](#options-3) - - [`connectors publish` command](#connectors-publish-command) - - [Examples](#examples-2) - - [Options](#options-4) - - [What it runs](#what-it-runs-2) - - [Python registry publishing](#python-registry-publishing) - - [`connectors up-to-date` command](#connectors-up-to-date-command) - - [Examples](#examples-3) - - [Other things it could do](#other-things-it-could-do) - - [`connectors bump-version` command](#connectors-bump-version-command) - - [Examples](#examples-4) - - [Arguments](#arguments) - - [`connectors upgrade_cdk` command](#connectors-upgrade_cdk-command) - - [Examples](#examples-5) - - [Arguments](#arguments-1) - - [`connectors migrate-to-base-image` command](#connectors-migrate-to-base-image-command) - - [Examples](#examples-6) - - [`connectors migrate-to-poetry` command](#connectors-migrate-to-poetry-command) - - [Examples](#examples-7) - - [`connectors migrate-to-inline-schemas` command](#connectors-migrate-to-inline-schemas-command) - - [Examples](#examples-8) - - [`connectors pull-request` command](#connectors-pull-request-command) - - [Examples](#examples-9) - - [`format` command subgroup](#format-command-subgroup) - - [Options](#options-6) - - [Examples](#examples-10) - - [`format check all` command](#format-check-all-command) - - [`format fix all` command](#format-fix-all-command) - - [`poetry` command subgroup](#poetry-command-subgroup) - - [Options](#options-7) - - [Examples](#examples-11) - - [`publish` command](#publish-command) - - [Options](#options-8) - - [`metadata` command subgroup](#metadata-command-subgroup) - - [`metadata deploy orchestrator` command](#metadata-deploy-orchestrator-command) - - [Example](#example) - - [What it runs](#what-it-runs-3) - - [`tests` command](#tests-command) - - [Options](#options-9) - - [Examples](#examples-12) - - [`migrate-to-manifest-only` command](#migrate-to-manifest-only-command) - - [Examples](#examples-13) - - [Changelog](#changelog) - - [More info](#more-info) -- [Troubleshooting](#troubleshooting) - - [Commands](#commands) - - [`make tools.airbyte-ci.check`](#make-toolsairbyte-cicheck) - - [`make tools.airbyte-ci.clean`](#make-toolsairbyte-ciclean) - - [Common issues](#common-issues) - - [`airbyte-ci` is not found](#airbyte-ci-is-not-found) - - [Development](#development) - - [`airbyte-ci` is not found](#airbyte-ci-is-not-found-1) - - [python3.10 not found](#python310-not-found) - - [Any type of pipeline failure](#any-type-of-pipeline-failure) - -### `airbyte-ci` command group - -**The main command group option has sensible defaults. In local use cases you're not likely to pass -options to the `airbyte-ci` command group.** - -#### Options - -| Option | Default value | Mapped environment variable | Description | -| ---------------------------------------------- | ------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- | -| `--yes/--y` | False | | Agrees to all prompts. | -| `--yes-auto-update/--no-auto-update` | True | | Agrees to the auto update prompts. | -| `--enable-update-check/--disable-update-check` | True | | Turns on the update check feature | -| `--enable-dagger-run/--disable-dagger-run` | `--enable-dagger-run` | | Disables the Dagger terminal UI. | -| `--is-local/--is-ci` | `--is-local` | | Determines the environment in which the CLI runs: local environment or CI environment. | -| `--git-branch` | The checked out git branch name | `CI_GIT_BRANCH` | The git branch on which the pipelines will run. | -| `--git-revision` | The current branch head | `CI_GIT_REVISION` | The commit hash on which the pipelines will run. | -| `--diffed-branch` | `master` | | Branch to which the git diff will happen to detect new or modified files. | -| `--gha-workflow-run-id` | | | GHA CI only - The run id of the GitHub action workflow | -| `--ci-context` | `manual` | | The current CI context: `manual` for manual run, `pull-request`, `nightly_builds`, `master` | -| `--pipeline-start-timestamp` | Current epoch time | `CI_PIPELINE_START_TIMESTAMP` | Start time of the pipeline as epoch time. Used for pipeline run duration computation. | -| `--show-dagger-logs/--hide-dagger-logs` | `--hide-dagger-logs` | | Flag to show or hide the dagger logs. | - -### `connectors` command subgroup - -Available commands: - -- `airbyte-ci connectors test`: Run tests for one or multiple connectors. -- `airbyte-ci connectors build`: Build docker images for one or multiple connectors. -- `airbyte-ci connectors publish`: Publish a connector to Airbyte's DockerHub. - -#### Options - -| Option | Multiple | Default value | Mapped Environment Variable | Description | -| -------------------------------------------------------------- | -------- | -------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--use-remote-secrets/--use-local-secrets` | False | | | If --use-remote-secrets, connectors configuration will be pulled from Google Secret Manager. Requires the `GCP_GSM_CREDENTIALS` environment variable to be set with a service account with permission to read GSM secrets. If --use-local-secrets the connector configuration will be read from the local connector `secrets` folder. If this flag is not used and a `GCP_GSM_CREDENTIALS` environment variable is set remote secrets will be used, local secrets will be used otherwise. | -| `--name` | True | | | Select a specific connector for which the pipeline will run. Can be used multiple times to select multiple connectors. The expected name is the connector technical name. e.g. `source-pokeapi` | -| `--support-level` | True | | | Select connectors with a specific support level: `community`, `certified`. Can be used multiple times to select multiple support levels. | -| `--metadata-query` | False | | | Filter connectors by the `data` field in the metadata file using a [simpleeval](https://github.com/danthedeckie/simpleeval) query. e.g. 'data.ab_internal.ql == 200' | -| `--use-local-cdk` | False | False | | Build with the airbyte-cdk from the local repository. " "This is useful for testing changes to the CDK. | -| `--language` | True | | | Select connectors with a specific language: `python`, `low-code`, `java`. Can be used multiple times to select multiple languages. | -| `--modified` | False | False | | Run the pipeline on only the modified connectors on the branch or previous commit (depends on the pipeline implementation). Archived connectors are ignored. | -| `--concurrency` | False | 5 | | Control the number of connector pipelines that can run in parallel. Useful to speed up pipelines or control their resource usage. | -| `--metadata-change-only/--not-metadata-change-only` | False | `--not-metadata-change-only` | | Only run the pipeline on connectors with changes on their metadata.yaml file. | -| `--enable-dependency-scanning / --disable-dependency-scanning` | False | ` --disable-dependency-scanning` | | When enabled the dependency scanning will be performed to detect the connectors to select according to a dependency change. | -| `--docker-hub-username` | | | DOCKER_HUB_USERNAME | Your username to connect to DockerHub. Required for the publish subcommand. | -| `--docker-hub-password` | | | DOCKER_HUB_PASSWORD | Your password to connect to DockerHub. Required for the publish subcommand. | - -### `connectors list` command - -Retrieve the list of connectors satisfying the provided filters. - -#### Examples - -List all connectors: - -`airbyte-ci connectors list` - -List all connectors and write the output to a file: -`airbyte-ci connectors list --output=connectors.json` - -List certified connectors: - -`airbyte-ci connectors --support-level=certified list` - -List connectors changed on the current branch: - -`airbyte-ci connectors --modified list` - -List connectors with a specific language: - -`airbyte-ci connectors --language=python list` - -List connectors with multiple filters: - -`airbyte-ci connectors --language=low-code --support-level=certified list` - -### `connectors test` command - -Run a test pipeline for one or multiple connectors. - -#### Examples - -Test a single connector: `airbyte-ci connectors --name=source-pokeapi test` - -Test multiple connectors: `airbyte-ci connectors --name=source-pokeapi --name=source-bigquery test` - -Test certified connectors: `airbyte-ci connectors --support-level=certified test` - -Test connectors changed on the current branch: `airbyte-ci connectors --modified test` - -Run acceptance test only on the modified connectors, just run its full refresh tests: -`airbyte-ci connectors --modified test --only-step="acceptance" --acceptance.-k=test_full_refresh` - -#### What it runs - -```mermaid -flowchart TD - entrypoint[[For each selected connector]] - subgraph static ["Static code analysis"] - qa[Run QA checks] - sem["Check version follows semantic versioning"] - incr["Check version is incremented"] - metadata_validation["Run metadata validation on metadata.yaml"] - sem --> incr - end - subgraph tests ["Tests"] - build[Build connector docker image] - unit[Run unit tests] - integration[Run integration tests] - pyairbyte_validation[Python CLI smoke tests via PyAirbyte] - cat[Run connector acceptance tests] - secret[Load connector configuration] - - unit-->secret - unit-->build - secret-->integration - secret-->cat - secret-->pyairbyte_validation - build-->integration - build-->cat - end - entrypoint-->static - entrypoint-->tests - report["Build test report"] - tests-->report - static-->report -``` - -#### Options - -| Option | Multiple | Default value | Description | -| ------------------------------------------------------- | -------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--skip-step/-x` | True | | Skip steps by id e.g. `-x unit -x acceptance` | -| `--only-step/-k` | True | | Only run specific steps by id e.g. `-k unit -k acceptance` | -| `--fail-fast` | False | False | Abort after any tests fail, rather than continuing to run additional tests. Use this setting to confirm a known bug is fixed (or not), or when you only require a pass/fail result. | -| `--code-tests-only` | True | False | Skip any tests not directly related to code updates. For instance, metadata checks, version bump checks, changelog verification, etc. Use this setting to help focus on code quality during development. | -| `--concurrent-cat` | False | False | Make CAT tests run concurrently using pytest-xdist. Be careful about source or destination API rate limits. | -| `--.=` | True | | You can pass extra parameters for specific test steps. More details in the extra parameters section below | -| `--ci-requirements` | False | | | Output the CI requirements as a JSON payload. It is used to determine the CI runner to use. - -Note: - -- The above options are implemented for Java connectors but may not be available for Python - connectors. If an option is not supported, the pipeline will not fail but instead the 'default' - behavior will be executed. - -#### Extra parameters - -You can pass extra parameters to the following steps: - -- `unit` -- `integration` -- `acceptance` - -This allows you to override the default parameters of these steps. For example, you can only run the -`test_read` test of the acceptance test suite with: -`airbyte-ci connectors --name=source-pokeapi test --acceptance.-k=test_read` Here the `-k` parameter -is passed to the pytest command running acceptance tests. Please keep in mind that the extra -parameters are not validated by the CLI: if you pass an invalid parameter, you'll face a late -failure during the pipeline execution. - -### `connectors build` command - -Run a build pipeline for one or multiple connectors and export the built docker image to the local -docker host. It's mainly purposed for local use. - -Build a single connector: `airbyte-ci connectors --name=source-pokeapi build` - -Build a single connector with a custom image tag: -`airbyte-ci connectors --name=source-pokeapi build --tag=my-custom-tag` - -Build a single connector for multiple architectures: -`airbyte-ci connectors --name=source-pokeapi build --architecture=linux/amd64 --architecture=linux/arm64` - -You will get: - -- `airbyte/source-pokeapi:dev-linux-amd64` -- `airbyte/source-pokeapi:dev-linux-arm64` - -Build multiple connectors: -`airbyte-ci connectors --name=source-pokeapi --name=source-bigquery build` - -Build certified connectors: `airbyte-ci connectors --support-level=certified build` - -Build connectors changed on the current branch: `airbyte-ci connectors --modified build` - -#### What it runs - -For Python and Low Code connectors: - -```mermaid -flowchart TD - arch(For each platform amd64/arm64) - connector[Build connector image] - load[Load to docker host with :dev tag, current platform] - spec[Get spec] - arch-->connector-->spec--"if success"-->load -``` - -For Java connectors: - -```mermaid -flowchart TD - arch(For each platform amd64/arm64) - distTar[Gradle distTar task run] - base[Build integration base] - java_base[Build integration base Java] - normalization[Build Normalization] - connector[Build connector image] - - arch-->base-->java_base-->connector - distTar-->connector - normalization--"if supports normalization"-->connector - - load[Load to docker host with :dev tag] - spec[Get spec] - connector-->spec--"if success"-->load -``` - -### Options - -| Option | Multiple | Default value | Description | -| --------------------- | -------- | -------------- | -------------------------------------------------------------------- | -| `--architecture`/`-a` | True | Local platform | Defines for which architecture(s) the connector image will be built. | -| `--tag` | False | `dev` | Image tag for the built image. | - -### `connectors publish` command - -Run a publish pipeline for one or multiple connectors. It's mainly purposed for CI use to release a -connector update. - -### Examples - -Publish all connectors modified in the head commit: `airbyte-ci connectors --modified publish` - -### Options - -| Option | Required | Default | Mapped environment variable | Description | -| ------------------------------------ | -------- | ------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--pre-release/--main-release` | False | `--pre-release` | | Whether to publish the pre-release or the main release version of a connector. Defaults to pre-release. For main release you have to set the credentials to interact with the GCS bucket. | -| `--spec-cache-gcs-credentials` | False | | `SPEC_CACHE_GCS_CREDENTIALS` | The service account key to upload files to the GCS bucket hosting spec cache. | -| `--spec-cache-bucket-name` | False | | `SPEC_CACHE_BUCKET_NAME` | The name of the GCS bucket where specs will be cached. | -| `--metadata-service-gcs-credentials` | False | | `METADATA_SERVICE_GCS_CREDENTIALS` | The service account key to upload files to the GCS bucket hosting the metadata files. | -| `--metadata-service-bucket-name` | False | | `METADATA_SERVICE_BUCKET_NAME` | The name of the GCS bucket where metadata files will be uploaded. | -| `--slack-webhook` | False | | `SLACK_WEBHOOK` | The Slack webhook URL to send notifications to. | -| `--slack-channel` | False | | `SLACK_CHANNEL` | The Slack channel name to send notifications to. | -| `--ci-requirements` | False | | | Output the CI requirements as a JSON payload. It is used to determine the CI runner to use. | -| `--python-registry-token` | False | | `PYTHON_REGISTRY_TOKEN` | The API token to authenticate with the registry. For pypi, the `pypi-` prefix needs to be specified | -| `--python-registry-url` | False | https://upload.pypi.org/legacy/ | `PYTHON_REGISTRY_URL` | The python registry to publish to. Defaults to main pypi | -| `--python-registry-check-url` | False | https://pypi.org/pypi | `PYTHON_REGISTRY_CHECK_URL` | The python registry url to check whether a package is published already | -| `--promote-release-candidate` | False | False | | Promote the release candidate version of selected connectors as main version. | -| `--rollback-release-candidate` | False | False | | Rollback the release candidate version of the selector connectors. | - -I've added an empty "Default" column, and you can fill in the default values as needed. - -#### What it runs - -```mermaid -flowchart TD - validate[Validate the metadata file] - check[Check if the connector image already exists] - build[Build the connector image for all platform variants] - publish_to_python_registry[Push the connector image to the python registry if enabled] - upload_spec[Upload connector spec to the spec cache bucket] - push[Push the connector image from DockerHub, with platform variants] - pull[Pull the connector image from DockerHub to check SPEC can be run and the image layers are healthy] - upload_metadata[Upload its metadata file to the metadata service bucket] - - validate-->check-->build-->upload_spec-->publish_to_python_registry-->push-->pull-->upload_metadata -``` - -#### Python registry publishing - -If `remoteRegistries.pypi.enabled` in the connector metadata is set to `true`, the connector will be -published to the python registry. To do so, the `--python-registry-token` and -`--python-registry-url` options are used to authenticate with the registry and publish the -connector. If the current version of the connector is already published to the registry, the publish -will be skipped (the `--python-registry-check-url` is used for the check). - -On a pre-release, the connector will be published as a `.dev` version. - -The `remoteRegistries.pypi.packageName` field holds the name of the used package name. It should be -set to `airbyte-source-`. Certified Python connectors are required to have PyPI -publishing enabled. - -An example `remoteRegistries` entry in a connector `metadata.yaml` looks like this: - -```yaml -remoteRegistries: - pypi: - enabled: true - packageName: airbyte-source-pokeapi -``` - -### `connectors up-to-date` command - -Meant to be run on a cron script. - -Actions: - -- Set the latest base image version on selected connectors -- Run `poetry update` on selected connectors -- Bump the connector version and update the changelog -- Open a PR with the changes, set `auto-merge` label on it. - -``` -Usage: airbyte-ci connectors up-to-date [OPTIONS] - -Options: - --no-bump Don't bump the version or changelog. - --dep TEXT Give a specific set of `poetry add` dependencies to update. For - example: --dep airbyte-cdk==0.80.0 --dep pytest@^6.2 - --open-reports Auto open reports in the browser. - --create-prs Create pull requests for each updated connector. - --auto-merge Set the auto-merge label on created PRs. - --help Show this message and exit. -``` - -### Examples - -Get source-openweather up to date. If there are changes, bump the version and add to changelog: - -- `airbyte-ci connectors --name=source-openweather up-to-date`: upgrades main dependecies -- `airbyte-ci connectors --name=source-openweather up-to-date` -- `airbyte-ci connectors --name=source-openweather up-to-date --create-prs`: make a pull request for it -- `airbyte-ci connectors --name=source-openweather up-to-date --no-bump`: don't change the version or changelog - -### `connectors bump-version` command - -Bump the version of the selected connectors. -A placeholder will be added to the changelog file for the new entry PR number. -Use the `connectors pull-request` command to create a PR, it will update the changelog entry with the PR number. - -### Examples - -Bump source-openweather: -`airbyte-ci connectors --name=source-openweather bump-version patch ""` - -#### Arguments - -| Argument | Description | -| ----------------- | ---------------------------------------------------------------------- | -| `BUMP_TYPE` | major, minor, patch, rc, or version: | -| `CHANGELOG_ENTRY` | The changelog entry that will get added to the connector documentation | - -#### Options - -| Option | Description | -| ----------- | ----------------------------------------------------------------------------------------- | -| --pr-number | Explicitly set the PR number in the changelog entry, a placeholder will be set otherwise. | -| --rc | Bump the version by the specified bump type and append the release candidate suffix. | - -### `connectors upgrade-cdk` command - -Updates the CDK version of the selected connectors. -For Python connectors, sets the `airbyte-cdk` dependency in `pyproject.toml` and refreshes the lockfile, updating only essential dependencies. - -### Examples - -`airbyte-ci connectors --language=python upgrade-cdk` -> Updates all python connectors to the caret range of the latest version. -`airbyte-ci connectors --name=source-openweather upgrade-cdk "3.0.0"` -> Pins source-openweather to version 3.0.0 -`airbyte-ci connectors --modified upgrade-cdk "<4"` -> Updates all modified connectors to the highest available version of major version 3.x.x - -#### Arguments - -| Argument | Description | -| ------------- | ------------------------------------------------------------------------- | -| `CDK_VERSION` | CDK version constraint to set (default to `^{most_recent_patch_version}`) | - -#### Notes - -When using < (less than) or > (greater than) for the `CDK_VERSION` argument, it must be wrapped in quotation marks ("<3"). Otherwise the shell (zsh or bash) will interprete these characters as redirection operators. - -### `connectors migrate-to-base-image` command - -Make a connector using a Dockerfile migrate to the base image by: - -- Removing its Dockerfile -- Updating its metadata to use the latest base image version -- Updating its documentation to explain the build process -- Bumping by a patch version - -#### Examples - -Migrate source-openweather to use the base image: -`airbyte-ci connectors --name=source-openweather migrate-to-base-image` - -### `connectors migrate-to-poetry` command - -Migrate connectors the poetry package manager. - -#### Examples - -Migrate source-openweather to use the base image: -`airbyte-ci connectors --name=source-openweather migrate-to-poetry` -`airbyte-ci connectors --name=source-openweather migrate-to-poetry --changelog --bump patch` - -### `connectors migrate-to-inline-schemas` command - -Migrate `.json` schemas into `manifest.yaml` files, when present. - -``` -Usage: airbyte-ci connectors migrate-to-inline-schemas [OPTIONS] - -Options: - --report Auto open report browser. - --help Show this message and exit. -``` - -#### Examples - -Migrate source-quickbooks to use inline schemas: -`airbyte-ci connectors --name=source-quickbooks migrate-to-inline-schemas` - -### `connectors pull-request` command - -Makes a pull request for all changed connectors. If the branch already exists, it will update the existing one. - -``` -Usage: airbyte-ci connectors pull-request [OPTIONS] - -Options: - -m, --message TEXT Commit message and pull request title and - changelog (if enabled). [required] - -b, --branch_id TEXT update a branch named / instead generating one from the message. - [required] - --report Auto open report browser. - --title TEXT Title of the PR to be created or edited - (optional - defaults to message or no change). - --body TEXT Body of the PR to be created or edited (optional - - defaults to empty or not change). - --help Show this message and exit. -``` - -#### Examples - -Make a PR for all changes, bump the version and make a changelog in those PRs. They will be on the branch ci_update/round2/: -`airbyte-ci connectors --modified pull-request -m "upgrading connectors" -b ci_update/round2` - -Do it just for a few connectors: -`airbyte-ci connectors --name source-aha --name source-quickbooks pull-request -m "upgrading connectors" -b ci_update/round2` - -You can also set or set/change the title or body of the PR: -`airbyte-ci connectors --name source-aha --name source-quickbooks pull-request -m "upgrading connectors" -b ci_update/round2 --title "New title" --body "full body\n\ngoes here"` - -### `connectors generate-erd` command - -Generates a couple of files and publish a new ERD to dbdocs. The generated files are: - -- `/erd/discovered_catalog.json`: the catalog used to generate the estimated relations and the dbml file -- `/erd/estimated_relationships.json`: the output of the LLM trying to figure out the relationships between the different streams -- `/erd/source.dbml`: the file used the upload the ERDs to dbdocs - -Pre-requisites: - -- The config file use to discover the catalog should be available in `/secrets/config.json` - -#### Create initial diagram workflow or on connector's schema change - -Steps - -- Ensure the pre-requisites mentioned above are met -- Run `DBDOCS_TOKEN= GENAI_API_KEY= airbyte-ci connectors --name= generate-erd` -- Create a PR with files `/erd/estimated_relationships.json` and `/erd/source.dbml` for documentation purposes - -Expected Outcome - -- The diagram is available in dbdocs -- `/erd/estimated_relationships.json` and `/erd/source.dbml` are updated on master - -#### On manual validation - -Steps - -- If not exists, create file `/erd/confirmed_relationships.json` with the following format and add: - - `relations` describes the relationships that we know exist - - `false_positives` describes the relationships the LLM found that we know do not exist - -``` -{ - "streams": [ - { - "name": , - "relations": { - : "." - } - "false_positives": { - : "." - } - }, - <...> - ] -} -``` - -- Ensure the pre-requisites mentioned above are met -- Run `DBDOCS_TOKEN= airbyte-ci connectors --name= generate-erd -x llm_relationships` -- Create a PR with files `/erd/confirmed_relationships.json` and `/erd/source.dbml` for documentation purposes - -#### Options - -| Option | Required | Default | Mapped environment variable | Description | -| ---------------- | -------- | ------- | --------------------------- | ----------------------------------------------------------- | -| `--skip-step/-x` | False | | | Skip steps by id e.g. `-x llm_relationships -x publish_erd` | - -### `format` command subgroup - -`airbyte-ci format` is no longer available. To format code in this repository, we're using `pre-commit`. Assuming `pre-commit` is installed, `pre-commit run` will run the formatters for you. - -### `poetry` command subgroup - -Available commands: - -- `airbyte-ci poetry publish` - -### Options - -| Option | Required | Default | Mapped environment variable | Description | -| ---------------- | -------- | ------- | --------------------------- | -------------------------------------------------------------- | -| `--package-path` | True | | | The path to the python package to execute a poetry command on. | - -### Examples - -- Publish a python package: - `airbyte-ci poetry --package-path=path/to/package publish --publish-name=my-package --publish-version="1.2.3" --python-registry-token="..." --registry-url="http://host.docker.internal:8012/"` - -### `publish` command - -This command publishes poetry packages (using `pyproject.toml`) or python packages (using -`setup.py`) to a python registry. - -For poetry packages, the package name and version can be taken from the `pyproject.toml` file or be -specified as options. - -#### Options - -| Option | Required | Default | Mapped environment variable | Description | -| ------------------------- | -------- | ------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- | -| `--publish-name` | False | | | The name of the package. Not required for poetry packages that define it in the `pyproject.toml` file | -| `--publish-version` | False | | | The version of the package. Not required for poetry packages that define it in the `pyproject.toml` file | -| `--python-registry-token` | True | | PYTHON_REGISTRY_TOKEN | The API token to authenticate with the registry. For pypi, the `pypi-` prefix needs to be specified | -| `--python-registry-url` | False | https://upload.pypi.org/legacy/ | PYTHON_REGISTRY_URL | The python registry to publish to. Defaults to main pypi | - -### `metadata` command subgroup - -Available commands: - -- `airbyte-ci metadata deploy orchestrator` - -### `metadata deploy orchestrator` command - -This command deploys the metadata service orchestrator to production. The -`DAGSTER_CLOUD_METADATA_API_TOKEN` environment variable must be set. - -#### Example - -`airbyte-ci metadata deploy orchestrator` - -#### What it runs - -```mermaid -flowchart TD - test[Run orchestrator tests] --> deploy[Deploy orchestrator to Dagster Cloud] -``` - -### `tests` command - -This command runs the poe tasks declared in the `[tool.airbyte-ci]` section of our internal poetry -packages. Feel free to checkout this -[Pydantic model](https://github.com/airbytehq/airbyte/blob/main/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/models.py#L9) -to see the list of available options in `[tool.airbyte-ci]` section. - -You can find the list of internal packages -[here](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/__init__.py#L1) - -#### Options - -| Option | Required | Multiple | Description | -| -------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------- | -| `--poetry-package-path/-p` | False | True | Poetry packages path to run the poe tasks for. | -| `--modified` | False | False | Run poe tasks of modified internal poetry packages. | -| `--ci-requirements` | False | False | Output the CI requirements as a JSON payload. It is used to determine the CI runner to use. | - -#### Examples - -You can pass multiple `--poetry-package-path` options to run poe tasks. - -E.G.: running Poe tasks on the modified internal packages of the current branch: -`airbyte-ci test --modified` - -### `migrate-to-manifest-only` command - -This command migrates valid connectors to the `manifest-only` format. It contains two steps: - -1. Check: Validates whether a connector is a candidate for the migration. If not, the operation will be skipped. -2. Migrate: Strips out all unneccessary files/folders, leaving only the root-level manifest, metadata, icon, and acceptance/integration test files. Unwraps the manifest (references and `$parameters`) so it's compatible with Connector Builder. - -#### Examples - -```bash -airbyte-ci connectors --name=source-pokeapi migrate-to-manifest-only -airbyte-ci connectors --language=low-code migrate-to-manifest-only -``` - -## Changelog - -| Version | PR | Description | -| ------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| 5.5.0 | [#64164](https://github.com/airbytehq/airbyte/pull/64164) | Remove the `MetadataValidation` step from the airbyte-ci pipeline. This is now done via a shell script. | -| 5.4.0 | [#64135](https://github.com/airbytehq/airbyte/pull/64135) | Delete the base_images sub-package. Connector base images are now built using Dockerfiles | -| 5.3.0 | [#61598](https://github.com/airbytehq/airbyte/pull/61598) | Add trackable commit text and github-native auto-merge in up-to-date, auto-merge, rc-promote, and rc-rollback | -| 5.2.5 | [#60325](https://github.com/airbytehq/airbyte/pull/60325) | Update slack team to oc-extensibility-critical-systems | -| 5.2.4 | [#59724](https://github.com/airbytehq/airbyte/pull/59724) | Fix components mounting and test dependencies for manifest-only unit tests | -| 5.1.0 | [#53238](https://github.com/airbytehq/airbyte/pull/53238) | Add ability to opt out of version increment checks via metadata flag | -| 5.0.1 | [#52664](https://github.com/airbytehq/airbyte/pull/52664) | Update Python version requirement from 3.10 to 3.11. | -| 4.49.4 | [#52104](https://github.com/airbytehq/airbyte/pull/52104) | Stream Gradle task output to the step logger | -| 5.0.0 | [#52647](https://github.com/airbytehq/airbyte/pull/52647) | Removed migration and formatting commands. | -| 4.49.3 | [#52102](https://github.com/airbytehq/airbyte/pull/52102) | Load docker image to local docker host for java connectors | -| 4.49.2 | [#52090](https://github.com/airbytehq/airbyte/pull/52090) | Re-add custom task parameters in GradleTask | -| 4.49.1 | [#52087](https://github.com/airbytehq/airbyte/pull/52087) | Wire the `--enable-report-auto-open` correctly for connector tests | -| 4.49.0 | [#52033](https://github.com/airbytehq/airbyte/pull/52033) | Run gradle as a subprocess and not via Dagger | -| 4.48.9 | [#51609](https://github.com/airbytehq/airbyte/pull/51609) | Fix ownership of shared cache volume for non root connectors | -| 4.48.8 | [#51582](https://github.com/airbytehq/airbyte/pull/51582) | Fix typo in `migrate-to-inline-schemas` command | -| 4.48.7 | [#51579](https://github.com/airbytehq/airbyte/pull/51579) | Give back the ownership of /tmp to the original user on finalize build | -| 4.48.6 | [#51577](https://github.com/airbytehq/airbyte/pull/51577) | Run finalize build scripts as root | -| 4.48.5 | [#49827](https://github.com/airbytehq/airbyte/pull/49827) | Bypasses CI checks for promoted release candidate PRs. | -| 4.48.4 | [#51003](https://github.com/airbytehq/airbyte/pull/51003) | Install git in the build / test connector container when `--use-cdk-ref` is passed. | -| 4.48.3 | [#50988](https://github.com/airbytehq/airbyte/pull/50988) | Remove deprecated `--no-update` flag from poetry commands | -| 4.48.2 | [#50871](https://github.com/airbytehq/airbyte/pull/50871) | Speed up connector modification detection. | -| 4.48.1 | [#50410](https://github.com/airbytehq/airbyte/pull/50410) | Java connector build: give ownership of built artifacts to the current image user. | -| 4.48.0 | [#49960](https://github.com/airbytehq/airbyte/pull/49960) | Deprecate airbyte-ci format command | -| 4.47.0 | [#49832](https://github.com/airbytehq/airbyte/pull/49462) | Build java connectors from the base image declared in `metadata.yaml`. | -| 4.46.5 | [#49835](https://github.com/airbytehq/airbyte/pull/49835) | Fix connector language discovery for projects with Kotlin Gradle build scripts. | -| 4.46.4 | [#49462](https://github.com/airbytehq/airbyte/pull/49462) | Support Kotlin Gradle build scripts in connectors. | -| 4.46.3 | [#49465](https://github.com/airbytehq/airbyte/pull/49465) | Fix `--use-local-cdk` on rootless connectors. | -| 4.46.2 | [#49136](https://github.com/airbytehq/airbyte/pull/49136) | Fix failed install of python components due to non-root permissions. | -| 4.46.1 | [#49146](https://github.com/airbytehq/airbyte/pull/49146) | Update `crane` image address as the one we were using has been deleted by the maintainer. | -| 4.46.0 | [#48790](https://github.com/airbytehq/airbyte/pull/48790) | Add unit tests step for manifest-only connectors | -| 4.45.3 | [#48927](https://github.com/airbytehq/airbyte/pull/48927) | Fix bug in determine_changelog_entry_comment | -| 4.45.2 | [#48868](https://github.com/airbytehq/airbyte/pull/48868) | Fix ownership issues while using `--use-local-cdk` | -| 4.45.1 | [#48872](https://github.com/airbytehq/airbyte/pull/48872) | Make the `connectors list` command write its output to a JSON file. | -| 4.45.0 | [#48866](https://github.com/airbytehq/airbyte/pull/48866) | Adds `--rc` option to `bump-version` command | -| 4.44.2 | [#48725](https://github.com/airbytehq/airbyte/pull/48725) | up-to-date: specific changelog comment for base image upgrade to rootless. | -| 4.44.1 | [#48836](https://github.com/airbytehq/airbyte/pull/48836) | Manifest-only connector build: give ownership of copied file to the current user. | -| 4.44.0 | [#48818](https://github.com/airbytehq/airbyte/pull/48818) | Use local CDK or CDK ref for manifest only connector build. | -| 4.43.1 | [#48824](https://github.com/airbytehq/airbyte/pull/48824) | Allow uploading CI reports to GCS with fewer permissions set. | -| 4.43.0 | [#36545](https://github.com/airbytehq/airbyte/pull/36545) | Switch to `airbyte` user when available in Python base image. | -| 4.42.2 | [#48404](https://github.com/airbytehq/airbyte/pull/48404) | Include `advanced_auth` in spec migration for manifest-only pipeline | -| 4.42.1 | [#47316](https://github.com/airbytehq/airbyte/pull/47316) | Connector testing: skip incremental acceptance test when the connector is not released. | -| 4.42.0 | [#47386](https://github.com/airbytehq/airbyte/pull/47386) | Version increment check: make sure consecutive RC remain on the same version. | -| 4.41.9 | [#47483](https://github.com/airbytehq/airbyte/pull/47483) | Fix build logic used in `up-to-date` to support any connector language. | -| 4.41.8 | [#47447](https://github.com/airbytehq/airbyte/pull/47447) | Use `cache_ttl` for base image registry listing in `up-to-date`. | -| 4.41.7 | [#47444](https://github.com/airbytehq/airbyte/pull/47444) | Remove redundant `--ignore-connector` error from up-to-date. `--metadata-query` can be used instead. | -| 4.41.6 | [#47308](https://github.com/airbytehq/airbyte/pull/47308) | Connector testing: skip incremental acceptance test when the connector is not released. | -| 4.41.5 | [#47255](https://github.com/airbytehq/airbyte/pull/47255) | Fix `DisableProgressiveRollout` following Dagger API change. | -| 4.41.4 | [#47203](https://github.com/airbytehq/airbyte/pull/47203) | Fix some `with_exec` and entrypoint usage following Dagger upgrade | -| 4.41.3 | [#47189](https://github.com/airbytehq/airbyte/pull/47189) | Fix up-to-date which did not export doc to the right path | -| 4.41.2 | [#47185](https://github.com/airbytehq/airbyte/pull/47185) | Fix the bump version command which did not update the changelog. | -| 4.41.1 | [#46914](https://github.com/airbytehq/airbyte/pull/46914) | Upgrade to Dagger 0.13.3 | -| 4.41.0 | [#46914](https://github.com/airbytehq/airbyte/pull/46914) | Rework the connector rollback pipeline for progressive rollout | -| 4.40.0 | [#46380](https://github.com/airbytehq/airbyte/pull/46380) | The `bump-version` command now allows the `rc` bump type. | -| 4.39.0 | [#46696](https://github.com/airbytehq/airbyte/pull/46696) | Bump PyAirbyte dependency and replace `airbyte-lib-validate-source` CLI command with new `validate` command | -| 4.38.0 | [#46380](https://github.com/airbytehq/airbyte/pull/46380) | `connectors up-to-date` now supports manifest-only connectors! | -| 4.37.0 | [#46380](https://github.com/airbytehq/airbyte/pull/46380) | Include custom components file handling in manifest-only migrations | -| 4.36.2 | [#46278](https://github.com/airbytehq/airbyte/pull/46278) | Fixed a bug in RC rollout and promote not taking `semaphore` | -| 4.36.1 | [#46274](https://github.com/airbytehq/airbyte/pull/46274) | `airbyte-ci format js` respects `.prettierc` and `.prettierignore` | -| 4.36.0 | [#44877](https://github.com/airbytehq/airbyte/pull/44877) | Implement `--promote/rollback-release-candidate` in `connectors publish`. | -| 4.35.6 | [#45632](https://github.com/airbytehq/airbyte/pull/45632) | Add entry to format file ignore list (`destination-*/expected-spec.json`) | -| 4.35.5 | [#45672](https://github.com/airbytehq/airbyte/pull/45672) | Fix docs mount during publish | -| 4.35.4 | [#42584](https://github.com/airbytehq/airbyte/pull/42584) | Mount connector directory to metadata validation | -| 4.35.3 | [#45393](https://github.com/airbytehq/airbyte/pull/45393) | Resolve symlinks in `SimpleDockerStep`. | -| 4.35.2 | [#45360](https://github.com/airbytehq/airbyte/pull/45360) | Updated dependencies. | -| 4.35.1 | [#45160](https://github.com/airbytehq/airbyte/pull/45160) | Remove deps.toml dependency for java connectors. | -| 4.35.0 | [#44879](https://github.com/airbytehq/airbyte/pull/44879) | Mount `components.py` when building manifest-only connector image | -| 4.34.2 | [#44786](https://github.com/airbytehq/airbyte/pull/44786) | Pre-emptively skip archived connectors when searching for modified files | -| 4.34.1 | [#44557](https://github.com/airbytehq/airbyte/pull/44557) | Conditionally propagate parameters in manifest-only migration | -| 4.34.0 | [#44551](https://github.com/airbytehq/airbyte/pull/44551) | `connectors publish` do not push the `latest` tag when the current version is a release candidate. | -| 4.33.1 | [#44465](https://github.com/airbytehq/airbyte/pull/44465) | Ignore version check if only erd folder is changed | -| 4.33.0 | [#44377](https://github.com/airbytehq/airbyte/pull/44377) | Upload connector SBOM to metadata service bucket on publish. | -| 4.32.5 | [#44173](https://github.com/airbytehq/airbyte/pull/44173) | Bug fix for live tests' --should-read-with-state handling. | -| 4.32.4 | [#44025](https://github.com/airbytehq/airbyte/pull/44025) | Ignore third party connectors on `publish`. | -| 4.32.3 | [#44118](https://github.com/airbytehq/airbyte/pull/44118) | Improve error handling in live tests. | -| 4.32.2 | [#43970](https://github.com/airbytehq/airbyte/pull/43970) | Make `connectors publish` early exit if no connectors are selected. | -| 4.32.1 | [#41642](https://github.com/airbytehq/airbyte/pull/41642) | Avoid transient publish failures by increasing `POETRY_REQUESTS_TIMEOUT` and setting retries on `PublishToPythonRegistry`. | -| 4.32.0 | [#43969](https://github.com/airbytehq/airbyte/pull/43969) | Add an `--ignore-connector` option to `up-to-date` | -| 4.31.5 | [#43934](https://github.com/airbytehq/airbyte/pull/43934) | Track deleted files when generating pull-request | -| 4.31.4 | [#43724](https://github.com/airbytehq/airbyte/pull/43724) | Do not send slack message on connector pre-release. | -| 4.31.3 | [#43426](https://github.com/airbytehq/airbyte/pull/43426) | Ignore archived connectors on connector selection from modified files. | -| 4.31.2 | [#43433](https://github.com/airbytehq/airbyte/pull/43433) | Fix 'changed_file' indentation in 'pull-request' command | -| 4.31.1 | [#43442](https://github.com/airbytehq/airbyte/pull/43442) | Resolve type check failure in bump version | -| 4.31.0 | [#42970](https://github.com/airbytehq/airbyte/pull/42970) | Add explicit version set to bump version | -| 4.30.1 | [#43386](https://github.com/airbytehq/airbyte/pull/43386) | Fix 'format' command usage bug in airbyte-enterprise. | -| 4.30.0 | [#42583](https://github.com/airbytehq/airbyte/pull/42583) | Updated dependencies | -| 4.29.0 | [#42576](https://github.com/airbytehq/airbyte/pull/42576) | New command: `migrate-to-manifest-only` | -| 4.28.3 | [#42046](https://github.com/airbytehq/airbyte/pull/42046) | Trigger connector tests on doc change. | -| 4.28.2 | [#43297](https://github.com/airbytehq/airbyte/pull/43297) | `migrate-to-inline_schemas` removes unused schema files and empty schema dirs. | -| 4.28.1 | [#42972](https://github.com/airbytehq/airbyte/pull/42972) | Add airbyte-enterprise support for format commandi | -| 4.28.0 | [#42849](https://github.com/airbytehq/airbyte/pull/42849) | Couple selection of strict-encrypt variants (e vice versa) | -| 4.27.0 | [#42574](https://github.com/airbytehq/airbyte/pull/42574) | Live tests: run from connectors test pipeline for connectors with sandbox connections | -| 4.26.1 | [#42905](https://github.com/airbytehq/airbyte/pull/42905) | Rename the docker cache volume to avoid using the corrupted previous volume. | -| 4.26.0 | [#42849](https://github.com/airbytehq/airbyte/pull/42849) | Send publish failures messages to `#connector-publish-failures` | -| 4.25.4 | [#42463](https://github.com/airbytehq/airbyte/pull/42463) | Add validation before live test runs | -| 4.25.3 | [#42437](https://github.com/airbytehq/airbyte/pull/42437) | Ugrade-cdk: Update to work with Python connectors using poetry | -| 4.25.2 | [#42077](https://github.com/airbytehq/airbyte/pull/42077) | Live/regression tests: add status check for regression test runs | -| 4.25.1 | [#42410](https://github.com/airbytehq/airbyte/pull/42410) | Live/regression tests: disable approval requirement on forks | -| 4.25.0 | [#42044](https://github.com/airbytehq/airbyte/pull/42044) | Live/regression tests: add support for selecting from a subset of connections | -| 4.24.3 | [#42040](https://github.com/airbytehq/airbyte/pull/42040) | Always send regression test approval status check; skip on auto-merge PRs. | -| 4.24.2 | [#41676](https://github.com/airbytehq/airbyte/pull/41676) | Send regression test approval status check when skipped. | -| 4.24.1 | [#41642](https://github.com/airbytehq/airbyte/pull/41642) | Use the AIRBYTE_GITHUB_REPO environment variable to run airbyte-ci in other repos. | -| 4.24.0 | [#41627](https://github.com/airbytehq/airbyte/pull/41627) | Require manual regression test approval for certified connectors | -| 4.23.1 | [#41541](https://github.com/airbytehq/airbyte/pull/41541) | Add support for submodule use-case. | -| 4.23.0 | [#39906](https://github.com/airbytehq/airbyte/pull/39906) | Add manifest only build pipeline | -| 4.22.0 | [#41623](https://github.com/airbytehq/airbyte/pull/41623) | Make `airbyte-ci` run on private forks. | -| 4.21.1 | [#41029](https://github.com/airbytehq/airbyte/pull/41029) | `up-to-date`: mount local docker config to `Syft` to pull private images and benefit from increased DockerHub rate limits. | -| 4.21.0 | [#40547](https://github.com/airbytehq/airbyte/pull/40547) | Make bump-version accept a `--pr-number` option. | -| 4.20.3 | [#40754](https://github.com/airbytehq/airbyte/pull/40754) | Accept and ignore additional args in `migrate-to-poetry` pipeline | -| 4.20.2 | [#40709](https://github.com/airbytehq/airbyte/pull/40709) | Fix use of GH token. | -| 4.20.1 | [#40698](https://github.com/airbytehq/airbyte/pull/40698) | Add live tests evaluation mode options. | -| 4.20.0 | [#38816](https://github.com/airbytehq/airbyte/pull/38816) | Add command for running all live tests (validation + regression). | -| 4.19.0 | [#39600](https://github.com/airbytehq/airbyte/pull/39600) | Productionize the `up-to-date` command | -| 4.18.3 | [#39341](https://github.com/airbytehq/airbyte/pull/39341) | Fix `--use-local-cdk` option: change `no-deps` to `force-reinstall` | -| 4.18.2 | [#39483](https://github.com/airbytehq/airbyte/pull/39483) | Skip IncrementalAcceptanceTests when AcceptanceTests succeed. | -| 4.18.1 | [#39457](https://github.com/airbytehq/airbyte/pull/39457) | Make slugify consistent with live-test | -| 4.18.0 | [#39366](https://github.com/airbytehq/airbyte/pull/39366) | Implement IncrementalAcceptance tests to only fail CI on community connectors when there's an Acceptance tests regression. | -| 4.17.0 | [#39321](https://github.com/airbytehq/airbyte/pull/39321) | Bust the java connector build cache flow to get fresh yum packages on a daily basis. | -| 4.16.0 | [#38772](https://github.com/airbytehq/airbyte/pull/38232) | Add pipeline to replace usage of AirbyteLogger. | -| 4.15.7 | [#38772](https://github.com/airbytehq/airbyte/pull/38772) | Fix regression test connector image retrieval. | -| 4.15.6 | [#38783](https://github.com/airbytehq/airbyte/pull/38783) | Fix a variable access error with `repo_dir` in the `bump-version` command. | -| 4.15.5 | [#38732](https://github.com/airbytehq/airbyte/pull/38732) | Update metadata deploy pipeline to 3.10 | -| 4.15.4 | [#38646](https://github.com/airbytehq/airbyte/pull/38646) | Make airbyte-ci able to test external repos. | -| 4.15.3 | [#38645](https://github.com/airbytehq/airbyte/pull/38645) | Fix typo preventing correct secret mounting on Python connectors integration tests. | -| 4.15.2 | [#38628](https://github.com/airbytehq/airbyte/pull/38628) | Introduce ConnectorTestContext to avoid trying fetching connector secret in the PublishContext. | -| 4.15.1 | [#38615](https://github.com/airbytehq/airbyte/pull/38615) | Do not eagerly fetch connector secrets. | -| 4.15.0 | [#38322](https://github.com/airbytehq/airbyte/pull/38322) | Introduce a SecretStore abstraction to fetch connector secrets from metadata files. | -| 4.14.1 | [#38582](https://github.com/airbytehq/airbyte/pull/38582) | Fixed bugs in `up-to-date` flags, `pull-request` version change logic. | -| 4.14.0 | [#38281](https://github.com/airbytehq/airbyte/pull/38281) | Conditionally run test suites according to `connectorTestSuitesOptions` in metadata files. | -| 4.13.3 | [#38221](https://github.com/airbytehq/airbyte/pull/38221) | Add dagster cloud dev deployment pipeline opitions | -| 4.13.2 | [#38246](https://github.com/airbytehq/airbyte/pull/38246) | Remove invalid connector test step options. | -| 4.13.1 | [#38020](https://github.com/airbytehq/airbyte/pull/38020) | Add `auto_merge` as an internal package to test. | -| 4.13.0 | [#32715](https://github.com/airbytehq/airbyte/pull/32715) | Tag connector metadata with git info | -| 4.12.7 | [#37787](https://github.com/airbytehq/airbyte/pull/37787) | Remove requirements on dockerhub credentials to run QA checks. | -| 4.12.6 | [#36497](https://github.com/airbytehq/airbyte/pull/36497) | Add airbyte-cdk to list of poetry packages for testing | -| 4.12.5 | [#37785](https://github.com/airbytehq/airbyte/pull/37785) | Set the `--yes-auto-update` flag to `True` by default. | -| 4.12.4 | [#37786](https://github.com/airbytehq/airbyte/pull/37786) | (fixed 4.12.2): Do not upload dagger log to GCP when no credentials are available. | -| 4.12.3 | [#37783](https://github.com/airbytehq/airbyte/pull/37783) | Revert 4.12.2 | -| 4.12.2 | [#37778](https://github.com/airbytehq/airbyte/pull/37778) | Do not upload dagger log to GCP when no credentials are available. | -| 4.12.1 | [#37765](https://github.com/airbytehq/airbyte/pull/37765) | Relax the required env var to run in CI and handle their absence gracefully. | -| 4.12.0 | [#37690](https://github.com/airbytehq/airbyte/pull/37690) | Pass custom CI status name in `connectors test` | -| 4.11.0 | [#37641](https://github.com/airbytehq/airbyte/pull/37641) | Updates to run regression tests in GitHub Actions. | -| 4.10.5 | [#37641](https://github.com/airbytehq/airbyte/pull/37641) | Reintroduce changes from 4.10.0 with a fix. | -| 4.10.4 | [#37641](https://github.com/airbytehq/airbyte/pull/37641) | Temporarily revert changes from version 4.10.0 | -| 4.10.3 | [#37615](https://github.com/airbytehq/airbyte/pull/37615) | Fix `KeyError` when running `migrate-to-poetry` | -| 4.10.2 | [#37614](https://github.com/airbytehq/airbyte/pull/37614) | Fix `UnboundLocalError: local variable 'add_changelog_entry_result' referenced before assignment` in `migrate-to-base-image` | -| 4.10.1 | [#37622](https://github.com/airbytehq/airbyte/pull/37622) | Temporarily disable regression tests in CI | -| 4.10.0 | [#37616](https://github.com/airbytehq/airbyte/pull/37616) | Improve modified files comparison when the target branch is from a fork. | -| 4.9.0 | [#37440](https://github.com/airbytehq/airbyte/pull/37440) | Run regression tests with `airbyte-ci connectors test` | -| 4.8.0 | [#37404](https://github.com/airbytehq/airbyte/pull/37404) | Accept a `git-repo-url` option on the `airbyte-ci` root command to checkout forked repo. | -| 4.7.4 | [#37485](https://github.com/airbytehq/airbyte/pull/37485) | Allow java connectors to be written in kotlin. | -| 4.7.3 | [#37101](https://github.com/airbytehq/airbyte/pull/37101) | Pin PyAirbyte version. | -| 4.7.2 | [#36962](https://github.com/airbytehq/airbyte/pull/36962) | Re-enable connector dependencies upload on publish. | -| 4.7.1 | [#36961](https://github.com/airbytehq/airbyte/pull/36961) | Temporarily disable python connectors dependencies upload until we find a schema the data team can work with. | -| 4.7.0 | [#36892](https://github.com/airbytehq/airbyte/pull/36892) | Upload Python connectors dependencies list to GCS on publish. | -| 4.6.5 | [#36722](https://github.com/airbytehq/airbyte/pull/36527) | Fix incorrect pipeline names | -| 4.6.4 | [#36480](https://github.com/airbytehq/airbyte/pull/36480) | Burst the Gradle Task cache if a new CDK version was released | -| 4.6.3 | [#36527](https://github.com/airbytehq/airbyte/pull/36527) | Handle extras as well as groups in `airbyte ci test` [poetry packages] | -| 4.6.2 | [#36220](https://github.com/airbytehq/airbyte/pull/36220) | Allow using `migrate-to-base-image` without PULL_REQUEST_NUMBER | -| 4.6.1 | [#36319](https://github.com/airbytehq/airbyte/pull/36319) | Fix `ValueError` related to PR number in migrate-to-poetry | -| 4.6.0 | [#35583](https://github.com/airbytehq/airbyte/pull/35583) | Implement the `airbyte-ci connectors migrate-to-poetry` command. | -| 4.5.4 | [#36206](https://github.com/airbytehq/airbyte/pull/36206) | Revert poetry cache removal during nightly builds | -| 4.5.3 | [#34586](https://github.com/airbytehq/airbyte/pull/34586) | Extract connector changelog modification logic into its own class | -| 4.5.2 | [#35802](https://github.com/airbytehq/airbyte/pull/35802) | Fix bug with connectors bump-version command | -| 4.5.1 | [#35786](https://github.com/airbytehq/airbyte/pull/35786) | Declare `live_tests` as an internal poetry package. | -| 4.5.0 | [#35784](https://github.com/airbytehq/airbyte/pull/35784) | Format command supports kotlin | -| 4.4.0 | [#35317](https://github.com/airbytehq/airbyte/pull/35317) | Augment java connector reports to include full logs and junit test results | -| 4.3.2 | [#35536](https://github.com/airbytehq/airbyte/pull/35536) | Make QA checks run correctly on `*-strict-encrypt` connectors. | -| 4.3.1 | [#35437](https://github.com/airbytehq/airbyte/pull/35437) | Do not run QA checks on publish, just MetadataValidation. | -| 4.3.0 | [#35438](https://github.com/airbytehq/airbyte/pull/35438) | Optionally disable telemetry with environment variable. | -| 4.2.4 | [#35325](https://github.com/airbytehq/airbyte/pull/35325) | Use `connectors_qa` for QA checks and remove redundant checks. | -| 4.2.3 | [#35322](https://github.com/airbytehq/airbyte/pull/35322) | Declare `connectors_qa` as an internal package for testing. | -| 4.2.2 | [#35364](https://github.com/airbytehq/airbyte/pull/35364) | Fix connector tests following gradle changes in #35307. | -| 4.2.1 | [#35204](https://github.com/airbytehq/airbyte/pull/35204) | Run `poetry check` before `poetry install` on poetry package install. | -| 4.2.0 | [#35103](https://github.com/airbytehq/airbyte/pull/35103) | Java 21 support. | -| 4.1.4 | [#35039](https://github.com/airbytehq/airbyte/pull/35039) | Fix bug which prevented gradle test reports from being added. | -| 4.1.3 | [#35010](https://github.com/airbytehq/airbyte/pull/35010) | Use `poetry install --no-root` in the builder container. | -| 4.1.2 | [#34945](https://github.com/airbytehq/airbyte/pull/34945) | Only install main dependencies when running poetry install. | -| 4.1.1 | [#34430](https://github.com/airbytehq/airbyte/pull/34430) | Speed up airbyte-ci startup (and airbyte-ci format). | -| 4.1.0 | [#34923](https://github.com/airbytehq/airbyte/pull/34923) | Include gradle test reports in HTML connector test report. | -| 4.0.0 | [#34736](https://github.com/airbytehq/airbyte/pull/34736) | Run poe tasks declared in internal poetry packages. | -| 3.10.4 | [#34867](https://github.com/airbytehq/airbyte/pull/34867) | Remove connector ops team | -| 3.10.3 | [#34836](https://github.com/airbytehq/airbyte/pull/34836) | Add check for python registry publishing enabled for certified python sources. | -| 3.10.2 | [#34044](https://github.com/airbytehq/airbyte/pull/34044) | Add pypi validation testing. | -| 3.10.1 | [#34756](https://github.com/airbytehq/airbyte/pull/34756) | Enable connectors tests in draft PRs. | -| 3.10.0 | [#34606](https://github.com/airbytehq/airbyte/pull/34606) | Allow configuration of separate check URL to check whether package exists already. | -| 3.9.0 | [#34606](https://github.com/airbytehq/airbyte/pull/34606) | Allow configuration of python registry URL via environment variable. | -| 3.8.1 | [#34607](https://github.com/airbytehq/airbyte/pull/34607) | Improve gradle dependency cache volume protection. | -| 3.8.0 | [#34316](https://github.com/airbytehq/airbyte/pull/34316) | Expose Dagger engine image name in `--ci-requirements` and add `--ci-requirements` to the `airbyte-ci` root command group. | -| 3.7.3 | [#34560](https://github.com/airbytehq/airbyte/pull/34560) | Simplify Gradle task execution framework by removing local maven repo support. | -| 3.7.2 | [#34555](https://github.com/airbytehq/airbyte/pull/34555) | Override secret masking in some very specific special cases. | -| 3.7.1 | [#34441](https://github.com/airbytehq/airbyte/pull/34441) | Support masked secret scrubbing for java CDK v0.15+ | -| 3.7.0 | [#34343](https://github.com/airbytehq/airbyte/pull/34343) | allow running connector upgrade_cdk for java connectors | -| 3.6.1 | [#34490](https://github.com/airbytehq/airbyte/pull/34490) | Fix inconsistent dagger log path typing | -| 3.6.0 | [#34111](https://github.com/airbytehq/airbyte/pull/34111) | Add python registry publishing | -| 3.5.3 | [#34339](https://github.com/airbytehq/airbyte/pull/34339) | only do minimal changes on a connector version_bump | -| 3.5.2 | [#34381](https://github.com/airbytehq/airbyte/pull/34381) | Bind a sidecar docker host for `airbyte-ci test` | -| 3.5.1 | [#34321](https://github.com/airbytehq/airbyte/pull/34321) | Upgrade to Dagger 0.9.6 . | -| 3.5.0 | [#33313](https://github.com/airbytehq/airbyte/pull/33313) | Pass extra params after Gradle tasks. | -| 3.4.2 | [#34301](https://github.com/airbytehq/airbyte/pull/34301) | Pass extra params after Gradle tasks. | -| 3.4.1 | [#34067](https://github.com/airbytehq/airbyte/pull/34067) | Use dagster-cloud 1.5.7 for deploy | -| 3.4.0 | [#34276](https://github.com/airbytehq/airbyte/pull/34276) | Introduce `--only-step` option for connector tests. | -| 3.3.0 | [#34218](https://github.com/airbytehq/airbyte/pull/34218) | Introduce `--ci-requirements` option for client defined CI runners. | -| 3.2.0 | [#34050](https://github.com/airbytehq/airbyte/pull/34050) | Connector test steps can take extra parameters | -| 3.1.3 | [#34136](https://github.com/airbytehq/airbyte/pull/34136) | Fix issue where dagger excludes were not being properly applied | -| 3.1.2 | [#33972](https://github.com/airbytehq/airbyte/pull/33972) | Remove secrets scrubbing hack for --is-local and other small tweaks. | -| 3.1.1 | [#33979](https://github.com/airbytehq/airbyte/pull/33979) | Fix AssertionError on report existence again | -| 3.1.0 | [#33994](https://github.com/airbytehq/airbyte/pull/33994) | Log more context information in CI. | -| 3.0.2 | [#33987](https://github.com/airbytehq/airbyte/pull/33987) | Fix type checking issue when running --help | -| 3.0.1 | [#33981](https://github.com/airbytehq/airbyte/pull/33981) | Fix issues with deploying dagster, pin pendulum version in dagster-cli install | -| 3.0.0 | [#33582](https://github.com/airbytehq/airbyte/pull/33582) | Upgrade to Dagger 0.9.5 | -| 2.14.3 | [#33964](https://github.com/airbytehq/airbyte/pull/33964) | Reintroduce mypy with fixes for AssertionError on publish and missing report URL on connector test commit status. | -| 2.14.2 | [#33954](https://github.com/airbytehq/airbyte/pull/33954) | Revert mypy changes | -| 2.14.1 | [#33956](https://github.com/airbytehq/airbyte/pull/33956) | Exclude pnpm lock files from auto-formatting | -| 2.14.0 | [#33941](https://github.com/airbytehq/airbyte/pull/33941) | Enable in-connector normalization in destination-postgres | -| 2.13.1 | [#33920](https://github.com/airbytehq/airbyte/pull/33920) | Report different sentry environments | -| 2.13.0 | [#33784](https://github.com/airbytehq/airbyte/pull/33784) | Make `airbyte-ci test` able to run any poetry command | -| 2.12.0 | [#33313](https://github.com/airbytehq/airbyte/pull/33313) | Add upgrade CDK command | -| 2.11.0 | [#32188](https://github.com/airbytehq/airbyte/pull/32188) | Add -x option to connector test to allow for skipping steps | -| 2.10.12 | [#33419](https://github.com/airbytehq/airbyte/pull/33419) | Make ClickPipelineContext handle dagger logging. | -| 2.10.11 | [#33497](https://github.com/airbytehq/airbyte/pull/33497) | Consider nested .gitignore rules in format. | -| 2.10.10 | [#33449](https://github.com/airbytehq/airbyte/pull/33449) | Add generated metadata models to the default format ignore list. | -| 2.10.9 | [#33370](https://github.com/airbytehq/airbyte/pull/33370) | Fix bug that broke airbyte-ci test | -| 2.10.8 | [#33249](https://github.com/airbytehq/airbyte/pull/33249) | Exclude git ignored files from formatting. | -| 2.10.7 | [#33248](https://github.com/airbytehq/airbyte/pull/33248) | Fix bug which broke airbyte-ci connectors tests when optional DockerHub credentials env vars are not set. | -| 2.10.6 | [#33170](https://github.com/airbytehq/airbyte/pull/33170) | Remove Dagger logs from console output of `format`. | -| 2.10.5 | [#33097](https://github.com/airbytehq/airbyte/pull/33097) | Improve `format` performances, exit with 1 status code when `fix` changes files. | -| 2.10.4 | [#33206](https://github.com/airbytehq/airbyte/pull/33206) | Add "-y/--yes" Flag to allow preconfirmation of prompts | -| 2.10.3 | [#33080](https://github.com/airbytehq/airbyte/pull/33080) | Fix update failing due to SSL error on install. | -| 2.10.2 | [#33008](https://github.com/airbytehq/airbyte/pull/33008) | Fix local `connector build`. | -| 2.10.1 | [#32928](https://github.com/airbytehq/airbyte/pull/32928) | Fix BuildConnectorImages constructor. | -| 2.10.0 | [#32819](https://github.com/airbytehq/airbyte/pull/32819) | Add `--tag` option to connector build. | -| 2.9.0 | [#32816](https://github.com/airbytehq/airbyte/pull/32816) | Add `--architecture` option to connector build. | -| 2.8.1 | [#32999](https://github.com/airbytehq/airbyte/pull/32999) | Improve Java code formatting speed | -| 2.8.0 | [#31930](https://github.com/airbytehq/airbyte/pull/31930) | Move pipx install to `airbyte-ci-dev`, and add auto-update feature targeting binary | -| 2.7.3 | [#32847](https://github.com/airbytehq/airbyte/pull/32847) | Improve --modified behaviour for pull requests. | -| 2.7.2 | [#32839](https://github.com/airbytehq/airbyte/pull/32839) | Revert changes in v2.7.1. | -| 2.7.1 | [#32806](https://github.com/airbytehq/airbyte/pull/32806) | Improve --modified behaviour for pull requests. | -| 2.7.0 | [#31930](https://github.com/airbytehq/airbyte/pull/31930) | Merge airbyte-ci-internal into airbyte-ci | -| 2.6.0 | [#31831](https://github.com/airbytehq/airbyte/pull/31831) | Add `airbyte-ci format` commands, remove connector-specific formatting check | -| 2.5.9 | [#32427](https://github.com/airbytehq/airbyte/pull/32427) | Re-enable caching for source-postgres | -| 2.5.8 | [#32402](https://github.com/airbytehq/airbyte/pull/32402) | Set Dagger Cloud token for airbyters only | -| 2.5.7 | [#31628](https://github.com/airbytehq/airbyte/pull/31628) | Add ClickPipelineContext class | -| 2.5.6 | [#32139](https://github.com/airbytehq/airbyte/pull/32139) | Test coverage report on Python connector UnitTest. | -| 2.5.5 | [#32114](https://github.com/airbytehq/airbyte/pull/32114) | Create cache mount for `/var/lib/docker` to store images in `dind` context. | -| 2.5.4 | [#32090](https://github.com/airbytehq/airbyte/pull/32090) | Do not cache `docker login`. | -| 2.5.3 | [#31974](https://github.com/airbytehq/airbyte/pull/31974) | Fix latest CDK install and pip cache mount on connector install. | -| 2.5.2 | [#31871](https://github.com/airbytehq/airbyte/pull/31871) | Deactivate PR comments, add HTML report links to the PR status when its ready. | -| 2.5.1 | [#31774](https://github.com/airbytehq/airbyte/pull/31774) | Add a docker configuration check on `airbyte-ci` startup. | -| 2.5.0 | [#31766](https://github.com/airbytehq/airbyte/pull/31766) | Support local connectors secrets. | -| 2.4.0 | [#31716](https://github.com/airbytehq/airbyte/pull/31716) | Enable pre-release publish with local CDK. | -| 2.3.1 | [#31748](https://github.com/airbytehq/airbyte/pull/31748) | Use AsyncClick library instead of base Click. | -| 2.3.0 | [#31699](https://github.com/airbytehq/airbyte/pull/31699) | Support optional concurrent CAT execution. | -| 2.2.6 | [#31752](https://github.com/airbytehq/airbyte/pull/31752) | Only authenticate when secrets are available. | -| 2.2.5 | [#31718](https://github.com/airbytehq/airbyte/pull/31718) | Authenticate the sidecar docker daemon to DockerHub. | -| 2.2.4 | [#31535](https://github.com/airbytehq/airbyte/pull/31535) | Improve gradle caching when building java connectors. | -| 2.2.3 | [#31688](https://github.com/airbytehq/airbyte/pull/31688) | Fix failing `CheckBaseImageUse` step when not running on PR. | -| 2.2.2 | [#31659](https://github.com/airbytehq/airbyte/pull/31659) | Support builds on x86_64 platform | -| 2.2.1 | [#31653](https://github.com/airbytehq/airbyte/pull/31653) | Fix CheckBaseImageIsUsed failing on non certified connectors. | -| 2.2.0 | [#30527](https://github.com/airbytehq/airbyte/pull/30527) | Add a new check for python connectors to make sure certified connectors use our base image. | -| 2.1.1 | [#31488](https://github.com/airbytehq/airbyte/pull/31488) | Improve `airbyte-ci` start time with Click Lazy load | -| 2.1.0 | [#31412](https://github.com/airbytehq/airbyte/pull/31412) | Run airbyte-ci from any where in airbyte project | -| 2.0.4 | [#31487](https://github.com/airbytehq/airbyte/pull/31487) | Allow for third party connector selections | -| 2.0.3 | [#31525](https://github.com/airbytehq/airbyte/pull/31525) | Refactor folder structure | -| 2.0.2 | [#31533](https://github.com/airbytehq/airbyte/pull/31533) | Pip cache volume by python version. | -| 2.0.1 | [#31545](https://github.com/airbytehq/airbyte/pull/31545) | Reword the changelog entry when using `migrate-to-base-image`. | -| 2.0.0 | [#31424](https://github.com/airbytehq/airbyte/pull/31424) | Remove `airbyte-ci connectors format` command. | -| 1.9.4 | [#31478](https://github.com/airbytehq/airbyte/pull/31478) | Fix running tests for connector-ops package. | -| 1.9.3 | [#31457](https://github.com/airbytehq/airbyte/pull/31457) | Improve the connector documentation for connectors migrated to our base image. | -| 1.9.2 | [#31426](https://github.com/airbytehq/airbyte/pull/31426) | Concurrent execution of java connectors tests. | -| 1.9.1 | [#31455](https://github.com/airbytehq/airbyte/pull/31455) | Fix `None` docker credentials on publish. | -| 1.9.0 | [#30520](https://github.com/airbytehq/airbyte/pull/30520) | New commands: `bump-version`, `upgrade_base_image`, `migrate-to-base-image`. | -| 1.8.0 | [#30520](https://github.com/airbytehq/airbyte/pull/30520) | New commands: `bump-version`, `upgrade_base_image`, `migrate-to-base-image`. | -| 1.7.2 | [#31343](https://github.com/airbytehq/airbyte/pull/31343) | Bind Pytest integration tests to a dockerhost. | -| 1.7.1 | [#31332](https://github.com/airbytehq/airbyte/pull/31332) | Disable Gradle step caching on source-postgres. | -| 1.7.0 | [#30526](https://github.com/airbytehq/airbyte/pull/30526) | Implement pre/post install hooks support. | -| 1.6.0 | [#30474](https://github.com/airbytehq/airbyte/pull/30474) | Test connector inside their containers. | -| 1.5.1 | [#31227](https://github.com/airbytehq/airbyte/pull/31227) | Use python 3.11 in amazoncorretto-bazed gradle containers, run 'test' gradle task instead of 'check'. | -| 1.5.0 | [#30456](https://github.com/airbytehq/airbyte/pull/30456) | Start building Python connectors using our base images. | -| 1.4.6 | [ #31087](https://github.com/airbytehq/airbyte/pull/31087) | Throw error if airbyte-ci tools is out of date | -| 1.4.5 | [#31133](https://github.com/airbytehq/airbyte/pull/31133) | Fix bug when building containers using `with_integration_base_java_and_normalization`. | -| 1.4.4 | [#30743](https://github.com/airbytehq/airbyte/pull/30743) | Add `--disable-report-auto-open` and `--use-host-gradle-dist-tar` to allow gradle integration. | -| 1.4.3 | [#30595](https://github.com/airbytehq/airbyte/pull/30595) | Add --version and version check | -| 1.4.2 | [#30595](https://github.com/airbytehq/airbyte/pull/30595) | Remove directory name requirement | -| 1.4.1 | [#30595](https://github.com/airbytehq/airbyte/pull/30595) | Load base migration guide into QA Test container for strict encrypt variants | -| 1.4.0 | [#30330](https://github.com/airbytehq/airbyte/pull/30330) | Add support for pyproject.toml as the prefered entry point for a connector package | -| 1.3.0 | [#30461](https://github.com/airbytehq/airbyte/pull/30461) | Add `--use-local-cdk` flag to all connectors commands | -| 1.2.3 | [#30477](https://github.com/airbytehq/airbyte/pull/30477) | Fix a test regression introduced the previous version. | -| 1.2.2 | [#30438](https://github.com/airbytehq/airbyte/pull/30438) | Add workaround to always stream logs properly with --is-local. | -| 1.2.1 | [#30384](https://github.com/airbytehq/airbyte/pull/30384) | Java connector test performance fixes. | -| 1.2.0 | [#30330](https://github.com/airbytehq/airbyte/pull/30330) | Add `--metadata-query` option to connectors command | -| 1.1.3 | [#30314](https://github.com/airbytehq/airbyte/pull/30314) | Stop patching gradle files to make them work with airbyte-ci. | -| 1.1.2 | [#30279](https://github.com/airbytehq/airbyte/pull/30279) | Fix correctness issues in layer caching by making atomic execution groupings | -| 1.1.1 | [#30252](https://github.com/airbytehq/airbyte/pull/30252) | Fix redundancies and broken logic in GradleTask, to speed up the CI runs. | -| 1.1.0 | [#29509](https://github.com/airbytehq/airbyte/pull/29509) | Refactor the airbyte-ci test command to run tests on any poetry package. | -| 1.0.0 | [#28000](https://github.com/airbytehq/airbyte/pull/29232) | Remove release stages in favor of support level from airbyte-ci. | -| 0.5.0 | [#28000](https://github.com/airbytehq/airbyte/pull/28000) | Run connector acceptance tests with dagger-in-dagger. | -| 0.4.7 | [#29156](https://github.com/airbytehq/airbyte/pull/29156) | Improve how we check existence of requirement.txt or setup.py file to not raise early pip install errors. | -| 0.4.6 | [#28729](https://github.com/airbytehq/airbyte/pull/28729) | Use keyword args instead of positional argument for optional paramater in Dagger's API | -| 0.4.5 | [#29034](https://github.com/airbytehq/airbyte/pull/29034) | Disable Dagger terminal UI when running publish. | -| 0.4.4 | [#29064](https://github.com/airbytehq/airbyte/pull/29064) | Make connector modified files a frozen set. | -| 0.4.3 | [#29033](https://github.com/airbytehq/airbyte/pull/29033) | Disable dependency scanning for Java connectors. | -| 0.4.2 | [#29030](https://github.com/airbytehq/airbyte/pull/29030) | Make report path always have the same prefix: `airbyte-ci/`. | -| 0.4.1 | [#28855](https://github.com/airbytehq/airbyte/pull/28855) | Improve the selected connectors detection for connectors commands. | -| 0.4.0 | [#28947](https://github.com/airbytehq/airbyte/pull/28947) | Show Dagger Cloud run URLs in CI | -| 0.3.2 | [#28789](https://github.com/airbytehq/airbyte/pull/28789) | Do not consider empty reports as successfull. | -| 0.3.1 | [#28938](https://github.com/airbytehq/airbyte/pull/28938) | Handle 5 status code on MetadataUpload as skipped | -| 0.3.0 | [#28869](https://github.com/airbytehq/airbyte/pull/28869) | Enable the Dagger terminal UI on local `airbyte-ci` execution | -| 0.2.3 | [#28907](https://github.com/airbytehq/airbyte/pull/28907) | Make dagger-in-dagger work for `airbyte-ci tests` command | -| 0.2.2 | [#28897](https://github.com/airbytehq/airbyte/pull/28897) | Sentry: Ignore error logs without exceptions from reporting | -| 0.2.1 | [#28767](https://github.com/airbytehq/airbyte/pull/28767) | Improve pytest step result evaluation to prevent false negative/positive. | -| 0.2.0 | [#28857](https://github.com/airbytehq/airbyte/pull/28857) | Add the `airbyte-ci tests` command to run the test suite on any `airbyte-ci` poetry package. | -| 0.1.1 | [#28858](https://github.com/airbytehq/airbyte/pull/28858) | Increase the max duration of Connector Package install to 20mn. | -| 0.1.0 | | Alpha version not in production yet. All the commands described in this doc are available. | - -## More info - -This project is owned by the Connectors Operations team. We share project updates and remaining -stories before its release to production in this -[EPIC](https://github.com/airbytehq/airbyte/issues/24403). - -# Troubleshooting - -## Commands - -### `make tools.airbyte-ci.check` - -This command checks if the `airbyte-ci` command is appropriately installed. - -### `make tools.airbyte-ci.clean` - -This command removes the `airbyte-ci` command from your system. - -## Common issues - -### `airbyte-ci` is not found - -If you get the following error when running `airbyte-ci`: - -```bash -$ airbyte-ci -zsh: command not found: airbyte-ci -``` - -It means that the `airbyte-ci` command is not in your PATH. - -Try running - -```bash -make make tools.airbyte-ci.check -``` - -For some hints on how to fix this. - -But when in doubt it can be best to run - -```bash -make tools.airbyte-ci.clean -``` - -Then reinstall the CLI with - -```bash -make tools.airbyte-ci.install -``` - -## Development - -### `airbyte-ci` is not found - -To fix this, you can either: - -- Ensure that airbyte-ci is installed with pipx. Run `pipx list` to check if airbyte-ci is - installed. -- Run `pipx ensurepath` to add the pipx binary directory to your PATH. -- Add the pipx binary directory to your PATH manually. The pipx binary directory is usually - `~/.local/bin`. - -### python3.10 not found - -If you get the following error when running -`pipx install --editable --force --python=python3.10 airbyte-ci/connectors/pipelines/`: - -```bash -$ pipx install --editable --force --python=python3.10 airbyte-ci/connectors/pipelines/ -Error: Python 3.10 not found on your system. -``` - -It means that you don't have Python 3.10 installed on your system. - -To fix this, you can either: - -- Install Python 3.10 with pyenv. Run `pyenv install 3.10` to install the latest Python version. -- Install Python 3.10 with your system package manager. For instance, on Ubuntu you can run - `sudo apt install python3.10`. -- Ensure that Python 3.10 is in your PATH. Run `which python3.10` to check if Python 3.10 is - installed and in your PATH. - -### Any type of pipeline failure - -First you should check that the version of the CLI you are using is the latest one. You can check -the version of the CLI with the `--version` option: - -```bash -$ airbyte-ci --version -airbyte-ci, version 0.1.0 -``` - -and compare it with the version in the pyproject.toml file: - -```bash -$ cat airbyte-ci/connectors/pipelines/pyproject.toml | grep version -``` - -If you get any type of pipeline failure, you can run the pipeline with the `--show-dagger-logs` -option to get more information about the failure. - -```bash -$ airbyte-ci --show-dagger-logs connectors --name=source-pokeapi test -``` - -and when in doubt, you can reinstall the CLI with the `--force` option: - -```bash -$ pipx reinstall pipelines --force -``` diff --git a/airbyte-ci/connectors/pipelines/pipelines/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/__init__.py deleted file mode 100644 index 4b1a6ecc74dd..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""The pipelines package.""" -import logging -import os -from typing import Union -from rich.logging import RichHandler - -from .helpers import sentry_utils - -sentry_utils.initialize() - -logging.getLogger("requests").setLevel(logging.WARNING) -logging.getLogger("urllib3").setLevel(logging.WARNING) -logging.getLogger("httpx").setLevel(logging.WARNING) - -# RichHandler does not work great in the CI environment, so we use a StreamHandler instead -logging_handler: Union[RichHandler, logging.StreamHandler] = RichHandler(rich_tracebacks=True) if "CI" not in os.environ else logging.StreamHandler() - - -logging.basicConfig( - level=logging.INFO, - format="%(name)s: %(message)s", - datefmt="[%X]", - handlers=[logging_handler], -) - -main_logger = logging.getLogger(__name__) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/commands.py deleted file mode 100644 index b0a7f223af3a..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/commands.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import List - -import asyncclick as click -import dagger - -from pipelines import main_logger -from pipelines.airbyte_ci.connectors.build_image.steps import run_connector_build_pipeline -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.consts import BUILD_PLATFORMS, LOCAL_BUILD_PLATFORM - - -@click.command(cls=DaggerPipelineCommand, help="Build all images for the selected connectors.") -@click.option( - "--use-host-gradle-dist-tar", - is_flag=True, - help="Use gradle distTar output from host for java connectors.", - default=False, - type=bool, -) -@click.option( - "-a", - "--architecture", - "build_architectures", - help="Architecture for which to build the connector image. If not specified, the image will be built for the local architecture.", - multiple=True, - default=[LOCAL_BUILD_PLATFORM], - type=click.Choice(BUILD_PLATFORMS, case_sensitive=True), -) -@click.option( - "-t", - "--tag", - help="The tag to use for the built image.", - default="dev", - type=str, -) -@click.pass_context -async def build(ctx: click.Context, use_host_gradle_dist_tar: bool, build_architectures: List[str], tag: str) -> bool: - """Runs a build pipeline for the selected connectors.""" - build_platforms = [dagger.Platform(architecture) for architecture in build_architectures] - main_logger.info(f"Building connectors for {build_platforms}, use --architecture to change this.") - connectors_contexts = [ - ConnectorContext( - pipeline_name=f"Build connector {connector.technical_name}", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - use_local_cdk=ctx.obj.get("use_local_cdk"), - use_cdk_ref=ctx.obj.get("use_cdk_ref"), - enable_report_auto_open=ctx.obj.get("enable_report_auto_open"), - use_host_gradle_dist_tar=use_host_gradle_dist_tar, - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - targeted_platforms=build_platforms, - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - if use_host_gradle_dist_tar and not ctx.obj["is_local"]: - raise Exception("flag --use-host-gradle-dist-tar requires --is-local") - await run_connectors_pipelines( - connectors_contexts, - run_connector_build_pipeline, - "Build Pipeline", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - tag, - ) - - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/pipeline.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/pipeline.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/__init__.py deleted file mode 100644 index 5e2fa42230b9..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -"""This module groups factory like functions to dispatch builds steps according to the connector language.""" - -from __future__ import annotations - -import anyio -from connector_ops.utils import ConnectorLanguage # type: ignore -from pipelines.airbyte_ci.connectors.build_image.steps import java_connectors, python_connectors, manifest_only_connectors -from pipelines.airbyte_ci.connectors.build_image.steps.common import LoadContainerToLocalDockerHost, StepStatus -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.reports import ConnectorReport, Report -from pipelines.models.steps import StepResult - - -class NoBuildStepForLanguageError(Exception): - pass - - -LANGUAGE_BUILD_CONNECTOR_MAPPING = { - ConnectorLanguage.PYTHON: python_connectors.run_connector_build, - ConnectorLanguage.LOW_CODE: python_connectors.run_connector_build, - ConnectorLanguage.MANIFEST_ONLY: manifest_only_connectors.run_connector_build, - ConnectorLanguage.JAVA: java_connectors.run_connector_build, -} - - -async def run_connector_build(context: ConnectorContext) -> StepResult: - """Run a build pipeline for a single connector.""" - if context.connector.language not in LANGUAGE_BUILD_CONNECTOR_MAPPING: - raise NoBuildStepForLanguageError(f"No build step for connector language {context.connector.language}.") - return await LANGUAGE_BUILD_CONNECTOR_MAPPING[context.connector.language](context) - - -async def run_connector_build_pipeline(context: ConnectorContext, semaphore: anyio.Semaphore, image_tag: str) -> Report: - """Run a build pipeline for a single connector. - - Args: - context (ConnectorContext): The initialized connector context. - semaphore (anyio.Semaphore): The semaphore to use to limit the number of concurrent builds. - image_tag (str): The tag to use for the built image. - Returns: - ConnectorReport: The reports holding builds results. - """ - step_results = [] - async with semaphore: - async with context: - build_result = await run_connector_build(context) - per_platform_built_containers = build_result.output - step_results.append(build_result) - if context.is_local and build_result.status is StepStatus.SUCCESS: - load_image_result = await LoadContainerToLocalDockerHost(context, image_tag).run(per_platform_built_containers) - step_results.append(load_image_result) - report = ConnectorReport(context, step_results, name="BUILD RESULTS") - context.report = report - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/build_customization.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/build_customization.py deleted file mode 100644 index d0b5fa8acb42..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/build_customization.py +++ /dev/null @@ -1,104 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import importlib -from logging import Logger -from types import ModuleType -from typing import List, Optional - -from connector_ops.utils import Connector # type: ignore -from dagger import Container - -BUILD_CUSTOMIZATION_MODULE_NAME = "build_customization" -BUILD_CUSTOMIZATION_SPEC_NAME = f"{BUILD_CUSTOMIZATION_MODULE_NAME}.py" -DEFAULT_MAIN_FILE_NAME = "main.py" - - -def get_build_customization_module(connector: Connector) -> Optional[ModuleType]: - """Import the build_customization.py file from the connector directory if it exists. - Returns: - Optional[ModuleType]: The build_customization.py module if it exists, None otherwise. - """ - build_customization_spec_path = connector.code_directory / BUILD_CUSTOMIZATION_SPEC_NAME - - if not build_customization_spec_path.exists() or not ( - build_customization_spec := importlib.util.spec_from_file_location( - f"{connector.code_directory.name}_{BUILD_CUSTOMIZATION_MODULE_NAME}", build_customization_spec_path - ) - ): - return None - - if build_customization_spec.loader is None: - return None - - build_customization_module = importlib.util.module_from_spec(build_customization_spec) - build_customization_spec.loader.exec_module(build_customization_module) - return build_customization_module - - -def get_main_file_name(connector: Connector) -> str: - """Get the main file name from the build_customization.py module if it exists, DEFAULT_MAIN_FILE_NAME otherwise. - - Args: - connector (Connector): The connector to build. - - Returns: - str: The main file name. - """ - build_customization_module = get_build_customization_module(connector) - - return ( - build_customization_module.MAIN_FILE_NAME - if build_customization_module and hasattr(build_customization_module, "MAIN_FILE_NAME") - else DEFAULT_MAIN_FILE_NAME - ) - - -def get_entrypoint(connector: Connector) -> List[str]: - main_file_name = get_main_file_name(connector) - return ["python", f"/airbyte/integration_code/{main_file_name}"] - - -def apply_airbyte_entrypoint(connector_container: Container, connector: Connector) -> Container: - entrypoint = get_entrypoint(connector) - - return connector_container.with_env_variable("AIRBYTE_ENTRYPOINT", " ".join(entrypoint)).with_entrypoint(entrypoint) - - -async def pre_install_hooks(connector: Connector, base_container: Container, logger: Logger) -> Container: - """Run the pre_connector_install hook if it exists in the build_customization.py module. - It will mutate the base_container and return it. - - Args: - connector (Connector): The connector to build. - base_container (Container): The base container to mutate. - logger (Logger): The logger to use. - - Returns: - Container: The mutated base_container. - """ - build_customization_module = get_build_customization_module(connector) - if build_customization_module and hasattr(build_customization_module, "pre_connector_install"): - base_container = await build_customization_module.pre_connector_install(base_container) - logger.info(f"Connector {connector.technical_name} pre install hook executed.") - return base_container - - -async def post_install_hooks(connector: Connector, connector_container: Container, logger: Logger) -> Container: - """Run the post_connector_install hook if it exists in the build_customization.py module. - It will mutate the connector_container and return it. - - Args: - connector (Connector): The connector to build. - connector_container (Container): The connector container to mutate. - logger (Logger): The logger to use. - - Returns: - Container: The mutated connector_container. - """ - build_customization_module = get_build_customization_module(connector) - if build_customization_module and hasattr(build_customization_module, "post_connector_install"): - connector_container = await build_customization_module.post_connector_install(connector_container) - logger.info(f"Connector {connector.technical_name} post install hook executed.") - return connector_container diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/common.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/common.py deleted file mode 100644 index e0e3c76c27fb..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/common.py +++ /dev/null @@ -1,147 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from abc import ABC -from typing import TYPE_CHECKING - -import docker # type: ignore -from click import UsageError -from connector_ops.utils import Connector # type: ignore -from dagger import Container, ExecError, Platform, QueryError - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.helpers.utils import export_container_to_tarball, sh_dash_c -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from typing import Any, Type, TypeVar - - T = TypeVar("T", bound="BuildConnectorImagesBase") - - -def apply_airbyte_docker_labels(connector_container: Container, connector: Connector) -> Container: - return connector_container.with_label("io.airbyte.version", connector.metadata["dockerImageTag"]).with_label( - "io.airbyte.name", connector.metadata["dockerRepository"] - ) - - -class BuildConnectorImagesBase(Step, ABC): - """ - A step to build connector images for a set of platforms. - """ - - context: ConnectorContext - USER = "airbyte" - - @property - def title(self) -> str: - return f"Build {self.context.connector.technical_name} docker image for platform(s) {', '.join(self.build_platforms)}" - - def __init__(self, context: ConnectorContext) -> None: - self.build_platforms = context.targeted_platforms - super().__init__(context) - - async def _run(self, *args: Any) -> StepResult: - build_results_per_platform = {} - for platform in self.build_platforms: - try: - connector_container = await self._build_connector(platform, *args) - connector_container = apply_airbyte_docker_labels(connector_container, self.context.connector) - try: - await connector_container.with_exec(["spec"], use_entrypoint=True) - except ExecError as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=str(e), - stdout=f"Failed to run the spec command on the connector container for platform {platform}.", - exc_info=e, - ) - build_results_per_platform[platform] = connector_container - except (QueryError, UsageError) as e: - return StepResult( - step=self, status=StepStatus.FAILURE, stderr=f"Failed to build connector image for platform {platform}: {e}" - ) - success_message = ( - f"The {self.context.connector.technical_name} docker image " - f"was successfully built for platform(s) {', '.join(self.build_platforms)}" - ) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=success_message, output=build_results_per_platform) - - async def _build_connector(self, platform: Platform, *args: Any, **kwargs: Any) -> Container: - """Implement the generation of the image for the platform and return the corresponding container. - - Returns: - Container: The container to package as a docker image for this platform. - """ - raise NotImplementedError("`BuildConnectorImagesBase`s must define a '_build_connector' attribute.") - - @classmethod - async def get_image_user(cls: Type[T], base_container: Container) -> str: - """If the base image in use has a user named 'airbyte', we will use it as the user for the connector image. - - Args: - base_container (Container): The base container to use to build the connector. - - Returns: - str: The user to use for the connector image. - """ - users = (await base_container.with_exec(sh_dash_c(["cut -d: -f1 /etc/passwd | sort | uniq"])).stdout()).splitlines() - if cls.USER in users: - return cls.USER - return "root" - - -class LoadContainerToLocalDockerHost(Step): - context: ConnectorContext - - def __init__(self, context: ConnectorContext, image_tag: str = "dev") -> None: - super().__init__(context) - self.image_tag = image_tag - - def _generate_dev_tag(self, platform: Platform, multi_platforms: bool) -> str: - """ - When building for multiple platforms, we need to tag the image with the platform name. - There's no way to locally build a multi-arch image, so we need to tag the image with the platform name when the user passed multiple architecture options. - """ - return f"{self.image_tag}-{platform.replace('/', '-')}" if multi_platforms else self.image_tag - - @property - def title(self) -> str: - return f"Load {self.image_name}:{self.image_tag} to the local docker host." - - @property - def image_name(self) -> str: - return f"airbyte/{self.context.connector.technical_name}" - - async def _run(self, containers: dict[Platform, Container]) -> StepResult: - loaded_images = [] - image_sha = None - multi_platforms = len(containers) > 1 - for platform, container in containers.items(): - _, exported_tar_path = await export_container_to_tarball(self.context, container, platform) - if not exported_tar_path: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=f"Failed to export the connector image {self.image_name}:{self.image_tag} to a tarball.", - ) - try: - client = docker.from_env() - image_tag = self._generate_dev_tag(platform, multi_platforms) - full_image_name = f"{self.image_name}:{image_tag}" - with open(exported_tar_path, "rb") as tarball_content: - new_image = client.images.load(tarball_content.read())[0] - new_image.tag(self.image_name, tag=image_tag) - image_sha = new_image.id - loaded_images.append(full_image_name) - except docker.errors.DockerException as e: - return StepResult( - step=self, status=StepStatus.FAILURE, stderr=f"Something went wrong while interacting with the local docker client: {e}" - ) - - return StepResult( - step=self, status=StepStatus.SUCCESS, stdout=f"Loaded image {','.join(loaded_images)} to your Docker host ({image_sha})." - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/java_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/java_connectors.py deleted file mode 100644 index 75bf89a73d0d..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/java_connectors.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from dagger import Container, Directory, File, Platform, QueryError - -from pipelines.airbyte_ci.connectors.build_image.steps.common import BuildConnectorImagesBase -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.steps.gradle import GradleTask -from pipelines.dagger.containers import java -from pipelines.models.steps import StepResult, StepStatus - - -class BuildConnectorDistributionTar(GradleTask): - """ - A step to build a Java connector image using the distTar Gradle task. - """ - - title = "Build connector tar" - gradle_task_name = "distTar" - - -class BuildConnectorImages(BuildConnectorImagesBase): - """ - A step to build Java connector images using the distTar Gradle task. - """ - - async def _run(self, dist_dir: Directory) -> StepResult: - dist_tar: File - try: - dir_files = await dist_dir.entries() - tar_files = [f for f in dir_files if f.endswith(".tar")] - num_files = len(tar_files) - if num_files != 1: - error_message = ( - "The distribution tar file for the current java connector was not built." - if num_files == 0 - else "More than one distribution tar file was built for the current java connector." - ) - return StepResult(step=self, status=StepStatus.FAILURE, stderr=error_message) - dist_tar = dist_dir.file(tar_files[0]) - except QueryError as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - return await super()._run(dist_tar) - - async def _build_connector(self, platform: Platform, dist_tar: File) -> Container: - return await java.with_airbyte_java_connector(self.context, dist_tar, platform) - - -async def run_connector_build(context: ConnectorContext) -> StepResult: - """Create the java connector distribution tar file and build the connector image.""" - - if context.use_host_gradle_dist_tar and context.is_local: - # Special case: use a local dist tar to speed up local development. - dist_dir = await context.dagger_client.host().directory(dist_tar_directory_path(context), include=["*.tar"]) - # Speed things up by only building for the local platform. - return await BuildConnectorImages(context).run(dist_dir) - - # Default case: distribution tar is built by the dagger pipeline. - build_connector_tar_result = await BuildConnectorDistributionTar(context).run() - if build_connector_tar_result.status is not StepStatus.SUCCESS: - return build_connector_tar_result - - dist_dir = await build_connector_tar_result.output.directory("build/distributions") - return await BuildConnectorImages(context).run(dist_dir) - - -def dist_tar_directory_path(context: ConnectorContext) -> str: - return f"{context.connector.code_directory}/build/distributions" diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/manifest_only_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/manifest_only_connectors.py deleted file mode 100644 index 72c9dc386f1e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/manifest_only_connectors.py +++ /dev/null @@ -1,71 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -from typing import Any - -from dagger import Container, Platform -from pydash.objects import get # type: ignore - -from pipelines.airbyte_ci.connectors.build_image.steps import build_customization -from pipelines.airbyte_ci.connectors.build_image.steps.common import BuildConnectorImagesBase -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.consts import COMPONENTS_FILE_PATH, MANIFEST_FILE_PATH -from pipelines.dagger.actions.python.common import apply_python_development_overrides -from pipelines.models.steps import StepResult - - -class BuildConnectorImages(BuildConnectorImagesBase): - """ - A step to build a manifest only connector image. - A spec command is run on the container to validate it was built successfully. - """ - - context: ConnectorContext - PATH_TO_INTEGRATION_CODE = "/airbyte/integration_code" - - async def _build_connector(self, platform: Platform, *args: Any) -> Container: - baseImage = get(self.context.connector.metadata, "connectorBuildOptions.baseImage") - if not baseImage: - raise ValueError("connectorBuildOptions.baseImage is required to build a manifest only connector.") - - return await self._build_from_base_image(platform) - - def _get_base_container(self, platform: Platform) -> Container: - base_image_name = get(self.context.connector.metadata, "connectorBuildOptions.baseImage") - self.logger.info(f"Building manifest connector from base image {base_image_name}") - return self.dagger_client.container(platform=platform).from_(base_image_name) - - async def _build_from_base_image(self, platform: Platform) -> Container: - """Build the connector container using the base image defined in the metadata, in the connectorBuildOptions.baseImage field. - - Returns: - Container: The connector container built from the base image. - """ - self.logger.info(f"Building connector from base image in metadata for {platform}") - - # Mount manifest file - base_container = self._get_base_container(platform) - user = await self.get_image_user(base_container) - base_container = base_container.with_file( - f"source_declarative_manifest/{MANIFEST_FILE_PATH}", - (await self.context.get_connector_dir(include=[MANIFEST_FILE_PATH])).file(MANIFEST_FILE_PATH), - owner=user, - ) - - # Mount components file if it exists - components_file = self.context.connector.manifest_only_components_path - if components_file.exists(): - base_container = base_container.with_file( - f"source_declarative_manifest/{COMPONENTS_FILE_PATH}", - (await self.context.get_connector_dir(include=[COMPONENTS_FILE_PATH])).file(COMPONENTS_FILE_PATH), - owner=user, - ) - connector_container = build_customization.apply_airbyte_entrypoint(base_container, self.context.connector) - connector_container = await apply_python_development_overrides(self.context, connector_container, user) - return connector_container - - -async def run_connector_build(context: ConnectorContext) -> StepResult: - return await BuildConnectorImages(context).run() diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/normalization.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/normalization.py deleted file mode 100644 index 2cfe294a0cac..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/normalization.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from dagger import Platform - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.dagger.actions.connector import normalization -from pipelines.models.steps import Step, StepResult, StepStatus - - -# TODO this class could be deleted -# if java connectors tests are not relying on an existing local normalization image to run -class BuildOrPullNormalization(Step): - """A step to build or pull the normalization image for a connector according to the image name.""" - - context: ConnectorContext - - def __init__(self, context: ConnectorContext, normalization_image: str, build_platform: Platform) -> None: - """Initialize the step to build or pull the normalization image. - - Args: - context (ConnectorContext): The current connector context. - normalization_image (str): The normalization image to build (if :dev) or pull. - """ - super().__init__(context) - self.build_platform = build_platform - self.use_dev_normalization = normalization_image.endswith(":dev") - self.normalization_image = normalization_image - - @property - def title(self) -> str: - return f"Build {self.normalization_image}" if self.use_dev_normalization else f"Pull {self.normalization_image}" - - async def _run(self) -> StepResult: - if self.use_dev_normalization: - build_normalization_container = normalization.with_normalization(self.context, self.build_platform) - else: - build_normalization_container = self.context.dagger_client.container().from_(self.normalization_image) - return StepResult(step=self, status=StepStatus.SUCCESS, output=build_normalization_container) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/python_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/python_connectors.py deleted file mode 100644 index aaddf8152ded..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/build_image/steps/python_connectors.py +++ /dev/null @@ -1,113 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -from typing import Any - -from dagger import Container, Platform - -from pipelines.airbyte_ci.connectors.build_image.steps import build_customization -from pipelines.airbyte_ci.connectors.build_image.steps.common import BuildConnectorImagesBase -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.dagger.actions.python.common import apply_python_development_overrides, with_python_connector_installed -from pipelines.models.steps import StepResult - - -class BuildConnectorImages(BuildConnectorImagesBase): - """ - A step to build a Python connector image. - A spec command is run on the container to validate it was built successfully. - """ - - context: ConnectorContext - PATH_TO_INTEGRATION_CODE = "/airbyte/integration_code" - - async def _build_connector(self, platform: Platform, *args: Any) -> Container: - if ( - "connectorBuildOptions" in self.context.connector.metadata - and "baseImage" in self.context.connector.metadata["connectorBuildOptions"] - ): - return await self._build_from_base_image(platform) - else: - return await self._build_from_dockerfile(platform) - - def _get_base_container(self, platform: Platform) -> Container: - base_image_name = self.context.connector.metadata["connectorBuildOptions"]["baseImage"] - self.logger.info(f"Building connector from base image {base_image_name}") - return self.dagger_client.container(platform=platform).from_(base_image_name) - - async def _create_builder_container(self, base_container: Container, user: str) -> Container: - """Pre install the connector dependencies in a builder container. - - Args: - base_container (Container): The base container to use to build the connector. - user (str): The user to use in the container. - Returns: - Container: The builder container, with installed dependencies. - """ - ONLY_BUILD_FILES = ["pyproject.toml", "poetry.lock", "poetry.toml", "setup.py", "requirements.txt", "README.md"] - - builder = await with_python_connector_installed( - self.context, - base_container, - str(self.context.connector.code_directory), - user, - install_root_package=False, - include=ONLY_BUILD_FILES, - ) - return builder - - async def _build_from_base_image(self, platform: Platform) -> Container: - """Build the connector container using the base image defined in the metadata, in the connectorBuildOptions.baseImage field. - - Returns: - Container: The connector container built from the base image. - """ - self.logger.info(f"Building connector from base image in metadata for {platform}") - base = self._get_base_container(platform) - user = await self.get_image_user(base) - customized_base = await build_customization.pre_install_hooks(self.context.connector, base, self.logger) - main_file_name = build_customization.get_main_file_name(self.context.connector) - - builder = await self._create_builder_container(customized_base, user) - - # The snake case name of the connector corresponds to the python package name of the connector - # We want to mount it to the container under PATH_TO_INTEGRATION_CODE/connector_snake_case_name - connector_snake_case_name = self.context.connector.technical_name.replace("-", "_") - - base_connector_container = ( - # copy python dependencies from builder to connector container - customized_base.with_directory("/usr/local", builder.directory("/usr/local")) - .with_workdir(self.PATH_TO_INTEGRATION_CODE) - .with_exec(["chown", "-R", f"{user}:{user}", "."]) - .with_file(main_file_name, (await self.context.get_connector_dir(include=[main_file_name])).file(main_file_name), owner=user) - .with_directory( - connector_snake_case_name, - (await self.context.get_connector_dir(include=[connector_snake_case_name])).directory(connector_snake_case_name), - owner=user, - ) - ) - - connector_container = build_customization.apply_airbyte_entrypoint(base_connector_container, self.context.connector) - customized_connector = await build_customization.post_install_hooks(self.context.connector, connector_container, self.logger) - # Make sure the user has access to /tmp - customized_connector = customized_connector.with_exec(["chown", "-R", f"{user}:{user}", "/tmp"]) - return customized_connector.with_user(user) - - async def _build_from_dockerfile(self, platform: Platform) -> Container: - """Build the connector container using its Dockerfile. - - Returns: - Container: The connector container built from its Dockerfile. - """ - self.logger.warn( - "This connector is built from its Dockerfile. This is now deprecated. Please set connectorBuildOptions.baseImage metadata field to use our new build process." - ) - container = self.dagger_client.container(platform=platform).build(await self.context.get_connector_dir()) - container = await apply_python_development_overrides(self.context, container, "root") - return container - - -async def run_connector_build(context: ConnectorContext) -> StepResult: - return await BuildConnectorImages(context).run() diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/commands.py deleted file mode 100644 index 22dee605124b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/commands.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import Optional - -import asyncclick as click -import semver - -from pipelines.airbyte_ci.connectors.bump_version.pipeline import run_connector_version_bump_pipeline -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand - - -class BumpType(click.ParamType): - name = "bump-type" - - def __init__(self) -> None: - self.choices = ["patch", "minor", "major", "rc"] - - def convert(self, value: str, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str: - if value in self.choices: - return value - if value.startswith("version:"): - version_str = value.split("version:", 1)[1] - if semver.VersionInfo.is_valid(version_str): - return value - self.fail(f"{value} is not a valid bump type. Valid choices are {self.choices} or 'version:'.", param, ctx) - - def __repr__(self) -> str: - return "BumpType" - - -@click.command(cls=DaggerPipelineCommand, short_help="Bump a connector version and update its changelog.") -@click.argument("bump-type", type=BumpType()) -@click.argument("changelog-entry", type=str) -@click.option("--pr-number", type=int, help="Pull request number.") -@click.option("--rc", is_flag=True, help="Bumps version number and appends a release candidate suffix.") -@click.pass_context -async def bump_version( - ctx: click.Context, - bump_type: str, - changelog_entry: str, - pr_number: int | None, - rc: bool, -) -> bool: - """Bump a connector version: update metadata.yaml and changelog.""" - - if rc and bump_type == "rc": - raise click.BadParameter("The `--rc` flag cannot be used when the specified `bump-type` is `rc`.") - - connectors_contexts = [ - ConnectorContext( - pipeline_name=f"Bump version of connector {connector.technical_name}", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - ci_git_user=ctx.obj["ci_git_user"], - ci_github_access_token=ctx.obj["ci_github_access_token"], - enable_report_auto_open=False, - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - docker_hub_username=ctx.obj.get("docker_hub_username"), - docker_hub_password=ctx.obj.get("docker_hub_password"), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - - await run_connectors_pipelines( - connectors_contexts, - run_connector_version_bump_pipeline, - "Version bump pipeline pipeline", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - bump_type, - changelog_entry, - rc, - pr_number, - ) - - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/pipeline.py deleted file mode 100644 index 57b7ac71c81f..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/bump_version/pipeline.py +++ /dev/null @@ -1,109 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from typing import TYPE_CHECKING - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.reports import ConnectorReport, Report -from pipelines.airbyte_ci.steps.bump_version import BumpConnectorVersion -from pipelines.airbyte_ci.steps.changelog import AddChangelogEntry -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from anyio import Semaphore - - -class RestoreVersionState(Step): - context: ConnectorContext - - title = "Restore original version state" - - def __init__(self, context: ConnectorContext) -> None: - super().__init__(context) - connector = context.connector - if connector.metadata_file_path.is_file(): - self.metadata_content = connector.metadata_file_path.read_text() - else: - self.metadata_content = None - - if connector.dockerfile_file_path.is_file(): - self.dockerfile_content = connector.dockerfile_file_path.read_text() - else: - self.dockerfile_content = None - - if connector.pyproject_file_path.is_file(): - self.poetry_content = connector.pyproject_file_path.read_text() - else: - self.poetry_content = None - - if connector.documentation_file_path and connector.documentation_file_path.is_file(): - self.documentation_content = connector.documentation_file_path.read_text() - else: - self.documentation_content = None - - async def _run(self) -> StepResult: - connector = self.context.connector - if self.metadata_content: - connector.metadata_file_path.write_text(self.metadata_content) - if self.dockerfile_content: - connector.dockerfile_file_path.write_text(self.dockerfile_content) - if self.poetry_content: - connector.pyproject_file_path.write_text(self.poetry_content) - if self.documentation_content and connector.documentation_file_path: - connector.documentation_file_path.write_text(self.documentation_content) - return StepResult(step=self, status=StepStatus.SUCCESS) - - async def _cleanup(self) -> StepResult: - return StepResult(step=self, status=StepStatus.SUCCESS) - - -async def run_connector_version_bump_pipeline( - context: ConnectorContext, - semaphore: "Semaphore", - bump_type: str, - changelog_entry: str, - rc: bool, - pull_request_number: str | int | None = None, -) -> Report: - """Run a pipeline to upgrade for a single connector. - - Args: - context (ConnectorContext): The initialized connector context. - - Returns: - Report: The reports holding the base image version upgrade results. - """ - report_name = "CONNECTOR VERSION BUMP RESULTS" - async with semaphore: - steps_results = [] - async with context: - connector_directory = await context.get_connector_dir() - bump_version = BumpConnectorVersion(context, connector_directory, bump_type, rc) - bump_version_result = await bump_version.run() - steps_results.append(bump_version_result) - if not bump_version_result.success: - report = ConnectorReport(context, steps_results, name=report_name) - context.report = report - return report - - updated_connector_directory = bump_version_result.output - for modified_file in bump_version.modified_files: - await updated_connector_directory.file(modified_file).export(str(context.connector.code_directory / modified_file)) - context.logger.info(f"Exported {modified_file} following the version bump.") - documentation_directory = await context.get_repo_dir( - include=[str(context.connector.local_connector_documentation_directory)] - ).directory(str(context.connector.local_connector_documentation_directory)) - add_changelog_entry = AddChangelogEntry( - context, documentation_directory, bump_version.new_version, changelog_entry, pull_request_number=pull_request_number - ) - add_changelog_entry_result = await add_changelog_entry.run() - steps_results.append(add_changelog_entry_result) - if not add_changelog_entry_result.success: - report = ConnectorReport(context, steps_results, name=report_name) - context.report = report - return report - - await add_changelog_entry.export_modified_files(context.connector.local_connector_documentation_directory) - report = ConnectorReport(context, steps_results, name=report_name) - context.report = report - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py deleted file mode 100644 index 5fc793533a11..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/commands.py +++ /dev/null @@ -1,275 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import os -from pathlib import Path -from typing import List, Set, Tuple - -import asyncclick as click -from connector_ops.utils import Connector, ConnectorLanguage, SupportLevelEnum, get_all_connectors_in_repo # type: ignore - -from pipelines import main_logger -from pipelines.cli.airbyte_ci import wrap_in_secret -from pipelines.cli.click_decorators import click_ignore_unused_kwargs, click_merge_args_into_context_obj -from pipelines.cli.lazy_group import LazyGroup -from pipelines.helpers.connectors.modifed import ConnectorWithModifiedFiles, get_connector_modified_files, get_modified_connectors -from pipelines.helpers.git import get_modified_files -from pipelines.helpers.utils import transform_strs_to_paths - -ALL_CONNECTORS = get_all_connectors_in_repo() -CONNECTORS_WITH_STRICT_ENCRYPT_VARIANTS = { - Connector(c.relative_connector_path.replace("-strict-encrypt", "")) - for c in ALL_CONNECTORS - if c.technical_name.endswith("-strict-encrypt") -} - - -def log_selected_connectors(selected_connectors_with_modified_files: List[ConnectorWithModifiedFiles]) -> None: - """Logs selected connector names into main logger, or says no connectors to run.""" - if selected_connectors_with_modified_files: - selected_connectors_names = [c.technical_name for c in selected_connectors_with_modified_files] - main_logger.info(f"Will run on the following {len(selected_connectors_names)} connectors: {', '.join(selected_connectors_names)}.") - else: - main_logger.info("No connectors to run.") - - -def get_base_connector_and_variants(connector: Connector) -> Set[Connector]: - """Returns the connector's name and it's strict-encrypt variant.""" - base_connector_path = connector.relative_connector_path.replace("-strict-encrypt", "") - base_connector = Connector(base_connector_path) - strict_encrypt_connector_path = f"{base_connector.relative_connector_path}-strict-encrypt" - if base_connector in CONNECTORS_WITH_STRICT_ENCRYPT_VARIANTS: - return {base_connector, Connector(strict_encrypt_connector_path)} - else: - return {base_connector} - - -def update_selected_connectors_with_variants(selected_connectors: Set[Connector]) -> Set[Connector]: - """Returns the connectors and their variants.""" - updated_selected_connectors = set() - for selected_connector in selected_connectors: - updated_selected_connectors.update(get_base_connector_and_variants(selected_connector)) - return updated_selected_connectors - - -def get_selected_connectors_with_modified_files( - selected_names: Tuple[str], - selected_support_levels: Tuple[str], - selected_languages: Tuple[str], - modified: bool, - metadata_changes_only: bool, - metadata_query: str, - modified_files: Set[Path], - enable_dependency_scanning: bool = False, -) -> List[ConnectorWithModifiedFiles]: - """Get the connectors that match the selected criteria. - - Args: - selected_names (Tuple[str]): Selected connector names. - selected_support_levels (Tuple[str]): Selected connector support levels. - selected_languages (Tuple[str]): Selected connector languages. - modified (bool): Whether to select the modified connectors. - metadata_changes_only (bool): Whether to select only the connectors with metadata changes. - modified_files (Set[Path]): The modified files. - enable_dependency_scanning (bool): Whether to enable the dependency scanning. - Returns: - List[ConnectorWithModifiedFiles]: The connectors that match the selected criteria. - """ - - if metadata_changes_only and not modified: - main_logger.info("--metadata-changes-only overrides --modified") - modified = True - - selected_modified_connectors = ( - get_modified_connectors(modified_files, ALL_CONNECTORS, enable_dependency_scanning) if modified else set() - ) - selected_connectors_by_name = {c for c in ALL_CONNECTORS if c.technical_name in selected_names} - selected_connectors_by_support_level = {connector for connector in ALL_CONNECTORS if connector.support_level in selected_support_levels} - selected_connectors_by_language = {connector for connector in ALL_CONNECTORS if connector.language in selected_languages} - selected_connectors_by_query = ( - {connector for connector in ALL_CONNECTORS if connector.metadata_query_match(metadata_query)} if metadata_query else set() - ) - - non_empty_connector_sets = [ - connector_set - for connector_set in [ - selected_connectors_by_name, - selected_connectors_by_support_level, - selected_connectors_by_language, - selected_connectors_by_query, - selected_modified_connectors, - ] - if connector_set - ] - # The selected connectors are the intersection of the selected connectors by name, support_level, language, simpleeval query and modified. - selected_connectors = set.intersection(*non_empty_connector_sets) if non_empty_connector_sets else set() - - # We always want to pair selection of a base connector with its variant, e vice versa - selected_connectors = update_selected_connectors_with_variants(selected_connectors) - - selected_connectors_with_modified_files = [] - for connector in selected_connectors: - connector_with_modified_files = ConnectorWithModifiedFiles( - relative_connector_path=connector.relative_connector_path, - modified_files=get_connector_modified_files(connector, modified_files), - ) - if not metadata_changes_only: - selected_connectors_with_modified_files.append(connector_with_modified_files) - else: - if connector_with_modified_files.has_metadata_change: - selected_connectors_with_modified_files.append(connector_with_modified_files) - - return selected_connectors_with_modified_files - - -def validate_environment(is_local: bool) -> None: - """Check if the required environment variables exist.""" - if is_local: - if not Path(".git").is_dir(): - raise click.UsageError("You need to run this command from the repository root.") - else: - required_env_vars_for_ci = [ - "CI_GITHUB_ACCESS_TOKEN", - "DOCKER_HUB_USERNAME", - "DOCKER_HUB_PASSWORD", - ] - for required_env_var in required_env_vars_for_ci: - if os.getenv(required_env_var) is None: - raise click.UsageError(f"When running in a CI context a {required_env_var} environment variable must be set.") - - -@click.group( - cls=LazyGroup, - help="Commands related to connectors and connector acceptance tests.", - lazy_subcommands={ - "build": "pipelines.airbyte_ci.connectors.build_image.commands.build", - "test": "pipelines.airbyte_ci.connectors.test.commands.test", - "list": "pipelines.airbyte_ci.connectors.list.commands.list_connectors", - "publish": "pipelines.airbyte_ci.connectors.publish.commands.publish", - "bump-version": "pipelines.airbyte_ci.connectors.bump_version.commands.bump_version", - "migrate-to-manifest-only": "pipelines.airbyte_ci.connectors.migrate_to_manifest_only.commands.migrate_to_manifest_only", - "migrate-to-inline-schemas": "pipelines.airbyte_ci.connectors.migrate_to_inline_schemas.commands.migrate_to_inline_schemas", - "generate-erd": "pipelines.airbyte_ci.connectors.generate_erd.commands.generate_erd", - "upgrade-cdk": "pipelines.airbyte_ci.connectors.upgrade_cdk.commands.upgrade_cdk", - "up-to-date": "pipelines.airbyte_ci.connectors.up_to_date.commands.up_to_date", - "pull-request": "pipelines.airbyte_ci.connectors.pull_request.commands.pull_request", - }, -) -@click.option( - "--name", - "names", - multiple=True, - help="Only test a specific connector. Use its technical name. e.g source-pokeapi.", - type=click.Choice([c.technical_name for c in ALL_CONNECTORS]), -) -@click.option("--language", "languages", multiple=True, help="Filter connectors to test by language.", type=click.Choice(ConnectorLanguage)) -@click.option( - "--support-level", - "support_levels", - multiple=True, - help="Filter connectors to test by support_level.", - type=click.Choice(SupportLevelEnum), -) -@click.option( - "--modified/--not-modified", - help="Only test modified connectors in the current branch. Archived connectors are ignored", - default=False, - type=bool, -) -@click.option( - "--metadata-changes-only/--not-metadata-changes-only", - help="Only test connectors with modified metadata files in the current branch.", - default=False, - type=bool, -) -@click.option( - "--metadata-query", - help="Filter connectors by metadata query using `simpleeval`. e.g. 'data.ab_internal.ql == 200'", - type=str, -) -@click.option("--concurrency", help="Number of connector tests pipeline to run in parallel.", default=5, type=int) -@click.option( - "--execute-timeout", - help="The maximum time in seconds for the execution of a Dagger request before an ExecuteTimeoutError is raised. Passing None results in waiting forever.", - default=None, - type=int, -) -@click.option( - "--enable-dependency-scanning/--disable-dependency-scanning", - help="When enabled, the dependency scanning will be performed to detect the connectors to test according to a dependency change.", - default=False, - type=bool, -) -@click.option( - "--use-local-cdk", - is_flag=True, - help=("Build with the airbyte-cdk from the local repository. " "This is useful for testing changes to the CDK."), - default=False, - type=bool, -) -@click.option( - "--use-cdk-ref", - help=( - "Build with the airbyte-cdk from the specified git ref. " - "This is useful for testing against dev versions or previous versions of the CDK. " - "Ignored for java connectors and if `--use-local-cdk` is set." - ), - type=str, -) -@click.option( - "--enable-report-auto-open/--disable-report-auto-open", - is_flag=True, - help=("When enabled, finishes by opening a browser window to display an HTML report."), - default=True, - type=bool, -) -@click.option( - "--docker-hub-username", - help="Your username to connect to DockerHub.", - type=click.STRING, - required=False, - envvar="DOCKER_HUB_USERNAME", - callback=wrap_in_secret, -) -@click.option( - "--docker-hub-password", - help="Your password to connect to DockerHub.", - type=click.STRING, - required=False, - envvar="DOCKER_HUB_PASSWORD", - callback=wrap_in_secret, -) -@click_merge_args_into_context_obj -@click.pass_context -@click_ignore_unused_kwargs -async def connectors( - ctx: click.Context, -) -> None: - """Group all the connectors-ci command.""" - validate_environment(ctx.obj["is_local"]) - - modified_files = [] - if ctx.obj["modified"] or ctx.obj["metadata_changes_only"]: - modified_files = transform_strs_to_paths( - await get_modified_files( - ctx.obj["git_branch"], - ctx.obj["git_revision"], - ctx.obj["diffed_branch"], - ctx.obj["is_local"], - ctx.obj["ci_context"], - ctx.obj["git_repo_url"], - ) - ) - - ctx.obj["selected_connectors_with_modified_files"] = get_selected_connectors_with_modified_files( - ctx.obj["names"], - ctx.obj["support_levels"], - ctx.obj["languages"], - ctx.obj["modified"], - ctx.obj["metadata_changes_only"], - ctx.obj["metadata_query"], - set(modified_files), - ctx.obj["enable_dependency_scanning"], - ) - log_selected_connectors(ctx.obj["selected_connectors_with_modified_files"]) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/consts.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/consts.py deleted file mode 100644 index b15d82958287..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/consts.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from enum import Enum - - -class CONNECTOR_TEST_STEP_ID(str, Enum): - """ - An enum for the different step ids of the connector test pipeline. - """ - - ACCEPTANCE = "acceptance" - INCREMENTAL_ACCEPTANCE = "incremental_acceptance" - BUILD_NORMALIZATION = "build_normalization" - BUILD_TAR = "build_tar" - BUILD = "build" - INTEGRATION = "integration" - PYTHON_CLI_VALIDATION = "python_cli_validation" - QA_CHECKS = "qa_checks" - UNIT = "unit" - VERSION_INC_CHECK = "version_inc_check" - CONNECTOR_LIVE_TESTS = "connector_live_tests" - REGRESSION_TEST = "common.regression_test" - ADD_CHANGELOG_ENTRY = "bump_version.changelog" - SET_CONNECTOR_VERSION = "bump_version.set" - CHECK_UPDATE_CANDIDATE = "up_to_date.check" - UPDATE_POETRY = "up_to_date.poetry" - UPDATE_PULL_REQUEST = "up_to_date.pull" - LLM_RELATIONSHIPS = "llm_relationships" - DBML_FILE = "dbml_file" - PUBLISH_ERD = "publish_erd" - PULL_REQUEST_CREATE = "pull_request.create" - PULL_REQUEST_UPDATE = "pull_request.update" - MANIFEST_ONLY_CHECK = "migrate_to_manifest_only.check" - MANIFEST_ONLY_STRIP = "migrate_to_manifest_only.strip" - MANIFEST_ONLY_UPDATE = "migrate_to_manifest_only.update" - INLINE_CANDIDATE = "migration_to_inline_schemas.candidate" - INLINE_MIGRATION = "migration_to_inline_schemas.migration" - INLINE_CLEANUP = "migration_to_inline_schemas.cleanup" - LOAD_IMAGE_TO_LOCAL_DOCKER_HOST = "load_image_to_local_docker_host" - - def __str__(self) -> str: - return self.value diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/context.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/context.py deleted file mode 100644 index ecb901945486..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/context.py +++ /dev/null @@ -1,273 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""Module declaring context related classes.""" - -from __future__ import annotations - -from datetime import datetime -from pathlib import Path -from types import TracebackType -from typing import TYPE_CHECKING - -import yaml # type: ignore -from asyncer import asyncify -from dagger import Directory, Platform -from github import PullRequest - -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.consts import BUILD_PLATFORMS -from pipelines.dagger.actions import secrets -from pipelines.helpers.connectors.modifed import ConnectorWithModifiedFiles -from pipelines.helpers.execution.run_steps import RunStepOptions -from pipelines.helpers.github import update_commit_status_check -from pipelines.helpers.slack import send_message_to_webhook -from pipelines.helpers.utils import METADATA_FILE_NAME -from pipelines.models.contexts.pipeline_context import PipelineContext -from pipelines.models.secrets import LocalDirectorySecretStore, Secret, SecretStore - -if TYPE_CHECKING: - from pathlib import Path as NativePath - from typing import Dict, FrozenSet, List, Optional, Sequence - - -class ConnectorContext(PipelineContext): - """The connector context is used to store configuration for a specific connector pipeline run.""" - - DEFAULT_CONNECTOR_ACCEPTANCE_TEST_IMAGE = "airbyte/connector-acceptance-test:dev" - - def __init__( - self, - pipeline_name: str, - connector: ConnectorWithModifiedFiles, - is_local: bool, - git_branch: str, - git_revision: str, - diffed_branch: str, - git_repo_url: str, - report_output_prefix: str, - ci_report_bucket: Optional[str] = None, - ci_gcp_credentials: Optional[Secret] = None, - ci_git_user: Optional[str] = None, - ci_github_access_token: Optional[Secret] = None, - connector_acceptance_test_image: str = DEFAULT_CONNECTOR_ACCEPTANCE_TEST_IMAGE, - gha_workflow_run_url: Optional[str] = None, - dagger_logs_url: Optional[str] = None, - pipeline_start_timestamp: Optional[int] = None, - ci_context: Optional[str] = None, - slack_webhook: Optional[str] = None, - pull_request: Optional[PullRequest.PullRequest] = None, - should_save_report: bool = True, - code_tests_only: bool = False, - use_local_cdk: bool = False, - use_cdk_ref: Optional[str] = None, - use_host_gradle_dist_tar: bool = False, - enable_report_auto_open: bool = True, - docker_hub_username: Optional[Secret] = None, - docker_hub_password: Optional[Secret] = None, - s3_build_cache_access_key_id: Optional[Secret] = None, - s3_build_cache_secret_key: Optional[Secret] = None, - genai_api_key: Optional[Secret] = None, - dbdocs_token: Optional[Secret] = None, - concurrent_cat: Optional[bool] = False, - run_step_options: RunStepOptions = RunStepOptions(), - targeted_platforms: Sequence[Platform] = BUILD_PLATFORMS, - secret_stores: Dict[str, SecretStore] | None = None, - ) -> None: - """Initialize a connector context. - - Args: - connector (Connector): The connector under test. - is_local (bool): Whether the context is for a local run or a CI run. - git_branch (str): The current git branch name. - git_revision (str): The current git revision, commit hash. - diffed_branch: str: The branch to compare the current branch against. - git_repo_url: str: The URL of the git repository. - report_output_prefix (str): The S3 key to upload the test report to. - connector_acceptance_test_image (Optional[str], optional): The image to use to run connector acceptance tests. Defaults to DEFAULT_CONNECTOR_ACCEPTANCE_TEST_IMAGE. - gha_workflow_run_url (Optional[str], optional): URL to the github action workflow run. Only valid for CI run. Defaults to None. - dagger_logs_url (Optional[str], optional): URL to the dagger logs. Only valid for CI run. Defaults to None. - pipeline_start_timestamp (Optional[int], optional): Timestamp at which the pipeline started. Defaults to None. - ci_context (Optional[str], optional): Pull requests, workflow dispatch or nightly build. Defaults to None. - slack_webhook (Optional[str], optional): The slack webhook to send messages to. Defaults to None. - pull_request (PullRequest, optional): The pull request object if the pipeline was triggered by a pull request. Defaults to None. - code_tests_only (bool, optional): Whether to ignore non-code tests like QA and metadata checks. Defaults to False. - use_host_gradle_dist_tar (bool, optional): Used when developing java connectors with gradle. Defaults to False. - enable_report_auto_open (bool, optional): Open HTML report in browser window. Defaults to True. - docker_hub_username (Optional[Secret], optional): Docker Hub username to use to read registries. Defaults to None. - docker_hub_password (Optional[Secret], optional): Docker Hub password to use to read registries. Defaults to None. - s3_build_cache_access_key_id (Optional[Secret], optional): Gradle S3 Build Cache credentials. Defaults to None. - s3_build_cache_secret_key (Optional[Secret], optional): Gradle S3 Build Cache credentials. Defaults to None. - concurrent_cat (bool, optional): Whether to run the CAT tests in parallel. Defaults to False. - targeted_platforms (Optional[Iterable[Platform]], optional): The platforms to build the connector image for. Defaults to BUILD_PLATFORMS. - """ - - self.pipeline_name = pipeline_name - self.connector = connector - self.connector_acceptance_test_image = connector_acceptance_test_image - self._secrets_dir: Optional[Directory] = None - self._updated_secrets_dir: Optional[Directory] = None - self.cdk_version: Optional[str] = None - self.should_save_report = should_save_report - self.code_tests_only = code_tests_only - self.use_local_cdk = use_local_cdk - self.use_cdk_ref = use_cdk_ref - self.use_host_gradle_dist_tar = use_host_gradle_dist_tar - self.enable_report_auto_open = enable_report_auto_open - self.docker_hub_username = docker_hub_username - self.docker_hub_password = docker_hub_password - self.s3_build_cache_access_key_id = s3_build_cache_access_key_id - self.s3_build_cache_secret_key = s3_build_cache_secret_key - self.genai_api_key = genai_api_key - self.dbdocs_token = dbdocs_token - self.concurrent_cat = concurrent_cat - self.targeted_platforms = targeted_platforms - super().__init__( - pipeline_name=pipeline_name, - is_local=is_local, - git_branch=git_branch, - git_revision=git_revision, - diffed_branch=diffed_branch, - git_repo_url=git_repo_url, - report_output_prefix=report_output_prefix, - gha_workflow_run_url=gha_workflow_run_url, - dagger_logs_url=dagger_logs_url, - pipeline_start_timestamp=pipeline_start_timestamp, - ci_context=ci_context, - slack_webhook=slack_webhook, - pull_request=pull_request, - ci_report_bucket=ci_report_bucket, - ci_gcp_credentials=ci_gcp_credentials, - ci_git_user=ci_git_user, - ci_github_access_token=ci_github_access_token, - run_step_options=run_step_options, - enable_report_auto_open=enable_report_auto_open, - secret_stores=secret_stores, - ) - - @property - def modified_files(self) -> FrozenSet[NativePath]: - return self.connector.modified_files - - @property - def secrets_dir(self) -> Optional[Directory]: - return self._secrets_dir - - @secrets_dir.setter - def secrets_dir(self, secrets_dir: Directory) -> None: - self._secrets_dir = secrets_dir - - @property - def updated_secrets_dir(self) -> Optional[Directory]: - return self._updated_secrets_dir - - @updated_secrets_dir.setter - def updated_secrets_dir(self, updated_secrets_dir: Directory) -> None: - self._updated_secrets_dir = updated_secrets_dir - - @property - def connector_acceptance_test_source_dir(self) -> Directory: - return self.get_repo_dir("airbyte-integrations/bases/connector-acceptance-test") - - @property - def live_tests_dir(self) -> Directory: - return self.get_repo_dir("airbyte-ci/connectors/live-tests") - - @property - def erd_package_dir(self) -> Directory: - return self.get_repo_dir("airbyte-ci/connectors/erd") - - @property - def should_save_updated_secrets(self) -> bool: - return self.ci_gcp_credentials is not None and self.updated_secrets_dir is not None - - @property - def host_image_export_dir_path(self) -> str: - return "." if self.is_ci else "/tmp" - - @property - def metadata_path(self) -> Path: - return self.connector.code_directory / METADATA_FILE_NAME - - @property - def metadata(self) -> dict: - return yaml.safe_load(self.metadata_path.read_text())["data"] - - @property - def docker_repository(self) -> str: - return self.metadata["dockerRepository"] - - @property - def docker_image_tag(self) -> str: - return self.metadata["dockerImageTag"] - - @property - def docker_image(self) -> str: - return f"{self.docker_repository}:{self.docker_image_tag}" - - @property - def local_secret_store_name(self) -> str: - return f"{self.connector.technical_name}-local" - - @property - def local_secret_store(self) -> Optional[LocalDirectorySecretStore]: - connector_secrets_path = self.connector.code_directory / "secrets" - if connector_secrets_path.is_dir(): - return LocalDirectorySecretStore(connector_secrets_path) - return None - - async def get_connector_dir(self, exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Directory: - """Get the connector under test source code directory. - - Args: - exclude ([List[str], optional): List of files or directories to exclude from the directory. Defaults to None. - include ([List[str], optional): List of files or directories to include in the directory. Defaults to None. - - Returns: - Directory: The connector under test source code directory. - """ - vanilla_connector_dir = self.get_repo_dir(str(self.connector.code_directory), exclude=exclude, include=include) - return await vanilla_connector_dir.with_timestamps(1) - - async def __aexit__( - self, exception_type: Optional[type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType] - ) -> bool: - """Perform teardown operation for the ConnectorContext. - - On the context exit the following operations will happen: - - Upload updated connector secrets back to Google Secret Manager - - Write a test report in JSON format locally and to S3 if running in a CI environment - - Update the commit status check on GitHub if running in a CI environment. - It should gracefully handle the execution error that happens and always upload a test report and update commit status check. - Args: - exception_type (Optional[type[BaseException]]): The exception type if an exception was raised in the context execution, None otherwise. - exception_value (Optional[BaseException]): The exception value if an exception was raised in the context execution, None otherwise. - traceback (Optional[TracebackType]): The traceback if an exception was raised in the context execution, None otherwise. - Returns: - bool: Whether the teardown operation ran successfully. - """ - self.stopped_at = datetime.utcnow() - self.state = self.determine_final_state(self.report, exception_value) - if exception_value: - self.logger.error("An error got handled by the ConnectorContext", exc_info=True) - if self.report is None: - self.logger.error("No test report was provided. This is probably due to an upstream error") - self.report = ConnectorReport(self, []) - - if self.should_save_updated_secrets: - await secrets.upload(self) - - self.report.print() - - if self.should_save_report: - await self.report.save() - - await asyncify(update_commit_status_check)(**self.github_commit_status) - - if self.should_send_slack_message: - # Using a type ignore here because the should_send_slack_message property is checking for non nullity of the slack_webhook - await asyncify(send_message_to_webhook)(self.create_slack_message(), self.get_slack_channels(), self.slack_webhook) # type: ignore - - # Supress the exception if any - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/__init__.py deleted file mode 100644 index 7f66676b8716..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/commands.py deleted file mode 100644 index 6203febb51b9..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/commands.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from typing import List - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.generate_erd.pipeline import run_connector_generate_erd_pipeline -from pipelines.cli.click_decorators import click_ignore_unused_kwargs, click_merge_args_into_context_obj -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.cli.secrets import wrap_in_secret -from pipelines.helpers.connectors.command import run_connector_pipeline - - -@click.command( - cls=DaggerPipelineCommand, - short_help="Generate ERD", -) -@click.option( - "--dbdocs-token", - help="The token to use with dbdocs CLI.", - type=click.STRING, - required=False, - envvar="DBDOCS_TOKEN", - callback=wrap_in_secret, -) -@click.option( - "--genai-api-key", - help="The API key to interact with GENAI.", - type=click.STRING, - required=False, - envvar="GENAI_API_KEY", - callback=wrap_in_secret, -) -@click.option( - "--skip-step", - "-x", - "skip_steps", - multiple=True, - type=click.Choice([step_id.value for step_id in [CONNECTOR_TEST_STEP_ID.LLM_RELATIONSHIPS, CONNECTOR_TEST_STEP_ID.PUBLISH_ERD]]), - help="Skip a step by name. Can be used multiple times to skip multiple steps.", -) -@click_merge_args_into_context_obj -@click.pass_context -@click_ignore_unused_kwargs -async def generate_erd(ctx: click.Context, skip_steps: List[str]) -> bool: - """ - dbdocs_token and genai_api_key and unused because they are set as part of the context in the helpers - """ - return await run_connector_pipeline( - ctx, - "Generate ERD schema", - False, - run_connector_generate_erd_pipeline, - skip_steps, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/pipeline.py deleted file mode 100644 index 3c066307586b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/generate_erd/pipeline.py +++ /dev/null @@ -1,169 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -from pathlib import Path -from typing import TYPE_CHECKING, List - -from dagger import Container, Directory - -from pipelines.airbyte_ci.connectors.build_image.steps.python_connectors import BuildConnectorImages -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.airbyte_ci.connectors.reports import Report -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.dagger.actions.python.poetry import with_poetry -from pipelines.helpers.connectors.command import run_connector_steps -from pipelines.helpers.execution.run_steps import STEP_TREE, StepToRun -from pipelines.models.secrets import Secret -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from anyio import Semaphore - - -def _get_erd_folder(code_directory: Path) -> Path: - path = code_directory / "erd" - if not path.exists(): - path.mkdir() - return path - - -class GenerateDbml(Step): - context: ConnectorContext - - title = "Generate DBML file" - - def __init__(self, context: PipelineContext, skip_relationship_generation: bool) -> None: - super().__init__(context) - self._skip_relationship_generation = skip_relationship_generation - - async def _run(self, connector_to_discover: Container) -> StepResult: - if not self._skip_relationship_generation and not self.context.genai_api_key: - raise ValueError("GENAI_API_KEY needs to be provided if the relationship generation is not skipped") - - discovered_catalog = await self._get_discovered_catalog(connector_to_discover) - - connector_directory = await self.context.get_connector_dir() - command = ["poetry", "run", "erd", "--source-path", "/source", "--source-technical-name", self.context.connector.technical_name] - if self._skip_relationship_generation: - command.append("--skip-llm-relationships") - - erd_directory = self._build_erd_container(connector_directory, discovered_catalog).with_exec(command).directory("/source/erd") - await erd_directory.export(str(_get_erd_folder(self.context.connector.code_directory))) - - return StepResult(step=self, status=StepStatus.SUCCESS, output=erd_directory) - - async def _get_discovered_catalog(self, connector_to_discover: Container) -> str: - source_config_path_in_container = "/data/config.json" - discover_output = ( - await connector_to_discover.with_new_file(source_config_path_in_container, contents=self._get_default_config().value) - .with_exec(["discover", "--config", source_config_path_in_container], use_entrypoint=True) - .stdout() - ) - return self._get_schema_from_discover_output(discover_output) - - def _get_default_config(self) -> Secret: - if not self.context.local_secret_store: - raise ValueError("Expecting local secret store to be set up but couldn't find it") - - filtered_secrets = [secret for secret in self.context.local_secret_store.get_all_secrets() if secret.file_name == "config.json"] - if not filtered_secrets: - raise ValueError("Expecting at least one secret to match name `config.json`") - elif len(filtered_secrets) > 1: - self.logger.warning( - f"Expecting only one secret with name `config.json but got {len(filtered_secrets)}. Will take the first one in the list." - ) - - return filtered_secrets[0] - - @staticmethod - def _get_schema_from_discover_output(discover_output: str) -> str: - for line in discover_output.split("\n"): - json_line = json.loads(line) - if json_line.get("type") == "CATALOG": - return json.dumps(json.loads(line).get("catalog")) - raise ValueError("No catalog was found in output") - - def _build_erd_container(self, connector_directory: Directory, discovered_catalog: str) -> Container: - """Create a container to run ERD generation.""" - container = with_poetry(self.context) - if self.context.genai_api_key: - container = container.with_secret_variable("GENAI_API_KEY", self.context.genai_api_key.as_dagger_secret(self.dagger_client)) - - container = ( - container.with_mounted_directory("/source", connector_directory) - .with_new_file("/source/erd/discovered_catalog.json", contents=discovered_catalog) - .with_mounted_directory("/app", self.context.erd_package_dir) - .with_workdir("/app") - ) - - return container.with_exec(["poetry", "lock"], use_entrypoint=True).with_exec(["poetry", "install"], use_entrypoint=True) - - -class UploadDbmlSchema(Step): - context: ConnectorContext - - title = "Upload DBML file to dbdocs.io" - - def __init__(self, context: PipelineContext) -> None: - super().__init__(context) - - async def _run(self, erd_directory: Directory) -> StepResult: - if not self.context.dbdocs_token: - raise ValueError( - "In order to publish to dbdocs, DBDOCS_TOKEN needs to be provided. Either pass the value or skip the publish step" - ) - - dbdocs_container = ( - self.dagger_client.container() - .from_("node:lts-bullseye-slim") - .with_exec(["npm", "install", "-g", "dbdocs"], use_entrypoint=True) - .with_env_variable("DBDOCS_TOKEN", self.context.dbdocs_token.value) - .with_workdir("/airbyte_dbdocs") - .with_file("/airbyte_dbdocs/dbdocs.dbml", erd_directory.file("source.dbml")) - ) - - db_docs_build = ["dbdocs", "build", "dbdocs.dbml", f"--project={self.context.connector.technical_name}"] - await dbdocs_container.with_exec(db_docs_build).stdout() - # TODO: produce link to dbdocs in output logs - - return StepResult(step=self, status=StepStatus.SUCCESS) - - -async def run_connector_generate_erd_pipeline( - context: ConnectorContext, - semaphore: "Semaphore", - skip_steps: List[str], -) -> Report: - context.targeted_platforms = [LOCAL_BUILD_PLATFORM] - steps_to_run: STEP_TREE = [] - - steps_to_run.append([StepToRun(id=CONNECTOR_TEST_STEP_ID.BUILD, step=BuildConnectorImages(context))]) - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.DBML_FILE, - step=GenerateDbml(context, CONNECTOR_TEST_STEP_ID.LLM_RELATIONSHIPS in skip_steps), - args=lambda results: {"connector_to_discover": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ] - ) - - if CONNECTOR_TEST_STEP_ID.PUBLISH_ERD not in skip_steps: - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.PUBLISH_ERD, - step=UploadDbmlSchema(context), - args=lambda results: {"erd_directory": results[CONNECTOR_TEST_STEP_ID.DBML_FILE].output}, - depends_on=[CONNECTOR_TEST_STEP_ID.DBML_FILE], - ), - ] - ) - - return await run_connector_steps(context, semaphore, steps_to_run) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/commands.py deleted file mode 100644 index dea261788ba5..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/list/commands.py +++ /dev/null @@ -1,56 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json -from pathlib import Path - -import asyncclick as click -from connector_ops.utils import console # type: ignore -from rich.table import Table -from rich.text import Text - -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand - - -@click.command(cls=DaggerPipelineCommand, help="List all selected connectors.", name="list") -@click.option( - "-o", - "--output", - "output_path", - type=click.Path(dir_okay=False, writable=True, path_type=Path), - help="Path where the JSON output will be saved.", -) -@click.pass_context -async def list_connectors( - ctx: click.Context, - output_path: Path, -) -> bool: - selected_connectors = sorted(ctx.obj["selected_connectors_with_modified_files"], key=lambda x: x.technical_name) - table = Table(title=f"{len(selected_connectors)} selected connectors") - table.add_column("Modified") - table.add_column("Connector") - table.add_column("Language") - table.add_column("Release stage") - table.add_column("Version") - table.add_column("Folder") - - for connector in selected_connectors: - modified = "X" if connector.modified_files else "" - connector_name = Text(connector.technical_name) - language: Text = Text(connector.language.value) if connector.language else Text("N/A") - try: - support_level: Text = Text(connector.support_level) - except Exception: - support_level = Text("N/A") - try: - version: Text = Text(connector.version) - except Exception: - version = Text("N/A") - folder = Text(str(connector.code_directory)) - table.add_row(modified, connector_name, language, support_level, version, folder) - if output_path: - with open(output_path, "w") as f: - json.dump([connector.technical_name for connector in selected_connectors], f) - console.print(table) - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/commands.py deleted file mode 100644 index da73b3ebc022..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/commands.py +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.migrate_to_inline_schemas.pipeline import run_connector_migrate_to_inline_schemas_pipeline -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.helpers.connectors.command import run_connector_pipeline -from pipelines.helpers.connectors.format import verify_formatters - - -@click.command( - cls=DaggerPipelineCommand, - short_help="Where possible (have a metadata.yaml), move stream schemas to inline schemas.", -) -@click.option( - "--report", - is_flag=True, - type=bool, - default=False, - help="Auto open report browser.", -) -@click.pass_context -async def migrate_to_inline_schemas(ctx: click.Context, report: bool) -> bool: - verify_formatters() - return await run_connector_pipeline( - ctx, - "Migrate to inline schemas", - report, - run_connector_migrate_to_inline_schemas_pipeline, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/pipeline.py deleted file mode 100644 index ba880ae73840..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_inline_schemas/pipeline.py +++ /dev/null @@ -1,437 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -import os -import shutil -import tempfile -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING, Any, List - -from connector_ops.utils import ConnectorLanguage # type: ignore - -from pipelines import main_logger -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.airbyte_ci.connectors.reports import Report -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.helpers.connectors.command import run_connector_steps -from pipelines.helpers.connectors.yaml import read_yaml, write_yaml -from pipelines.helpers.execution.run_steps import STEP_TREE, StepToRun -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from anyio import Semaphore - -SCHEMAS_DIR_NAME = "schemas" - - -class CheckIsInlineCandidate(Step): - """Check if the connector is a candidate to get inline schemas. - Candidate conditions: - - The connector is a Python connector. - - The connector is a source connector. - - The connector has a manifest file. - - The connector has schemas directory. - """ - - context: ConnectorContext - - title = "Check if the connector is a candidate for inline schema migration." - - def __init__(self, context: PipelineContext) -> None: - super().__init__(context) - - async def _run(self) -> StepResult: - connector = self.context.connector - manifest_path = connector.manifest_path - python_path = connector.python_source_dir_path - if connector.language not in [ - ConnectorLanguage.PYTHON, - ConnectorLanguage.LOW_CODE, - ConnectorLanguage.MANIFEST_ONLY, - ]: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector is not a Python connector.", - ) - if connector.connector_type != "source": - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector is not a source connector.", - ) - - if not manifest_path.is_file(): - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector does not have a manifest file.", - ) - - schemas_dir = python_path / SCHEMAS_DIR_NAME - if not schemas_dir.is_dir(): - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector does not have a schemas directory.", - ) - - # TODO: does this help or not? - # if _has_subdirectory(schemas_dir): - # return StepResult(step=self, status=StepStatus.SKIPPED, stderr="This has subdirectories. It's probably complicated.") - - return StepResult( - step=self, - status=StepStatus.SUCCESS, - ) - - -class RestoreInlineState(Step): - context: ConnectorContext - - title = "Restore original state" - - def __init__(self, context: ConnectorContext) -> None: - super().__init__(context) - self.manifest_path = context.connector.manifest_path - self.original_manifest = None - if self.manifest_path.is_file(): - self.original_manifest = self.manifest_path.read_text() - - self.schemas_path = context.connector.python_source_dir_path / SCHEMAS_DIR_NAME - self.backup_schema_path = None - if self.schemas_path.is_dir(): - self.backup_schema_path = Path(tempfile.mkdtemp()) - copy_directory(self.schemas_path, self.backup_schema_path) - - async def _run(self) -> StepResult: - if self.original_manifest: - self.manifest_path.write_text(self.original_manifest) - - if self.backup_schema_path: - copy_directory(self.backup_schema_path, self.schemas_path) - - return StepResult( - step=self, - status=StepStatus.SUCCESS, - ) - - async def _cleanup(self) -> StepResult: - if self.backup_schema_path: - shutil.rmtree(self.backup_schema_path) - return StepResult( - step=self, - status=StepStatus.SUCCESS, - ) - - -class InlineSchemas(Step): - context: ConnectorContext - - title = "Migrate connector to inline schemas." - - def __init__(self, context: PipelineContext) -> None: - super().__init__(context) - - async def _run(self) -> StepResult: - connector = self.context.connector - connector_path = connector.code_directory - manifest_path = connector.manifest_path - python_path = connector.python_source_dir_path - logger = self.logger - - json_streams = _parse_json_streams(python_path) - if len(json_streams) == 0: - return StepResult(step=self, status=StepStatus.SKIPPED, stderr="No JSON streams found.") - - data = read_yaml(manifest_path) - if "streams" not in data: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="No manifest streams found.", - ) - - # find the explit ones and remove or udpate - json_loaders = _find_json_loaders(data, []) - for loader in json_loaders: - logger.info(f" JSON loader ref: {loader.ref} -> {loader.file_path}") - - _update_json_loaders(connector_path, data, json_streams, json_loaders) - - # go through the declared streams and update the inline schemas - for stream in data["streams"]: - if isinstance(stream, str): - # see if reference - if stream.startswith("#"): - yaml_stream = _load_reference(data, stream) - if not yaml_stream: - logger.info(f" Stream reference not found: {stream}") - continue - if not _get_stream_name(yaml_stream): - logger.info(f" Stream reference name not found: {stream}") - continue - else: - logger.info(f" Stream reference unknown: {stream}") - continue - else: - yaml_stream = stream - - if not yaml_stream: - logger.info(f" !! Yaml stream not found: {stream}") - continue - - stream_name = _get_stream_name(yaml_stream) - if not stream_name: - logger.info(f" !! Stream name not found: {stream}") - continue - if yaml_stream.get("schema_loader") and yaml_stream["schema_loader"].get("type") == "InlineSchemaLoader": - continue - - yaml_stream["schema_loader"] = {} - schema_loader = yaml_stream["schema_loader"] - _update_inline_schema(schema_loader, json_streams, stream_name) - - write_yaml(data, manifest_path) - # await format_prettier([manifest_path], logger=logger) - - for json_stream in json_streams.values(): - logger.info(f" !! JSON schema not found: {json_stream.name}") - - return StepResult(step=self, status=StepStatus.SUCCESS) - - -class RemoveUnusedJsonSchamas(Step): - context: ConnectorContext - - title = "Cleanup json schemas that are dangling but unused." - - async def _run(self) -> StepResult: - connector = self.context.connector - python_path = connector.python_source_dir_path - schemas_path = python_path / SCHEMAS_DIR_NAME - logger = self.logger - - manifest = connector.manifest_path.read_text() - - if manifest.find("JsonFileSchemaLoader") != -1: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="Skipping: the manifest is still using JSON Schema loader.", - ) - - if schemas_path.exists(): - logger.info(f" Removing schemnas dir: {schemas_path}") - shutil.rmtree(schemas_path) - - return StepResult(step=self, status=StepStatus.SUCCESS) - - -@dataclass -class JsonStream: - name: str - schema: dict - file_path: Path - - -@dataclass -class JsonLoaderNode: - ref: str - file_path: str - - -def copy_directory(src: Path, dest: Path) -> None: - if dest.exists(): - shutil.rmtree(dest) - shutil.copytree(src, dest) - - -def _has_subdirectory(directory: Path) -> bool: - # Iterate through all items in the directory - for entry in directory.iterdir(): - # Check if this entry is a directory - if entry.is_dir(): - return True - - return False - - -def _get_stream_name(yaml_stream: dict) -> str | None: - if "name" in yaml_stream: - return yaml_stream["name"] - if "$parameters" in yaml_stream and "name" in yaml_stream["$parameters"]: - return yaml_stream["$parameters"]["name"] - return None - - -def _update_json_loaders( - connector_path: Path, - data: dict, - streams: dict[str, JsonStream], - loaders: List[JsonLoaderNode], -) -> None: - logger = main_logger - for loader in loaders: - if "{{" in loader.file_path: - # remove templated paths and their references - (f" Removing reference: {loader.ref}") - _remove_reference(data, None, loader, []) - continue - else: - # direct pointer to a file. update. - file_path = Path(os.path.abspath(os.path.join(connector_path, loader.file_path))) - if not file_path.is_file(): - logger.info(f" JsonFileSchemaLoader not found: {file_path}") - continue - schema_loader = _load_reference(data, loader.ref) - if not schema_loader: - logger.info(f" JsonFileSchemaLoader reference not found: {loader.ref}") - continue - _update_inline_schema(schema_loader, streams, file_path.stem) - - -def _update_inline_schema(schema_loader: dict, json_streams: dict[str, JsonStream], file_name: str) -> None: - logger = main_logger - if file_name not in json_streams: - logger.info(f" Stream {file_name} not found in JSON schemas.") - return - - json_stream = json_streams[file_name] - schema_loader["type"] = "InlineSchemaLoader" - schema_loader["schema"] = json_stream.schema - - json_stream.file_path.unlink() - json_streams.pop(file_name) - - -def _remove_reference(parent: Any, key: str | int | None, loader: JsonLoaderNode, path: List[str]) -> bool: # noqa: ANN401 - logger = main_logger - if key is None: - data = parent - else: - data = parent[key] - - if isinstance(data, dict): - ref = f"#/{'/'.join(path)}" - if ref == loader.ref: - logger.info(f" Removing reference: {ref}") - return True - elif "$ref" in data and data["$ref"] == loader.ref: - logger.info(f" Found reference: {ref}") - return True - else: - todelete = [] - for key, value in data.items(): - if _remove_reference(data, key, loader, path + [str(key)]): - todelete.append(key) - for key in todelete: - del data[key] - elif isinstance(data, list): - for i, value in enumerate(data): - ref = f"Array[{str(i)}]" - _remove_reference(data, i, loader, path + [ref]) - - return False - - -def _load_reference(data: dict, ref: str) -> dict | None: - yaml_stream = data - path = ref.split("/") - for p in path: - if p == "#": - continue - if p.startswith("Array["): - i = int(p[6:-1]) - if not isinstance(yaml_stream, list) or len(yaml_stream) <= i: - return None - yaml_stream = yaml_stream[i] - continue - if p not in yaml_stream: - return None - yaml_stream = yaml_stream[p] - return yaml_stream - - -def _find_json_loaders(data: Any, path: List[str]) -> List[JsonLoaderNode]: # noqa: ANN401 - logger = main_logger - loaders: List[JsonLoaderNode] = [] - if isinstance(data, dict): - if "type" in data and data["type"] == "JsonFileSchemaLoader": - ref = f"#/{'/'.join(path)}" - if "file_path" in data: - loaders.append(JsonLoaderNode(ref, data["file_path"])) - else: - logger.info(f" !! JsonFileSchemaLoader missing file_path: {ref}") - else: - for key, value in data.items(): - loaders += _find_json_loaders(value, path + [key]) - elif isinstance(data, list): - for i, value in enumerate(data): - loaders += _find_json_loaders(value, path + [f"Array[{str(i)}]"]) - return loaders - - -def _parse_json_streams(python_path: Path) -> dict[str, JsonStream]: - streams: dict[str, JsonStream] = {} - schemas_path = python_path / SCHEMAS_DIR_NAME - if not schemas_path.is_dir(): - return streams - - for schema_file in schemas_path.iterdir(): - if schema_file.is_file() and schema_file.suffix == ".json": - stream_name = schema_file.stem - with schema_file.open("r") as file: - # read json - schema = json.load(file) - streams[stream_name] = JsonStream( - name=stream_name, - schema=schema, - file_path=schema_file, - ) - - return streams - - -async def run_connector_migrate_to_inline_schemas_pipeline(context: ConnectorContext, semaphore: "Semaphore") -> Report: - restore_original_state = RestoreInlineState(context) - - context.targeted_platforms = [LOCAL_BUILD_PLATFORM] - - steps_to_run: STEP_TREE = [] - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INLINE_CANDIDATE, - step=CheckIsInlineCandidate(context), - ) - ] - ) - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INLINE_MIGRATION, - step=InlineSchemas(context), - depends_on=[CONNECTOR_TEST_STEP_ID.INLINE_CANDIDATE], - ) - ] - ) - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INLINE_CLEANUP, - step=RemoveUnusedJsonSchamas(context), - depends_on=[CONNECTOR_TEST_STEP_ID.INLINE_MIGRATION], - ) - ] - ) - - return await run_connector_steps(context, semaphore, steps_to_run, restore_original_state=restore_original_state) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/commands.py deleted file mode 100644 index f7440de7479a..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/commands.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -# - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.migrate_to_manifest_only.pipeline import run_connectors_manifest_only_pipeline -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand - - -@click.command(cls=DaggerPipelineCommand, short_help="Migrate a low-code connector to manifest-only") -@click.pass_context -async def migrate_to_manifest_only(ctx: click.Context) -> bool: - connectors_contexts = [ - ConnectorContext( - pipeline_name=f"Migrate connector {connector.technical_name} to manifest-only", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - report_output_prefix=ctx.obj["report_output_prefix"], - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - - await run_connectors_pipelines( - connectors_contexts, - run_connectors_manifest_only_pipeline, - "Migrate connector to manifest-only pipeline", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - ctx.obj["git_branch"], - ctx.obj["git_revision"], - ctx.obj["diffed_branch"], - ctx.obj["is_local"], - ctx.obj["ci_context"], - ctx.obj["git_repo_url"], - ) - - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/declarative_component_schema.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/declarative_component_schema.py deleted file mode 100644 index c58c68dc0968..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/declarative_component_schema.py +++ /dev/null @@ -1,1598 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -# generated by datamodel-codegen: -# filename: declarative_component_schema.yaml - -from __future__ import annotations - -from enum import Enum -from typing import Any, Dict, List, Optional, Union - -from pydantic import BaseModel, Extra, Field -from typing_extensions import Literal - -## WARNING -## This is a hack. I've copied the current version of the models from the generated code and pasted them here since we can't access the CDK from the current directory. -## These are from CDK 4.5.3. -## These models won't update alongside the CDK, so this is a temporary solution to unblock manifest-only migrations. - - -class AuthFlowType(Enum): - oauth2_0 = "oauth2.0" - oauth1_0 = "oauth1.0" - - -class BasicHttpAuthenticator(BaseModel): - type: Literal["BasicHttpAuthenticator"] - username: str = Field( - ..., - description="The username that will be combined with the password, base64 encoded and used to make requests. Fill it in the user inputs.", - examples=["{{ config['username'] }}", "{{ config['api_key'] }}"], - title="Username", - ) - password: Optional[str] = Field( - "", - description="The password that will be combined with the username, base64 encoded and used to make requests. Fill it in the user inputs.", - examples=["{{ config['password'] }}", ""], - title="Password", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class BearerAuthenticator(BaseModel): - type: Literal["BearerAuthenticator"] - api_token: str = Field( - ..., - description="Token to inject as request header for authenticating with the API.", - examples=["{{ config['api_key'] }}", "{{ config['token'] }}"], - title="Bearer Token", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CheckStream(BaseModel): - type: Literal["CheckStream"] - stream_names: List[str] = Field( - ..., - description="Names of the streams to try reading from when running a check operation.", - examples=[["users"], ["users", "contacts"]], - title="Stream Names", - ) - - -class ConstantBackoffStrategy(BaseModel): - type: Literal["ConstantBackoffStrategy"] - backoff_time_in_seconds: Union[float, str] = Field( - ..., - description="Backoff time in seconds.", - examples=[30, 30.5, "{{ config['backoff_time'] }}"], - title="Backoff Time", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomAuthenticator(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomAuthenticator"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom authentication strategy. Has to be a sub class of DeclarativeAuthenticator. The format is `source_..`.", - examples=["source_railz.components.ShortLivedTokenAuthenticator"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomBackoffStrategy(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomBackoffStrategy"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom backoff strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomBackoffStrategy"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomErrorHandler(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomErrorHandler"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom error handler. The format is `source_..`.", - examples=["source_railz.components.MyCustomErrorHandler"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomIncrementalSync(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomIncrementalSync"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom incremental sync. The format is `source_..`.", - examples=["source_railz.components.MyCustomIncrementalSync"], - title="Class Name", - ) - cursor_field: str = Field( - ..., - description="The location of the value on a record that will be used as a bookmark during sync.", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomPaginationStrategy(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomPaginationStrategy"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom pagination strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomPaginationStrategy"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomRecordExtractor(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomRecordExtractor"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom record extraction strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomRecordExtractor"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomRecordFilter(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomRecordFilter"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom record filter strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomCustomRecordFilter"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomRequester(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomRequester"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom requester strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomRecordExtractor"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomRetriever(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomRetriever"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom retriever strategy. The format is `source_..`.", - examples=["source_railz.components.MyCustomRetriever"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomPartitionRouter(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomPartitionRouter"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom partition router. The format is `source_..`.", - examples=["source_railz.components.MyCustomPartitionRouter"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomSchemaLoader(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomSchemaLoader"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom schema loader. The format is `source_..`.", - examples=["source_railz.components.MyCustomSchemaLoader"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomStateMigration(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomStateMigration"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom state migration. The format is `source_..`.", - examples=["source_railz.components.MyCustomStateMigration"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class CustomTransformation(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["CustomTransformation"] - class_name: str = Field( - ..., - description="Fully-qualified name of the class that will be implementing the custom transformation. The format is `source_..`.", - examples=["source_railz.components.MyCustomTransformation"], - title="Class Name", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class LegacyToPerPartitionStateMigration(BaseModel): - class Config: - extra = Extra.allow - - type: Optional[Literal["LegacyToPerPartitionStateMigration"]] = None - - -class Algorithm(Enum): - HS256 = "HS256" - HS384 = "HS384" - HS512 = "HS512" - ES256 = "ES256" - ES256K = "ES256K" - ES384 = "ES384" - ES512 = "ES512" - RS256 = "RS256" - RS384 = "RS384" - RS512 = "RS512" - PS256 = "PS256" - PS384 = "PS384" - PS512 = "PS512" - EdDSA = "EdDSA" - - -class JwtHeaders(BaseModel): - class Config: - extra = Extra.forbid - - kid: Optional[str] = Field( - None, - description="Private key ID for user account.", - examples=["{{ config['kid'] }}"], - title="Key Identifier", - ) - typ: Optional[str] = Field( - "JWT", - description="The media type of the complete JWT.", - examples=["JWT"], - title="Type", - ) - cty: Optional[str] = Field( - None, - description="Content type of JWT header.", - examples=["JWT"], - title="Content Type", - ) - - -class JwtPayload(BaseModel): - class Config: - extra = Extra.forbid - - iss: Optional[str] = Field( - None, - description="The user/principal that issued the JWT. Commonly a value unique to the user.", - examples=["{{ config['iss'] }}"], - title="Issuer", - ) - sub: Optional[str] = Field( - None, - description="The subject of the JWT. Commonly defined by the API.", - title="Subject", - ) - aud: Optional[str] = Field( - None, - description="The recipient that the JWT is intended for. Commonly defined by the API.", - examples=["appstoreconnect-v1"], - title="Audience", - ) - - -class JwtAuthenticator(BaseModel): - type: Literal["JwtAuthenticator"] - secret_key: str = Field( - ..., - description="Secret used to sign the JSON web token.", - examples=["{{ config['secret_key'] }}"], - ) - base64_encode_secret_key: Optional[bool] = Field( - False, - description='When set to true, the secret key will be base64 encoded prior to being encoded as part of the JWT. Only set to "true" when required by the API.', - ) - algorithm: Algorithm = Field( - ..., - description="Algorithm used to sign the JSON web token.", - examples=["ES256", "HS256", "RS256", "{{ config['algorithm'] }}"], - ) - token_duration: Optional[int] = Field( - 1200, - description="The amount of time in seconds a JWT token can be valid after being issued.", - examples=[1200, 3600], - title="Token Duration", - ) - header_prefix: Optional[str] = Field( - None, - description="The prefix to be used within the Authentication header.", - examples=["Bearer", "Basic"], - title="Header Prefix", - ) - jwt_headers: Optional[JwtHeaders] = Field( - None, - description="JWT headers used when signing JSON web token.", - title="JWT Headers", - ) - additional_jwt_headers: Optional[Dict[str, Any]] = Field( - None, - description="Additional headers to be included with the JWT headers object.", - title="Additional JWT Headers", - ) - jwt_payload: Optional[JwtPayload] = Field( - None, - description="JWT Payload used when signing JSON web token.", - title="JWT Payload", - ) - additional_jwt_payload: Optional[Dict[str, Any]] = Field( - None, - description="Additional properties to be added to the JWT payload.", - title="Additional JWT Payload Properties", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class RefreshTokenUpdater(BaseModel): - refresh_token_name: Optional[str] = Field( - "refresh_token", - description="The name of the property which contains the updated refresh token in the response from the token refresh endpoint.", - examples=["refresh_token"], - title="Refresh Token Property Name", - ) - access_token_config_path: Optional[List[str]] = Field( - ["credentials", "access_token"], - description="Config path to the access token. Make sure the field actually exists in the config.", - examples=[["credentials", "access_token"], ["access_token"]], - title="Config Path To Access Token", - ) - refresh_token_config_path: Optional[List[str]] = Field( - ["credentials", "refresh_token"], - description="Config path to the access token. Make sure the field actually exists in the config.", - examples=[["credentials", "refresh_token"], ["refresh_token"]], - title="Config Path To Refresh Token", - ) - token_expiry_date_config_path: Optional[List[str]] = Field( - ["credentials", "token_expiry_date"], - description="Config path to the expiry date. Make sure actually exists in the config.", - examples=[["credentials", "token_expiry_date"]], - title="Config Path To Expiry Date", - ) - refresh_token_error_status_codes: Optional[List[int]] = Field( - [], - description="Status Codes to Identify refresh token error in response (Refresh Token Error Key and Refresh Token Error Values should be also specified). Responses with one of the error status code and containing an error value will be flagged as a config error", - examples=[[400, 500]], - title="Refresh Token Error Status Codes", - ) - refresh_token_error_key: Optional[str] = Field( - "", - description="Key to Identify refresh token error in response (Refresh Token Error Status Codes and Refresh Token Error Values should be also specified).", - examples=["error"], - title="Refresh Token Error Key", - ) - refresh_token_error_values: Optional[List[str]] = Field( - [], - description='List of values to check for exception during token refresh process. Used to check if the error found in the response matches the key from the Refresh Token Error Key field (e.g. response={"error": "invalid_grant"}). Only responses with one of the error status code and containing an error value will be flagged as a config error', - examples=[["invalid_grant", "invalid_permissions"]], - title="Refresh Token Error Values", - ) - - -class OAuthAuthenticator(BaseModel): - type: Literal["OAuthAuthenticator"] - client_id: str = Field( - ..., - description="The OAuth client ID. Fill it in the user inputs.", - examples=["{{ config['client_id }}", "{{ config['credentials']['client_id }}"], - title="Client ID", - ) - client_secret: str = Field( - ..., - description="The OAuth client secret. Fill it in the user inputs.", - examples=[ - "{{ config['client_secret }}", - "{{ config['credentials']['client_secret }}", - ], - title="Client Secret", - ) - refresh_token: Optional[str] = Field( - None, - description="Credential artifact used to get a new access token.", - examples=[ - "{{ config['refresh_token'] }}", - "{{ config['credentials]['refresh_token'] }}", - ], - title="Refresh Token", - ) - token_refresh_endpoint: str = Field( - ..., - description="The full URL to call to obtain a new access token.", - examples=["https://connect.squareup.com/oauth2/token"], - title="Token Refresh Endpoint", - ) - access_token_name: Optional[str] = Field( - "access_token", - description="The name of the property which contains the access token in the response from the token refresh endpoint.", - examples=["access_token"], - title="Access Token Property Name", - ) - expires_in_name: Optional[str] = Field( - "expires_in", - description="The name of the property which contains the expiry date in the response from the token refresh endpoint.", - examples=["expires_in"], - title="Token Expiry Property Name", - ) - grant_type: Optional[str] = Field( - "refresh_token", - description="Specifies the OAuth2 grant type. If set to refresh_token, the refresh_token needs to be provided as well. For client_credentials, only client id and secret are required. Other grant types are not officially supported.", - examples=["refresh_token", "client_credentials"], - title="Grant Type", - ) - refresh_request_body: Optional[Dict[str, Any]] = Field( - None, - description="Body of the request sent to get a new access token.", - examples=[ - { - "applicationId": "{{ config['application_id'] }}", - "applicationSecret": "{{ config['application_secret'] }}", - "token": "{{ config['token'] }}", - } - ], - title="Refresh Request Body", - ) - scopes: Optional[List[str]] = Field( - None, - description="List of scopes that should be granted to the access token.", - examples=[["crm.list.read", "crm.objects.contacts.read", "crm.schema.contacts.read"]], - title="Scopes", - ) - token_expiry_date: Optional[str] = Field( - None, - description="The access token expiry date.", - examples=["2023-04-06T07:12:10.421833+00:00", 1680842386], - title="Token Expiry Date", - ) - token_expiry_date_format: Optional[str] = Field( - None, - description="The format of the time to expiration datetime. Provide it if the time is returned as a date-time string instead of seconds.", - examples=["%Y-%m-%d %H:%M:%S.%f+00:00"], - title="Token Expiry Date Format", - ) - refresh_token_updater: Optional[RefreshTokenUpdater] = Field( - None, - description="When the token updater is defined, new refresh tokens, access tokens and the access token expiry date are written back from the authentication response to the config object. This is important if the refresh token can only used once.", - title="Token Updater", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class ExponentialBackoffStrategy(BaseModel): - type: Literal["ExponentialBackoffStrategy"] - factor: Optional[Union[float, str]] = Field( - 5, - description="Multiplicative constant applied on each retry.", - examples=[5, 5.5, "10"], - title="Factor", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SessionTokenRequestBearerAuthenticator(BaseModel): - type: Literal["Bearer"] - - -class HttpMethod(Enum): - GET = "GET" - POST = "POST" - - -class Action(Enum): - SUCCESS = "SUCCESS" - FAIL = "FAIL" - RETRY = "RETRY" - IGNORE = "IGNORE" - RATE_LIMITED = "RATE_LIMITED" - - -class FailureType(Enum): - system_error = "system_error" - config_error = "config_error" - transient_error = "transient_error" - - -class HttpResponseFilter(BaseModel): - type: Literal["HttpResponseFilter"] - action: Optional[Action] = Field( - None, - description="Action to execute if a response matches the filter.", - examples=["SUCCESS", "FAIL", "RETRY", "IGNORE", "RATE_LIMITED"], - title="Action", - ) - failure_type: Optional[FailureType] = Field( - None, - description="Failure type of traced exception if a response matches the filter.", - examples=["system_error", "config_error", "transient_error"], - title="Failure Type", - ) - error_message: Optional[str] = Field( - None, - description="Error Message to display if the response matches the filter.", - title="Error Message", - ) - error_message_contains: Optional[str] = Field( - None, - description="Match the response if its error message contains the substring.", - example=["This API operation is not enabled for this site"], - title="Error Message Substring", - ) - http_codes: Optional[List[int]] = Field( - None, - description="Match the response if its HTTP code is included in this list.", - examples=[[420, 429], [500]], - title="HTTP Codes", - ) - predicate: Optional[str] = Field( - None, - description="Match the response if the predicate evaluates to true.", - examples=[ - "{{ 'Too much requests' in response }}", - "{{ 'error_code' in response and response['error_code'] == 'ComplexityException' }}", - ], - title="Predicate", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class InlineSchemaLoader(BaseModel): - type: Literal["InlineSchemaLoader"] - schema_: Optional[Dict[str, Any]] = Field( - None, - alias="schema", - description='Describes a streams\' schema. Refer to the Data Types documentation for more details on which types are valid.', - title="Schema", - ) - - -class JsonFileSchemaLoader(BaseModel): - type: Literal["JsonFileSchemaLoader"] - file_path: Optional[str] = Field( - None, - description="Path to the JSON file defining the schema. The path is relative to the connector module's root.", - example=["./schemas/users.json"], - title="File Path", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class JsonDecoder(BaseModel): - type: Literal["JsonDecoder"] - - -class JsonlDecoder(BaseModel): - type: Literal["JsonlDecoder"] - - -class IterableDecoder(BaseModel): - type: Literal["IterableDecoder"] - - -class MinMaxDatetime(BaseModel): - type: Literal["MinMaxDatetime"] - datetime: str = Field( - ..., - description="Datetime value.", - examples=["2021-01-01", "2021-01-01T00:00:00Z", "{{ config['start_time'] }}"], - title="Datetime", - ) - datetime_format: Optional[str] = Field( - "", - description='Format of the datetime value. Defaults to "%Y-%m-%dT%H:%M:%S.%f%z" if left empty. Use placeholders starting with "%" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`, `000001`, ..., `999999`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (Sunday as first day) - `00`, `01`, ..., `53`\n * **%W**: Week number of the year (Monday as first day) - `00`, `01`, ..., `53`\n * **%c**: Date and time representation - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date representation - `08/16/1988`\n * **%X**: Time representation - `21:30:00`\n * **%%**: Literal \'%\' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n', - examples=["%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d", "%s"], - title="Datetime Format", - ) - max_datetime: Optional[str] = Field( - None, - description="Ceiling applied on the datetime value. Must be formatted with the datetime_format field.", - examples=["2021-01-01T00:00:00Z", "2021-01-01"], - title="Max Datetime", - ) - min_datetime: Optional[str] = Field( - None, - description="Floor applied on the datetime value. Must be formatted with the datetime_format field.", - examples=["2010-01-01T00:00:00Z", "2010-01-01"], - title="Min Datetime", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class NoAuth(BaseModel): - type: Literal["NoAuth"] - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class NoPagination(BaseModel): - type: Literal["NoPagination"] - - -class OAuthConfigSpecification(BaseModel): - class Config: - extra = Extra.allow - - oauth_user_input_from_connector_config_specification: Optional[Dict[str, Any]] = Field( - None, - description="OAuth specific blob. This is a Json Schema used to validate Json configurations used as input to OAuth.\nMust be a valid non-nested JSON that refers to properties from ConnectorSpecification.connectionSpecification\nusing special annotation 'path_in_connector_config'.\nThese are input values the user is entering through the UI to authenticate to the connector, that might also shared\nas inputs for syncing data via the connector.\nExamples:\nif no connector values is shared during oauth flow, oauth_user_input_from_connector_config_specification=[]\nif connector values such as 'app_id' inside the top level are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['app_id']\n }\n }\nif connector values such as 'info.app_id' nested inside another object are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['info', 'app_id']\n }\n }", - examples=[ - {"app_id": {"type": "string", "path_in_connector_config": ["app_id"]}}, - { - "app_id": { - "type": "string", - "path_in_connector_config": ["info", "app_id"], - } - }, - ], - title="OAuth user input", - ) - complete_oauth_output_specification: Optional[Dict[str, Any]] = Field( - None, - description="OAuth specific blob. This is a Json Schema used to validate Json configurations produced by the OAuth flows as they are\nreturned by the distant OAuth APIs.\nMust be a valid JSON describing the fields to merge back to `ConnectorSpecification.connectionSpecification`.\nFor each field, a special annotation `path_in_connector_config` can be specified to determine where to merge it,\nExamples:\n complete_oauth_output_specification={\n refresh_token: {\n type: string,\n path_in_connector_config: ['credentials', 'refresh_token']\n }\n }", - examples=[ - { - "refresh_token": { - "type": "string,", - "path_in_connector_config": ["credentials", "refresh_token"], - } - } - ], - title="OAuth output specification", - ) - complete_oauth_server_input_specification: Optional[Dict[str, Any]] = Field( - None, - description="OAuth specific blob. This is a Json Schema used to validate Json configurations persisted as Airbyte Server configurations.\nMust be a valid non-nested JSON describing additional fields configured by the Airbyte Instance or Workspace Admins to be used by the\nserver when completing an OAuth flow (typically exchanging an auth code for refresh token).\nExamples:\n complete_oauth_server_input_specification={\n client_id: {\n type: string\n },\n client_secret: {\n type: string\n }\n }", - examples=[{"client_id": {"type": "string"}, "client_secret": {"type": "string"}}], - title="OAuth input specification", - ) - complete_oauth_server_output_specification: Optional[Dict[str, Any]] = Field( - None, - description="OAuth specific blob. This is a Json Schema used to validate Json configurations persisted as Airbyte Server configurations that\nalso need to be merged back into the connector configuration at runtime.\nThis is a subset configuration of `complete_oauth_server_input_specification` that filters fields out to retain only the ones that\nare necessary for the connector to function with OAuth. (some fields could be used during oauth flows but not needed afterwards, therefore\nthey would be listed in the `complete_oauth_server_input_specification` but not `complete_oauth_server_output_specification`)\nMust be a valid non-nested JSON describing additional fields configured by the Airbyte Instance or Workspace Admins to be used by the\nconnector when using OAuth flow APIs.\nThese fields are to be merged back to `ConnectorSpecification.connectionSpecification`.\nFor each field, a special annotation `path_in_connector_config` can be specified to determine where to merge it,\nExamples:\n complete_oauth_server_output_specification={\n client_id: {\n type: string,\n path_in_connector_config: ['credentials', 'client_id']\n },\n client_secret: {\n type: string,\n path_in_connector_config: ['credentials', 'client_secret']\n }\n }", - examples=[ - { - "client_id": { - "type": "string,", - "path_in_connector_config": ["credentials", "client_id"], - }, - "client_secret": { - "type": "string,", - "path_in_connector_config": ["credentials", "client_secret"], - }, - } - ], - title="OAuth server output specification", - ) - - -class OffsetIncrement(BaseModel): - type: Literal["OffsetIncrement"] - page_size: Optional[Union[int, str]] = Field( - None, - description="The number of records to include in each pages.", - examples=[100, "{{ config['page_size'] }}"], - title="Limit", - ) - inject_on_first_request: Optional[bool] = Field( - False, - description="Using the `offset` with value `0` during the first request", - title="Inject Offset", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class PageIncrement(BaseModel): - type: Literal["PageIncrement"] - page_size: Optional[Union[int, str]] = Field( - None, - description="The number of records to include in each pages.", - examples=[100, "100", "{{ config['page_size'] }}"], - title="Page Size", - ) - start_from_page: Optional[int] = Field( - 0, - description="Index of the first page to request.", - examples=[0, 1], - title="Start From Page", - ) - inject_on_first_request: Optional[bool] = Field( - False, - description="Using the `page number` with value defined by `start_from_page` during the first request", - title="Inject Page Number", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class PrimaryKey(BaseModel): - __root__: Union[str, List[str], List[List[str]]] = Field( - ..., - description="The stream field to be used to distinguish unique records. Can either be a single field, an array of fields representing a composite key, or an array of arrays representing a composite key where the fields are nested fields.", - examples=["id", ["code", "type"]], - title="Primary Key", - ) - - -class RecordFilter(BaseModel): - type: Literal["RecordFilter"] - condition: Optional[str] = Field( - "", - description="The predicate to filter a record. Records will be removed if evaluated to False.", - examples=[ - "{{ record['created_at'] >= stream_interval['start_time'] }}", - "{{ record.status in ['active', 'expired'] }}", - ], - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SchemaNormalization(Enum): - None_ = "None" - Default = "Default" - - -class RemoveFields(BaseModel): - type: Literal["RemoveFields"] - condition: Optional[str] = Field( - "", - description="The predicate to filter a property by a property value. Property will be removed if it is empty OR expression is evaluated to True.,", - examples=[ - "{{ property|string == '' }}", - "{{ property is integer }}", - "{{ property|length > 5 }}", - "{{ property == 'some_string_to_match' }}", - ], - ) - field_pointers: List[List[str]] = Field( - ..., - description="Array of paths defining the field to remove. Each item is an array whose field describe the path of a field to remove.", - examples=[["tags"], [["content", "html"], ["content", "plain_text"]]], - title="Field Paths", - ) - - -class RequestPath(BaseModel): - type: Literal["RequestPath"] - - -class InjectInto(Enum): - request_parameter = "request_parameter" - header = "header" - body_data = "body_data" - body_json = "body_json" - - -class RequestOption(BaseModel): - type: Literal["RequestOption"] - field_name: str = Field( - ..., - description="Configures which key should be used in the location that the descriptor is being injected into", - examples=["segment_id"], - title="Request Option", - ) - inject_into: InjectInto = Field( - ..., - description="Configures where the descriptor should be set on the HTTP requests. Note that request parameters that are already encoded in the URL path will not be duplicated.", - examples=["request_parameter", "header", "body_data", "body_json"], - title="Inject Into", - ) - - -class Schemas(BaseModel): - pass - - class Config: - extra = Extra.allow - - -class LegacySessionTokenAuthenticator(BaseModel): - type: Literal["LegacySessionTokenAuthenticator"] - header: str = Field( - ..., - description="The name of the session token header that will be injected in the request", - examples=["X-Session"], - title="Session Request Header", - ) - login_url: str = Field( - ..., - description="Path of the login URL (do not include the base URL)", - examples=["session"], - title="Login Path", - ) - session_token: Optional[str] = Field( - None, - description="Session token to use if using a pre-defined token. Not needed if authenticating with username + password pair", - example=["{{ config['session_token'] }}"], - title="Session Token", - ) - session_token_response_key: str = Field( - ..., - description="Name of the key of the session token to be extracted from the response", - examples=["id"], - title="Response Token Response Key", - ) - username: Optional[str] = Field( - None, - description="Username used to authenticate and obtain a session token", - examples=[" {{ config['username'] }}"], - title="Username", - ) - password: Optional[str] = Field( - "", - description="Password used to authenticate and obtain a session token", - examples=["{{ config['password'] }}", ""], - title="Password", - ) - validate_session_url: str = Field( - ..., - description="Path of the URL to use to validate that the session token is valid (do not include the base URL)", - examples=["user/current"], - title="Validate Session Path", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class ValueType(Enum): - string = "string" - number = "number" - integer = "integer" - boolean = "boolean" - - -class WaitTimeFromHeader(BaseModel): - type: Literal["WaitTimeFromHeader"] - header: str = Field( - ..., - description="The name of the response header defining how long to wait before retrying.", - examples=["Retry-After"], - title="Response Header Name", - ) - regex: Optional[str] = Field( - None, - description="Optional regex to apply on the header to extract its value. The regex should define a capture group defining the wait time.", - examples=["([-+]?\\d+)"], - title="Extraction Regex", - ) - max_waiting_time_in_seconds: Optional[float] = Field( - None, - description="Given the value extracted from the header is greater than this value, stop the stream.", - examples=[3600], - title="Max Waiting Time in Seconds", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class WaitUntilTimeFromHeader(BaseModel): - type: Literal["WaitUntilTimeFromHeader"] - header: str = Field( - ..., - description="The name of the response header defining how long to wait before retrying.", - examples=["wait_time"], - title="Response Header", - ) - min_wait: Optional[Union[float, str]] = Field( - None, - description="Minimum time to wait before retrying.", - examples=[10, "60"], - title="Minimum Wait Time", - ) - regex: Optional[str] = Field( - None, - description="Optional regex to apply on the header to extract its value. The regex should define a capture group defining the wait time.", - examples=["([-+]?\\d+)"], - title="Extraction Regex", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class AddedFieldDefinition(BaseModel): - type: Literal["AddedFieldDefinition"] - path: List[str] = Field( - ..., - description="List of strings defining the path where to add the value on the record.", - examples=[["segment_id"], ["metadata", "segment_id"]], - title="Path", - ) - value: str = Field( - ..., - description="Value of the new field. Use {{ record['existing_field'] }} syntax to refer to other fields in the record.", - examples=[ - "{{ record['updates'] }}", - "{{ record['MetaData']['LastUpdatedTime'] }}", - "{{ stream_partition['segment_id'] }}", - ], - title="Value", - ) - value_type: Optional[ValueType] = Field( - None, - description="Type of the value. If not specified, the type will be inferred from the value.", - title="Value Type", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class AddFields(BaseModel): - type: Literal["AddFields"] - fields: List[AddedFieldDefinition] = Field( - ..., - description="List of transformations (path and corresponding value) that will be added to the record.", - title="Fields", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class ApiKeyAuthenticator(BaseModel): - type: Literal["ApiKeyAuthenticator"] - api_token: Optional[str] = Field( - None, - description="The API key to inject in the request. Fill it in the user inputs.", - examples=["{{ config['api_key'] }}", "Token token={{ config['api_key'] }}"], - title="API Key", - ) - header: Optional[str] = Field( - None, - description="The name of the HTTP header that will be set to the API key. This setting is deprecated, use inject_into instead. Header and inject_into can not be defined at the same time.", - examples=["Authorization", "Api-Token", "X-Auth-Token"], - title="Header Name", - ) - inject_into: Optional[RequestOption] = Field( - None, - description="Configure how the API Key will be sent in requests to the source API. Either inject_into or header has to be defined.", - examples=[ - {"inject_into": "header", "field_name": "Authorization"}, - {"inject_into": "request_parameter", "field_name": "authKey"}, - ], - title="Inject API Key Into Outgoing HTTP Request", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class AuthFlow(BaseModel): - auth_flow_type: Optional[AuthFlowType] = Field(None, description="The type of auth to use", title="Auth flow type") - predicate_key: Optional[List[str]] = Field( - None, - description="JSON path to a field in the connectorSpecification that should exist for the advanced auth to be applicable.", - examples=[["credentials", "auth_type"]], - title="Predicate key", - ) - predicate_value: Optional[str] = Field( - None, - description="Value of the predicate_key fields for the advanced auth to be applicable.", - examples=["Oauth"], - title="Predicate value", - ) - oauth_config_specification: Optional[OAuthConfigSpecification] = None - - -class CursorPagination(BaseModel): - type: Literal["CursorPagination"] - cursor_value: str = Field( - ..., - description="Value of the cursor defining the next page to fetch.", - examples=[ - "{{ headers.link.next.cursor }}", - "{{ last_record['key'] }}", - "{{ response['nextPage'] }}", - ], - title="Cursor Value", - ) - page_size: Optional[int] = Field( - None, - description="The number of records to include in each pages.", - examples=[100], - title="Page Size", - ) - stop_condition: Optional[str] = Field( - None, - description="Template string evaluating when to stop paginating.", - examples=[ - "{{ response.data.has_more is false }}", - "{{ 'next' not in headers['link'] }}", - ], - title="Stop Condition", - ) - decoder: Optional[JsonDecoder] = Field( - None, - description="Component decoding the response so records can be extracted.", - title="Decoder", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DatetimeBasedCursor(BaseModel): - type: Literal["DatetimeBasedCursor"] - cursor_field: str = Field( - ..., - description="The location of the value on a record that will be used as a bookmark during sync. To ensure no data loss, the API must return records in ascending order based on the cursor field. Nested fields are not supported, so the field must be at the top level of the record. You can use a combination of Add Field and Remove Field transformations to move the nested field to the top.", - examples=["created_at", "{{ config['record_cursor'] }}"], - title="Cursor Field", - ) - datetime_format: str = Field( - ..., - description="The datetime format used to format the datetime values that are sent in outgoing requests to the API. Use placeholders starting with \"%\" to describe the format the API is using. The following placeholders are available:\n * **%s**: Epoch unix timestamp - `1686218963`\n * **%s_as_float**: Epoch unix timestamp in seconds as float with microsecond precision - `1686218963.123456`\n * **%ms**: Epoch unix timestamp (milliseconds) - `1686218963123`\n * **%a**: Weekday (abbreviated) - `Sun`\n * **%A**: Weekday (full) - `Sunday`\n * **%w**: Weekday (decimal) - `0` (Sunday), `6` (Saturday)\n * **%d**: Day of the month (zero-padded) - `01`, `02`, ..., `31`\n * **%b**: Month (abbreviated) - `Jan`\n * **%B**: Month (full) - `January`\n * **%m**: Month (zero-padded) - `01`, `02`, ..., `12`\n * **%y**: Year (without century, zero-padded) - `00`, `01`, ..., `99`\n * **%Y**: Year (with century) - `0001`, `0002`, ..., `9999`\n * **%H**: Hour (24-hour, zero-padded) - `00`, `01`, ..., `23`\n * **%I**: Hour (12-hour, zero-padded) - `01`, `02`, ..., `12`\n * **%p**: AM/PM indicator\n * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59`\n * **%S**: Second (zero-padded) - `00`, `01`, ..., `59`\n * **%f**: Microsecond (zero-padded to 6 digits) - `000000`\n * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00`\n * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT`\n * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366`\n * **%U**: Week number of the year (starting Sunday) - `00`, ..., `53`\n * **%W**: Week number of the year (starting Monday) - `00`, ..., `53`\n * **%c**: Date and time - `Tue Aug 16 21:30:00 1988`\n * **%x**: Date standard format - `08/16/1988`\n * **%X**: Time standard format - `21:30:00`\n * **%%**: Literal '%' character\n\n Some placeholders depend on the locale of the underlying system - in most cases this locale is configured as en/US. For more information see the [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes).\n", - examples=["%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d", "%s", "%ms", "%s_as_float"], - title="Outgoing Datetime Format", - ) - start_datetime: Union[str, MinMaxDatetime] = Field( - ..., - description="The datetime that determines the earliest record that should be synced.", - examples=["2020-01-1T00:00:00Z", "{{ config['start_time'] }}"], - title="Start Datetime", - ) - cursor_datetime_formats: Optional[List[str]] = Field( - None, - description="The possible formats for the cursor field, in order of preference. The first format that matches the cursor field value will be used to parse it. If not provided, the `datetime_format` will be used.", - title="Cursor Datetime Formats", - ) - cursor_granularity: Optional[str] = Field( - None, - description="Smallest increment the datetime_format has (ISO 8601 duration) that is used to ensure the start of a slice does not overlap with the end of the previous one, e.g. for %Y-%m-%d the granularity should be P1D, for %Y-%m-%dT%H:%M:%SZ the granularity should be PT1S. Given this field is provided, `step` needs to be provided as well.", - examples=["PT1S"], - title="Cursor Granularity", - ) - end_datetime: Optional[Union[str, MinMaxDatetime]] = Field( - None, - description="The datetime that determines the last record that should be synced. If not provided, `{{ now_utc() }}` will be used.", - examples=["2021-01-1T00:00:00Z", "{{ now_utc() }}", "{{ day_delta(-1) }}"], - title="End Datetime", - ) - end_time_option: Optional[RequestOption] = Field( - None, - description="Optionally configures how the end datetime will be sent in requests to the source API.", - title="Inject End Time Into Outgoing HTTP Request", - ) - is_data_feed: Optional[bool] = Field( - None, - description="A data feed API is an API that does not allow filtering and paginates the content from the most recent to the least recent. Given this, the CDK needs to know when to stop paginating and this field will generate a stop condition for pagination.", - title="Whether the target API is formatted as a data feed", - ) - is_client_side_incremental: Optional[bool] = Field( - None, - description="If the target API endpoint does not take cursor values to filter records and returns all records anyway, the connector with this cursor will filter out records locally, and only emit new records from the last sync, hence incremental. This means that all records would be read from the API, but only new records will be emitted to the destination.", - title="Whether the target API does not support filtering and returns all data (the cursor filters records in the client instead of the API side)", - ) - is_compare_strictly: Optional[bool] = Field( - False, - description="Set to True if the target API does not accept queries where the start time equal the end time.", - title="Whether to skip requests if the start time equals the end time", - ) - lookback_window: Optional[str] = Field( - None, - description="Time interval before the start_datetime to read data for, e.g. P1M for looking back one month.", - examples=["P1D", "P{{ config['lookback_days'] }}D"], - title="Lookback Window", - ) - partition_field_end: Optional[str] = Field( - None, - description="Name of the partition start time field.", - examples=["ending_time"], - title="Partition Field End", - ) - partition_field_start: Optional[str] = Field( - None, - description="Name of the partition end time field.", - examples=["starting_time"], - title="Partition Field Start", - ) - start_time_option: Optional[RequestOption] = Field( - None, - description="Optionally configures how the start datetime will be sent in requests to the source API.", - title="Inject Start Time Into Outgoing HTTP Request", - ) - step: Optional[str] = Field( - None, - description="The size of the time window (ISO8601 duration). Given this field is provided, `cursor_granularity` needs to be provided as well.", - examples=["P1W", "{{ config['step_increment'] }}"], - title="Step", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DefaultErrorHandler(BaseModel): - type: Literal["DefaultErrorHandler"] - backoff_strategies: Optional[ - List[ - Union[ - ConstantBackoffStrategy, - CustomBackoffStrategy, - ExponentialBackoffStrategy, - WaitTimeFromHeader, - WaitUntilTimeFromHeader, - ] - ] - ] = Field( - None, - description="List of backoff strategies to use to determine how long to wait before retrying a retryable request.", - title="Backoff Strategies", - ) - max_retries: Optional[int] = Field( - 5, - description="The maximum number of time to retry a retryable request before giving up and failing.", - examples=[5, 0, 10], - title="Max Retry Count", - ) - response_filters: Optional[List[HttpResponseFilter]] = Field( - None, - description="List of response filters to iterate on when deciding how to handle an error. When using an array of multiple filters, the filters will be applied sequentially and the response will be selected if it matches any of the filter's predicate.", - title="Response Filters", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DefaultPaginator(BaseModel): - type: Literal["DefaultPaginator"] - pagination_strategy: Union[CursorPagination, CustomPaginationStrategy, OffsetIncrement, PageIncrement] = Field( - ..., - description="Strategy defining how records are paginated.", - title="Pagination Strategy", - ) - decoder: Optional[JsonDecoder] = Field( - None, - description="Component decoding the response so records can be extracted.", - title="Decoder", - ) - page_size_option: Optional[RequestOption] = None - page_token_option: Optional[Union[RequestOption, RequestPath]] = None - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DpathExtractor(BaseModel): - type: Literal["DpathExtractor"] - field_path: List[str] = Field( - ..., - description='List of potentially nested fields describing the full path of the field to extract. Use "*" to extract all values from an array. See more info in the [docs](https://docs.airbyte.com/connector-development/config-based/understanding-the-yaml-file/record-selector).', - examples=[ - ["data"], - ["data", "records"], - ["data", "{{ parameters.name }}"], - ["data", "*", "record"], - ], - title="Field Path", - ) - decoder: Optional[Union[JsonDecoder, JsonlDecoder, IterableDecoder]] = Field(None, title="Decoder") - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SessionTokenRequestApiKeyAuthenticator(BaseModel): - type: Literal["ApiKey"] - inject_into: RequestOption = Field( - ..., - description="Configure how the API Key will be sent in requests to the source API.", - examples=[ - {"inject_into": "header", "field_name": "Authorization"}, - {"inject_into": "request_parameter", "field_name": "authKey"}, - ], - title="Inject API Key Into Outgoing HTTP Request", - ) - - -class ListPartitionRouter(BaseModel): - type: Literal["ListPartitionRouter"] - cursor_field: str = Field( - ..., - description='While iterating over list values, the name of field used to reference a list value. The partition value can be accessed with string interpolation. e.g. "{{ stream_partition[\'my_key\'] }}" where "my_key" is the value of the cursor_field.', - examples=["section", "{{ config['section_key'] }}"], - title="Current Partition Value Identifier", - ) - values: Union[str, List[str]] = Field( - ..., - description="The list of attributes being iterated over and used as input for the requests made to the source API.", - examples=[["section_a", "section_b", "section_c"], "{{ config['sections'] }}"], - title="Partition Values", - ) - request_option: Optional[RequestOption] = Field( - None, - description="A request option describing where the list value should be injected into and under what field name if applicable.", - title="Inject Partition Value Into Outgoing HTTP Request", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class RecordSelector(BaseModel): - type: Literal["RecordSelector"] - extractor: Union[CustomRecordExtractor, DpathExtractor] - record_filter: Optional[Union[CustomRecordFilter, RecordFilter]] = Field( - None, - description="Responsible for filtering records to be emitted by the Source.", - title="Record Filter", - ) - schema_normalization: Optional[SchemaNormalization] = SchemaNormalization.None_ - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class Spec(BaseModel): - type: Literal["Spec"] - connection_specification: Dict[str, Any] = Field( - ..., - description="A connection specification describing how a the connector can be configured.", - title="Connection Specification", - ) - documentation_url: Optional[str] = Field( - None, - description="URL of the connector's documentation page.", - examples=["https://docs.airbyte.com/integrations/sources/dremio"], - title="Documentation URL", - ) - advanced_auth: Optional[AuthFlow] = Field( - None, - description="Advanced specification for configuring the authentication flow.", - title="Advanced Auth", - ) - - -class CompositeErrorHandler(BaseModel): - type: Literal["CompositeErrorHandler"] - error_handlers: List[Union[CompositeErrorHandler, DefaultErrorHandler]] = Field( - ..., - description="List of error handlers to iterate on to determine how to handle a failed response.", - title="Error Handlers", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DeclarativeSource(BaseModel): - class Config: - extra = Extra.forbid - - type: Literal["DeclarativeSource"] - check: CheckStream - streams: List[DeclarativeStream] - version: str = Field( - ..., - description="The version of the Airbyte CDK used to build and test the source.", - ) - schemas: Optional[Schemas] = None - definitions: Optional[Dict[str, Any]] = None - spec: Optional[Spec] = None - metadata: Optional[Dict[str, Any]] = Field( - None, - description="For internal Airbyte use only - DO NOT modify manually. Used by consumers of declarative manifests for storing related metadata.", - ) - description: Optional[str] = Field( - None, - description="A description of the connector. It will be presented on the Source documentation page.", - ) - - -class SelectiveAuthenticator(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["SelectiveAuthenticator"] - authenticator_selection_path: List[str] = Field( - ..., - description="Path of the field in config with selected authenticator name", - examples=[["auth"], ["auth", "type"]], - title="Authenticator Selection Path", - ) - authenticators: Dict[ - str, - Union[ - ApiKeyAuthenticator, - BasicHttpAuthenticator, - BearerAuthenticator, - CustomAuthenticator, - OAuthAuthenticator, - JwtAuthenticator, - NoAuth, - SessionTokenAuthenticator, - LegacySessionTokenAuthenticator, - ], - ] = Field( - ..., - description="Authenticators to select from.", - examples=[ - { - "authenticators": { - "token": "#/definitions/ApiKeyAuthenticator", - "oauth": "#/definitions/OAuthAuthenticator", - "jwt": "#/definitions/JwtAuthenticator", - } - } - ], - title="Authenticators", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class DeclarativeStream(BaseModel): - class Config: - extra = Extra.allow - - type: Literal["DeclarativeStream"] - retriever: Union[CustomRetriever, SimpleRetriever] = Field( - ..., - description="Component used to coordinate how records are extracted across stream slices and request pages.", - title="Retriever", - ) - incremental_sync: Optional[Union[CustomIncrementalSync, DatetimeBasedCursor]] = Field( - None, - description="Component used to fetch data incrementally based on a time field in the data.", - title="Incremental Sync", - ) - name: Optional[str] = Field("", description="The stream name.", example=["Users"], title="Name") - primary_key: Optional[PrimaryKey] = Field("", description="The primary key of the stream.", title="Primary Key") - schema_loader: Optional[Union[InlineSchemaLoader, JsonFileSchemaLoader, CustomSchemaLoader]] = Field( - None, - description="Component used to retrieve the schema for the current stream.", - title="Schema Loader", - ) - transformations: Optional[List[Union[AddFields, CustomTransformation, RemoveFields]]] = Field( - None, - description="A list of transformations to be applied to each output record.", - title="Transformations", - ) - state_migrations: Optional[List[Union[LegacyToPerPartitionStateMigration, CustomStateMigration]]] = Field( - [], - description="Array of state migrations to be applied on the input state", - title="State Migrations", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SessionTokenAuthenticator(BaseModel): - type: Literal["SessionTokenAuthenticator"] - login_requester: HttpRequester = Field( - ..., - description="Description of the request to perform to obtain a session token to perform data requests. The response body is expected to be a JSON object with a session token property.", - examples=[ - { - "type": "HttpRequester", - "url_base": "https://my_api.com", - "path": "/login", - "authenticator": { - "type": "BasicHttpAuthenticator", - "username": "{{ config.username }}", - "password": "{{ config.password }}", - }, - } - ], - title="Login Requester", - ) - session_token_path: List[str] = Field( - ..., - description="The path in the response body returned from the login requester to the session token.", - examples=[["access_token"], ["result", "token"]], - title="Session Token Path", - ) - expiration_duration: Optional[str] = Field( - None, - description="The duration in ISO 8601 duration notation after which the session token expires, starting from the time it was obtained. Omitting it will result in the session token being refreshed for every request.", - examples=["PT1H", "P1D"], - title="Expiration Duration", - ) - request_authentication: Union[SessionTokenRequestApiKeyAuthenticator, SessionTokenRequestBearerAuthenticator] = Field( - ..., - description="Authentication method to use for requests sent to the API, specifying how to inject the session token.", - title="Data Request Authentication", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class HttpRequester(BaseModel): - type: Literal["HttpRequester"] - url_base: str = Field( - ..., - description="Base URL of the API source. Do not put sensitive information (e.g. API tokens) into this field - Use the Authentication component for this.", - examples=[ - "https://connect.squareup.com/v2", - "{{ config['base_url'] or 'https://app.posthog.com'}}/api/", - ], - title="API Base URL", - ) - path: str = Field( - ..., - description="Path the specific API endpoint that this stream represents. Do not put sensitive information (e.g. API tokens) into this field - Use the Authentication component for this.", - examples=[ - "/products", - "/quotes/{{ stream_partition['id'] }}/quote_line_groups", - "/trades/{{ config['symbol_id'] }}/history", - ], - title="URL Path", - ) - authenticator: Optional[ - Union[ - ApiKeyAuthenticator, - BasicHttpAuthenticator, - BearerAuthenticator, - CustomAuthenticator, - OAuthAuthenticator, - JwtAuthenticator, - NoAuth, - SessionTokenAuthenticator, - LegacySessionTokenAuthenticator, - SelectiveAuthenticator, - ] - ] = Field( - None, - description="Authentication method to use for requests sent to the API.", - title="Authenticator", - ) - error_handler: Optional[Union[DefaultErrorHandler, CustomErrorHandler, CompositeErrorHandler]] = Field( - None, - description="Error handler component that defines how to handle errors.", - title="Error Handler", - ) - http_method: Optional[HttpMethod] = Field( - HttpMethod.GET, - description="The HTTP method used to fetch data from the source (can be GET or POST).", - examples=["GET", "POST"], - title="HTTP Method", - ) - request_body_data: Optional[Union[str, Dict[str, str]]] = Field( - None, - description="Specifies how to populate the body of the request with a non-JSON payload. Plain text will be sent as is, whereas objects will be converted to a urlencoded form.", - examples=[ - '[{"clause": {"type": "timestamp", "operator": 10, "parameters":\n [{"value": {{ stream_interval[\'start_time\'] | int * 1000 }} }]\n }, "orderBy": 1, "columnName": "Timestamp"}]/\n' - ], - title="Request Body Payload (Non-JSON)", - ) - request_body_json: Optional[Union[str, Dict[str, Any]]] = Field( - None, - description="Specifies how to populate the body of the request with a JSON payload. Can contain nested objects.", - examples=[ - {"sort_order": "ASC", "sort_field": "CREATED_AT"}, - {"key": "{{ config['value'] }}"}, - {"sort": {"field": "updated_at", "order": "ascending"}}, - ], - title="Request Body JSON Payload", - ) - request_headers: Optional[Union[str, Dict[str, str]]] = Field( - None, - description="Return any non-auth headers. Authentication headers will overwrite any overlapping headers returned from this method.", - examples=[{"Output-Format": "JSON"}, {"Version": "{{ config['version'] }}"}], - title="Request Headers", - ) - request_parameters: Optional[Union[str, Dict[str, str]]] = Field( - None, - description="Specifies the query parameters that should be set on an outgoing HTTP request given the inputs.", - examples=[ - {"unit": "day"}, - { - "query": 'last_event_time BETWEEN TIMESTAMP "{{ stream_interval.start_time }}" AND TIMESTAMP "{{ stream_interval.end_time }}"' - }, - {"searchIn": "{{ ','.join(config.get('search_in', [])) }}"}, - {"sort_by[asc]": "updated_at"}, - ], - title="Query Parameters", - ) - use_cache: Optional[bool] = Field( - False, - description="Enables stream requests caching. This field is automatically set by the CDK.", - title="Use Cache", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class ParentStreamConfig(BaseModel): - type: Literal["ParentStreamConfig"] - parent_key: str = Field( - ..., - description="The primary key of records from the parent stream that will be used during the retrieval of records for the current substream. This parent identifier field is typically a characteristic of the child records being extracted from the source API.", - examples=["id", "{{ config['parent_record_id'] }}"], - title="Parent Key", - ) - stream: DeclarativeStream = Field(..., description="Reference to the parent stream.", title="Parent Stream") - partition_field: str = Field( - ..., - description="While iterating over parent records during a sync, the parent_key value can be referenced by using this field.", - examples=["parent_id", "{{ config['parent_partition_field'] }}"], - title="Current Parent Key Value Identifier", - ) - request_option: Optional[RequestOption] = Field( - None, - description="A request option describing where the parent key value should be injected into and under what field name if applicable.", - title="Request Option", - ) - incremental_dependency: Optional[bool] = Field( - False, - description="Indicates whether the parent stream should be read incrementally based on updates in the child stream.", - title="Incremental Dependency", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SimpleRetriever(BaseModel): - type: Literal["SimpleRetriever"] - record_selector: RecordSelector = Field( - ..., - description="Component that describes how to extract records from a HTTP response.", - ) - requester: Union[CustomRequester, HttpRequester] = Field( - ..., - description="Requester component that describes how to prepare HTTP requests to send to the source API.", - ) - paginator: Optional[Union[DefaultPaginator, NoPagination]] = Field( - None, - description="Paginator component that describes how to navigate through the API's pages.", - ) - ignore_stream_slicer_parameters_on_paginated_requests: Optional[bool] = Field( - False, - description="If true, the partition router and incremental request options will be ignored when paginating requests. Request options set directly on the requester will not be ignored.", - ) - partition_router: Optional[ - Union[ - CustomPartitionRouter, - ListPartitionRouter, - SubstreamPartitionRouter, - List[Union[CustomPartitionRouter, ListPartitionRouter, SubstreamPartitionRouter]], - ] - ] = Field( - [], - description="PartitionRouter component that describes how to partition the stream, enabling incremental syncs and checkpointing.", - title="Partition Router", - ) - decoder: Optional[Union[JsonDecoder, JsonlDecoder, IterableDecoder]] = Field( - None, - description="Component decoding the response so records can be extracted.", - title="Decoder", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -class SubstreamPartitionRouter(BaseModel): - type: Literal["SubstreamPartitionRouter"] - parent_stream_configs: List[ParentStreamConfig] = Field( - ..., - description="Specifies which parent streams are being iterated over and how parent records should be used to partition the child stream data set.", - title="Parent Stream Configs", - ) - parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters") - - -CompositeErrorHandler.update_forward_refs() -DeclarativeSource.update_forward_refs() -SelectiveAuthenticator.update_forward_refs() -DeclarativeStream.update_forward_refs() -SessionTokenAuthenticator.update_forward_refs() -SimpleRetriever.update_forward_refs() diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_component_transformer.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_component_transformer.py deleted file mode 100644 index e94ffaa1f430..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_component_transformer.py +++ /dev/null @@ -1,268 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import copy -import logging -import typing -from typing import Any, Dict, Mapping, Optional, Set, Type - -from pydantic import BaseModel - -from .declarative_component_schema import ( - ApiKeyAuthenticator, - BasicHttpAuthenticator, - BearerAuthenticator, - CompositeErrorHandler, - ConstantBackoffStrategy, - CursorPagination, - CustomAuthenticator, - CustomBackoffStrategy, - CustomErrorHandler, - CustomIncrementalSync, - CustomPaginationStrategy, - CustomPartitionRouter, - CustomRecordExtractor, - CustomRecordFilter, - CustomRequester, - CustomRetriever, - CustomSchemaLoader, - CustomStateMigration, - CustomTransformation, - DatetimeBasedCursor, - DeclarativeSource, - DeclarativeStream, - DefaultErrorHandler, - DefaultPaginator, - DpathExtractor, - ExponentialBackoffStrategy, - HttpRequester, - HttpResponseFilter, - JsonFileSchemaLoader, - JwtAuthenticator, - LegacySessionTokenAuthenticator, - ListPartitionRouter, - MinMaxDatetime, - OAuthAuthenticator, - OffsetIncrement, - PageIncrement, - ParentStreamConfig, - RecordFilter, - RecordSelector, - SelectiveAuthenticator, - SessionTokenAuthenticator, - SimpleRetriever, - SubstreamPartitionRouter, - WaitTimeFromHeader, - WaitUntilTimeFromHeader, -) - -PARAMETERS_STR = "$parameters" - -# Mapping of component type to the class that implements it. This is used to fetch the Pydantic model class for a component type -COMPONENT_TYPE_REGISTY: Dict[str, type] = { - "BasicHttpAuthenticator": BasicHttpAuthenticator, - "BearerAuthenticator": BearerAuthenticator, - "ConstantBackoffStrategy": ConstantBackoffStrategy, - "CustomAuthenticator": CustomAuthenticator, - "CustomBackoffStrategy": CustomBackoffStrategy, - "CustomErrorHandler": CustomErrorHandler, - "CustomIncrementalSync": CustomIncrementalSync, - "CustomPaginationStrategy": CustomPaginationStrategy, - "CustomRecordExtractor": CustomRecordExtractor, - "CustomRecordFilter": CustomRecordFilter, - "CustomRequester": CustomRequester, - "CustomRetriever": CustomRetriever, - "CustomPartitionRouter": CustomPartitionRouter, - "CustomSchemaLoader": CustomSchemaLoader, - "CustomStateMigration": CustomStateMigration, - "CustomTransformation": CustomTransformation, - "JwtAuthenticator": JwtAuthenticator, - "OAuthAuthenticator": OAuthAuthenticator, - "ExponentialBackoffStrategy": ExponentialBackoffStrategy, - "HttpResponseFilter": HttpResponseFilter, - "JsonFileSchemaLoader": JsonFileSchemaLoader, - "MinMaxDatetime": MinMaxDatetime, - "OffsetIncrement": OffsetIncrement, - "PageIncrement": PageIncrement, - "RecordFilter": RecordFilter, - "LegacySessionTokenAuthenticator": LegacySessionTokenAuthenticator, - "WaitTimeFromHeader": WaitTimeFromHeader, - "WaitUntilTimeFromHeader": WaitUntilTimeFromHeader, - "ApiKeyAuthenticator": ApiKeyAuthenticator, - "CursorPagination": CursorPagination, - "DatetimeBasedCursor": DatetimeBasedCursor, - "DefaultErrorHandler": DefaultErrorHandler, - "DefaultPaginator": DefaultPaginator, - "DpathExtractor": DpathExtractor, - "ListPartitionRouter": ListPartitionRouter, - "RecordSelector": RecordSelector, - "CompositeErrorHandler": CompositeErrorHandler, - "SelectiveAuthenticator": SelectiveAuthenticator, - "DeclarativeStream": DeclarativeStream, - "SessionTokenAuthenticator": SessionTokenAuthenticator, - "HttpRequester": HttpRequester, - "ParentStreamConfig": ParentStreamConfig, - "SimpleRetriever": SimpleRetriever, - "SubstreamPartitionRouter": SubstreamPartitionRouter, - "DeclarativeSource": DeclarativeSource, -} - -DEFAULT_MODEL_TYPES: Mapping[str, str] = { - # CompositeErrorHandler - "CompositeErrorHandler.error_handlers": "DefaultErrorHandler", - # CursorPagination - "CursorPagination.decoder": "JsonDecoder", - # DatetimeBasedCursor - "DatetimeBasedCursor.end_datetime": "MinMaxDatetime", - "DatetimeBasedCursor.end_time_option": "RequestOption", - "DatetimeBasedCursor.start_datetime": "MinMaxDatetime", - "DatetimeBasedCursor.start_time_option": "RequestOption", - # CustomIncrementalSync - "CustomIncrementalSync.end_datetime": "MinMaxDatetime", - "CustomIncrementalSync.end_time_option": "RequestOption", - "CustomIncrementalSync.start_datetime": "MinMaxDatetime", - "CustomIncrementalSync.start_time_option": "RequestOption", - # DeclarativeSource - "DeclarativeSource.check": "CheckStream", - "DeclarativeSource.spec": "Spec", - "DeclarativeSource.streams": "DeclarativeStream", - # DeclarativeStream - "DeclarativeStream.retriever": "SimpleRetriever", - "DeclarativeStream.schema_loader": "JsonFileSchemaLoader", - # DefaultErrorHandler - "DefaultErrorHandler.response_filters": "HttpResponseFilter", - # DefaultPaginator - "DefaultPaginator.decoder": "JsonDecoder", - "DefaultPaginator.page_size_option": "RequestOption", - # DpathExtractor - "DpathExtractor.decoder": "JsonDecoder", - # HttpRequester - "HttpRequester.error_handler": "DefaultErrorHandler", - # ListPartitionRouter - "ListPartitionRouter.request_option": "RequestOption", - # ParentStreamConfig - "ParentStreamConfig.request_option": "RequestOption", - "ParentStreamConfig.stream": "DeclarativeStream", - # RecordSelector - "RecordSelector.extractor": "DpathExtractor", - "RecordSelector.record_filter": "RecordFilter", - # SimpleRetriever - "SimpleRetriever.paginator": "NoPagination", - "SimpleRetriever.record_selector": "RecordSelector", - "SimpleRetriever.requester": "HttpRequester", - # SubstreamPartitionRouter - "SubstreamPartitionRouter.parent_stream_configs": "ParentStreamConfig", - # AddFields - "AddFields.fields": "AddedFieldDefinition", - # CustomPartitionRouter - "CustomPartitionRouter.parent_stream_configs": "ParentStreamConfig", -} - -# We retain a separate registry for custom components to automatically insert the type if it is missing. This is intended to -# be a short term fix because once we have migrated, then type and class_name should be requirements for all custom components. -CUSTOM_COMPONENTS_MAPPING: Mapping[str, str] = { - "CompositeErrorHandler.backoff_strategies": "CustomBackoffStrategy", - "DeclarativeStream.retriever": "CustomRetriever", - "DeclarativeStream.transformations": "CustomTransformation", - "DefaultErrorHandler.backoff_strategies": "CustomBackoffStrategy", - "DefaultPaginator.pagination_strategy": "CustomPaginationStrategy", - "HttpRequester.authenticator": "CustomAuthenticator", - "HttpRequester.error_handler": "CustomErrorHandler", - "RecordSelector.extractor": "CustomRecordExtractor", - "SimpleRetriever.partition_router": "CustomPartitionRouter", -} - -logger = logging.getLogger(__name__) - - -def get_model_fields(model_class: Optional[Type[BaseModel]]) -> Set[str]: - """Fetches field names from a Pydantic model class if available.""" - if model_class is not None: - return set(model_class.__fields__.keys()) - return set() - - -class ManifestComponentTransformer: - def propagate_types_and_parameters( - self, - parent_field_identifier: str, - declarative_component: Mapping[str, Any], - parent_parameters: Mapping[str, Any], - ) -> Mapping[str, Any]: - """ - Recursively transforms the specified declarative component and subcomponents to propagate parameters and insert the - default component type if it was not already present. The resulting transformed components are a deep copy of the input - components, not an in-place transformation. - :param declarative_component: The current component that is having type and parameters added - :param parent_field_identifier: The name of the field of the current component coming from the parent component - :param parent_parameters: The parameters set on parent components defined before the current component - :return: A deep copy of the transformed component with types and parameters persisted to it - """ - propagated_component = dict(copy.deepcopy(declarative_component)) - if "type" not in propagated_component: - # If the component has class_name we assume that this is a reference to a custom component. This is a slight change to - # existing behavior because we originally allowed for either class or type to be specified. After the pydantic migration, - # class_name will only be a valid field on custom components and this change reflects that. I checked, and we currently - # have no low-code connectors that use class_name except for custom components. - if "class_name" in propagated_component: - found_type = CUSTOM_COMPONENTS_MAPPING.get(parent_field_identifier) - else: - found_type = DEFAULT_MODEL_TYPES.get(parent_field_identifier) - if found_type: - propagated_component["type"] = found_type - - # When there is no resolved type, we're not processing a component (likely a regular object) and don't need to propagate parameters - # When the type refers to a json schema, we're not processing a component as well. This check is currently imperfect as there could - # be json_schema are not objects but we believe this is not likely in our case because: - # * records are Mapping so objects hence SchemaLoader root should be an object - # * connection_specification is a Mapping - if "type" not in propagated_component or self._is_json_schema_object(propagated_component): - return propagated_component - - component_type = propagated_component.get("type", "") - model_class = COMPONENT_TYPE_REGISTY.get(component_type) - # Grab the list of expected fields for the component type - valid_fields = get_model_fields(model_class) - - # Combines parameters defined at the current level with parameters from parent components. Parameters at the current - # level take precedence - current_parameters = dict(copy.deepcopy(parent_parameters)) - component_parameters = propagated_component.pop(PARAMETERS_STR, {}) - current_parameters = {**current_parameters, **component_parameters} - - # Parameters should be applied to the current component fields with the existing field taking precedence over parameters if - # both exist - for parameter_key, parameter_value in current_parameters.items(): - if parameter_key in valid_fields: - propagated_component[parameter_key] = propagated_component.get(parameter_key) or parameter_value - - for field_name, field_value in propagated_component.items(): - if isinstance(field_value, dict): - # We exclude propagating a parameter that matches the current field name because that would result in an infinite cycle - excluded_parameter = current_parameters.pop(field_name, None) - parent_type_field_identifier = f"{propagated_component.get('type')}.{field_name}" - propagated_component[field_name] = self.propagate_types_and_parameters( - parent_type_field_identifier, field_value, current_parameters - ) - if excluded_parameter: - current_parameters[field_name] = excluded_parameter - elif isinstance(field_value, typing.List): - # We exclude propagating a parameter that matches the current field name because that would result in an infinite cycle - excluded_parameter = current_parameters.pop(field_name, None) - for i, element in enumerate(field_value): - if isinstance(element, dict): - parent_type_field_identifier = f"{propagated_component.get('type')}.{field_name}" - field_value[i] = self.propagate_types_and_parameters(parent_type_field_identifier, element, current_parameters) - if excluded_parameter: - current_parameters[field_name] = excluded_parameter - - return propagated_component - - @staticmethod - def _is_json_schema_object(propagated_component: Mapping[str, Any]) -> bool: - component_type = propagated_component.get("type") - if isinstance(component_type, list): - # Handle nullable types, ie ["null", "object"] - return "object" in component_type - return component_type == "object" diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_resolver.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_resolver.py deleted file mode 100644 index a6dbebffd7d0..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/manifest_resolver.py +++ /dev/null @@ -1,204 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import re -from typing import Any, Mapping, Set, Tuple, Union - -REF_TAG = "$ref" - - -class ManifestReferenceResolver: - """ - An incoming manifest can contain references to values previously defined. - This parser will dereference these values to produce a complete ConnectionDefinition. - - References can be defined using a #/ string. - ``` - key: 1234 - reference: "#/key" - ``` - will produce the following definition: - ``` - key: 1234 - reference: 1234 - ``` - This also works with objects: - ``` - key_value_pairs: - k1: v1 - k2: v2 - same_key_value_pairs: "#/key_value_pairs" - ``` - will produce the following definition: - ``` - key_value_pairs: - k1: v1 - k2: v2 - same_key_value_pairs: - k1: v1 - k2: v2 - ``` - - The $ref keyword can be used to refer to an object and enhance it with addition key-value pairs - ``` - key_value_pairs: - k1: v1 - k2: v2 - same_key_value_pairs: - $ref: "#/key_value_pairs" - k3: v3 - ``` - will produce the following definition: - ``` - key_value_pairs: - k1: v1 - k2: v2 - same_key_value_pairs: - k1: v1 - k2: v2 - k3: v3 - ``` - - References can also point to nested values. - Nested references are ambiguous because one could define a key containing with `.` - in this example, we want to refer to the limit key in the dict object: - ``` - dict: - limit: 50 - limit_ref: "#/dict/limit" - ``` - will produce the following definition: - ``` - dict - limit: 50 - limit-ref: 50 - ``` - - whereas here we want to access the `nested/path` value. - ``` - nested: - path: "first one" - nested/path: "uh oh" - value: "#/nested/path - ``` - will produce the following definition: - ``` - nested: - path: "first one" - nested/path: "uh oh" - value: "uh oh" - ``` - - to resolve the ambiguity, we try looking for the reference key at the top level, and then traverse the structs downward - until we find a key with the given path, or until there is nothing to traverse. - """ - - def preprocess_manifest(self, manifest: Mapping[str, Any]) -> Mapping[str, Any]: - """ - :param manifest: incoming manifest that could have references to previously defined components - :return: - """ - return self._evaluate_node(manifest, manifest, set()) # type: ignore[no-any-return] - - def _evaluate_node(self, node: Any, manifest: Mapping[str, Any], visited: Set[Any]) -> Any: # noqa: ANN401 - if isinstance(node, dict): - evaluated_dict = {k: self._evaluate_node(v, manifest, visited) for k, v in node.items() if not self._is_ref_key(k)} - if REF_TAG in node: - # The node includes a $ref key, so we splat the referenced value(s) into the evaluated dict - evaluated_ref = self._evaluate_node(node[REF_TAG], manifest, visited) - if not isinstance(evaluated_ref, dict): - return evaluated_ref - else: - # The values defined on the component take precedence over the reference values - return evaluated_ref | evaluated_dict - else: - return evaluated_dict - elif isinstance(node, list): - return [self._evaluate_node(v, manifest, visited) for v in node] - elif self._is_ref(node): - if node in visited: - raise ValueError(node) - visited.add(node) - ret = self._evaluate_node(self._lookup_ref_value(node, manifest), manifest, visited) - visited.remove(node) - return ret - else: - return node - - def _lookup_ref_value(self, ref: str, manifest: Mapping[str, Any]) -> Any: # noqa: ANN401 - ref_match = re.match(r"#/(.*)", ref) - if not ref_match: - raise ValueError(f"Invalid reference format {ref}") - try: - path = ref_match.groups()[0] - return self._read_ref_value(path, manifest) - except (AttributeError, KeyError, IndexError): - raise ValueError(f"{path}, {ref}") - - @staticmethod - def _is_ref(node: Any) -> bool: # noqa: ANN401 - return isinstance(node, str) and node.startswith("#/") - - @staticmethod - def _is_ref_key(key: str) -> bool: - return bool(key == REF_TAG) - - @staticmethod - def _read_ref_value(ref: str, manifest_node: Mapping[str, Any]) -> Any: # noqa: ANN401 - """ - Read the value at the referenced location of the manifest. - - References are ambiguous because one could define a key containing `/` - In this example, we want to refer to the `limit` key in the `dict` object: - dict: - limit: 50 - limit_ref: "#/dict/limit" - - Whereas here we want to access the `nested/path` value. - nested: - path: "first one" - nested/path: "uh oh" - value: "#/nested/path" - - To resolve the ambiguity, we try looking for the reference key at the top level, and then traverse the structs downward - until we find a key with the given path, or until there is nothing to traverse. - - Consider the path foo/bar/baz. To resolve the ambiguity, we first try 'foo/bar/baz' in its entirety as a top-level key. If this - fails, we try 'foo' as the top-level key, and if this succeeds, pass 'bar/baz' on as the key to be tried at the next level. - """ - while ref: - try: - return manifest_node[ref] - except (KeyError, TypeError): - head, ref = _parse_path(ref) - manifest_node = manifest_node[head] # type: ignore # Couldn't figure out how to fix this since manifest_node can get reassigned into other types like lists - return manifest_node - - -def _parse_path(ref: str) -> Tuple[Union[str, int], str]: - """ - Return the next path component, together with the rest of the path. - - A path component may be a string key, or an int index. - - >>> _parse_path("foo/bar") - "foo", "bar" - >>> _parse_path("foo/7/8/bar") - "foo", "7/8/bar" - >>> _parse_path("7/8/bar") - 7, "8/bar" - >>> _parse_path("8/bar") - 8, "bar" - >>> _parse_path("8foo/bar") - "8foo", "bar" - """ - match = re.match(r"([^/]*)/?(.*)", ref) - if match: - first, rest = match.groups() - try: - return int(first), rest - except ValueError: - return first, rest - else: - raise ValueError(f"Invalid path {ref} specified") diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/pipeline.py deleted file mode 100644 index 89e167de379e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/pipeline.py +++ /dev/null @@ -1,324 +0,0 @@ -# -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -# - -import json -import shutil -from pathlib import Path -from typing import Any - -import git # type: ignore -from anyio import Semaphore # type: ignore -from connector_ops.utils import ConnectorLanguage # type: ignore - -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.migrate_to_manifest_only.manifest_component_transformer import ManifestComponentTransformer -from pipelines.airbyte_ci.connectors.migrate_to_manifest_only.manifest_resolver import ManifestReferenceResolver -from pipelines.airbyte_ci.connectors.migrate_to_manifest_only.utils import ( - get_latest_base_image, - readme_for_connector, - remove_parameters_from_manifest, -) -from pipelines.airbyte_ci.connectors.reports import Report -from pipelines.helpers.connectors.command import run_connector_steps -from pipelines.helpers.connectors.yaml import read_yaml, write_yaml -from pipelines.helpers.execution.run_steps import STEP_TREE, StepToRun -from pipelines.models.steps import Step, StepResult, StepStatus - -## GLOBAL VARIABLES ## - -# spec.yaml and spec.json will be removed as part of the conversion. But if they are present, it's fine to convert still. -MANIFEST_ONLY_COMPATIBLE_FILES = [ - "manifest.yaml", - "components.py", - "run.py", - "__init__.py", - "source.py", - "spec.json", - "spec.yaml", - "__pycache__", -] -MANIFEST_ONLY_FILES_TO_KEEP = [ - "manifest.yaml", - "components.py", - "metadata.yaml", - "icon.svg", - "unit_tests", - "integration_tests", - "acceptance-test-config.yml", - "secrets", -] - - -## STEPS ## - - -class CheckIsManifestMigrationCandidate(Step): - """ - Pipeline step to check if the connector is a candidate for migration to manifest-only. - """ - - context: ConnectorContext - title: str = "Validate Manifest Migration Candidate" - airbyte_repo: git.Repo = git.Repo(search_parent_directories=True) - - async def _run(self) -> StepResult: - connector = self.context.connector - invalid_files: list = [] - - ## 1. Confirm the connector is low-code and not already manifest-only - if connector.language != ConnectorLanguage.LOW_CODE: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector is not a low-code connector.", - ) - - if connector.language == ConnectorLanguage.MANIFEST_ONLY: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="The connector is already in manifest-only format.", - ) - - ## 2. Detect invalid python files in the connector's source directory - for file in connector.python_source_dir_path.iterdir(): - if file.name not in MANIFEST_ONLY_COMPATIBLE_FILES: - invalid_files.append(file.name) - if invalid_files: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout=f"The connector has unrecognized source files: {', '.join(invalid_files)}", - ) - - ## 3. Detect connector class name to make sure it's inherited from source-declarative-manifest - # and does not override the `streams` method - connector_source_py = (connector.python_source_dir_path / "source.py").read_text() - - if "YamlDeclarativeSource" not in connector_source_py: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout="The connector does not use the YamlDeclarativeSource class.", - ) - - if "def streams" in connector_source_py: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout="The connector overrides the streams method.", - ) - - # All checks passed, the connector is a valid candidate for migration - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"{connector.technical_name} is a valid candidate for migration.") - - -class StripConnector(Step): - """ - Pipeline step to strip a low-code connector to manifest-only files. - """ - - context: ConnectorContext - title = "Strip Out Unnecessary Files" - - def _delete_directory_item(self, file: Path) -> None: - """ - Deletes the passed file or folder. - """ - self.logger.info(f"Deleting {file.name}") - try: - if file.is_dir(): - shutil.rmtree(file) - else: - file.unlink() - except Exception as e: - raise ValueError(f"Failed to delete {file.name}: {e}") - - def _check_if_non_inline_spec(self, path: Path) -> Path | None: - """ - Checks if a non-inline spec file exists and return its path. - """ - spec_file_yaml = path / "spec.yaml" - spec_file_json = path / "spec.json" - - if spec_file_yaml.exists(): - return spec_file_yaml - elif spec_file_json.exists(): - return spec_file_json - return None - - def _read_spec_from_file(self, spec_file: Path) -> dict: - """ - Grabs the relevant data from a non-inline spec, to be added to the manifest. - """ - try: - if spec_file.suffix == ".json": - with open(spec_file, "r") as file: - spec = json.load(file) - else: - spec = read_yaml(spec_file) - - documentation_url = spec.get("documentationUrl") or spec.get("documentation_url") - connection_specification = spec.get("connection_specification") or spec.get("connectionSpecification") - advanced_auth = spec.get("advanced_auth") - return { - "documentation_url": documentation_url, - "connection_specification": connection_specification, - "advanced_auth": advanced_auth, - } - - except Exception as e: - raise ValueError(f"Failed to read data in spec file: {e}") - - async def _run(self) -> StepResult: - connector = self.context.connector - - ## 1a. Move manifest.yaml to the root level of the directory - self.logger.info("Moving manifest to the root level of the directory") - root_manifest_path = connector.code_directory / "manifest.yaml" - connector.manifest_path.rename(root_manifest_path) - - ## 1b. Move components.py to the root level of the directory if it exists - components_path = connector.python_source_dir_path / "components.py" - if components_path.exists(): - self.logger.info("Custom components file found. Moving to the root level of the directory") - root_components_path = connector.manifest_only_components_path - components_path.rename(root_components_path) - - ## 2. Update the version in manifest.yaml - try: - manifest = read_yaml(root_manifest_path) - manifest["version"] = "5.15.0" - manifest["type"] = "DeclarativeSource" - - # Resolve $parameters and types with CDK magic - resolved_manifest = ManifestReferenceResolver().preprocess_manifest(manifest) - propagated_manifest = ManifestComponentTransformer().propagate_types_and_parameters("", resolved_manifest, {}) - cleaned_manifest = remove_parameters_from_manifest(propagated_manifest) - - write_yaml(cleaned_manifest, root_manifest_path) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=f"Failed to update version in manifest.yaml: {e}") - - ## 3. Check for non-inline spec files and add the data to manifest.yaml - spec_file = self._check_if_non_inline_spec(connector.python_source_dir_path) - if spec_file: - self.logger.info("Non-inline spec file found. Migrating spec to manifest") - try: - spec_data = self._read_spec_from_file(spec_file) - manifest = read_yaml(root_manifest_path) - - # Confirm the connector does not have both inline and non-inline specs - if "spec" in manifest: - return StepResult(step=self, status=StepStatus.FAILURE, stdout="Connector has both inline and non-inline specs.") - - manifest["spec"] = { - "type": "Spec", - "documentation_url": spec_data.get("documentation_url"), - "connection_specification": spec_data.get("connection_specification"), - "advanced_auth": spec_data.get("advanced_auth"), - } - write_yaml(manifest, root_manifest_path) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=f"Failed to add spec data to manifest.yaml: {e}") - - ## 4. Delete all non-essential files - try: - for item in connector.code_directory.iterdir(): - if item.name in MANIFEST_ONLY_FILES_TO_KEEP: - continue # Preserve the allowed files - else: - self._delete_directory_item(item) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=f"Failed to delete files: {e}") - - return StepResult(step=self, status=StepStatus.SUCCESS, stdout="The connector has been successfully stripped.") - - -class UpdateManifestOnlyFiles(Step): - """ - Pipeline step to update connector's metadata, acceptance-test-config and readme to manifest-only. - """ - - context: ConnectorContext - title = "Update Connector Metadata" - - async def _run(self) -> StepResult: - connector = self.context.connector - - ## 1. Update the acceptance test config to point to the right spec path - try: - acceptance_test_config_data = read_yaml(connector.acceptance_test_config_path) - # Handle legacy acceptance-test-config: - if "acceptance_tests" in acceptance_test_config_data: - acceptance_test_config_data["acceptance_tests"]["spec"]["tests"][0]["spec_path"] = "manifest.yaml" - else: - acceptance_test_config_data["tests"]["spec"][0]["spec_path"] = "manifest.yaml" - write_yaml(acceptance_test_config_data, connector.acceptance_test_config_path) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=f"Failed to update acceptance-test-config.yml: {e}") - - ## 2. Update the connector's metadata - self.logger.info("Updating metadata file") - try: - metadata = read_yaml(connector.metadata_file_path) - - # Remove any existing language tags and append the manifest-only tag - tags = metadata.get("data", {}).get("tags", []) - for tag in tags: - if "language:" in tag: - tags.remove(tag) - tags.append("language:manifest-only") - - pypi_package = metadata.get("data", {}).get("remoteRegistries", {}).get("pypi") - if pypi_package: - pypi_package["enabled"] = False - - # Update the base image - latest_base_image = get_latest_base_image("airbyte/source-declarative-manifest") - connector_base_image = metadata.get("data", {}).get("connectorBuildOptions") - connector_base_image["baseImage"] = latest_base_image - - # Write the changes to metadata.yaml - write_yaml(metadata, connector.metadata_file_path) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=f"Failed to update metadata.yaml: {e}") - - ## 3. Update the connector's README - self.logger.info("Updating README file") - readme = readme_for_connector(connector.technical_name) - - with open(connector.code_directory / "README.md", "w") as file: - file.write(readme) - - return StepResult(step=self, status=StepStatus.SUCCESS, stdout="The connector has been successfully migrated to manifest-only.") - - -## MAIN FUNCTION ## -async def run_connectors_manifest_only_pipeline(context: ConnectorContext, semaphore: "Semaphore", *args: Any) -> Report: - steps_to_run: STEP_TREE = [] - steps_to_run.append([StepToRun(id=CONNECTOR_TEST_STEP_ID.MANIFEST_ONLY_CHECK, step=CheckIsManifestMigrationCandidate(context))]) - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.MANIFEST_ONLY_STRIP, - step=StripConnector(context), - depends_on=[CONNECTOR_TEST_STEP_ID.MANIFEST_ONLY_CHECK], - ) - ] - ) - - steps_to_run.append( - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.MANIFEST_ONLY_UPDATE, - step=UpdateManifestOnlyFiles(context), - depends_on=[CONNECTOR_TEST_STEP_ID.MANIFEST_ONLY_STRIP], - ) - ] - ) - - return await run_connector_steps(context, semaphore, steps_to_run) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/templates/README.md.j2 b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/templates/README.md.j2 deleted file mode 100644 index 6c86747bc7ad..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/templates/README.md.j2 +++ /dev/null @@ -1,65 +0,0 @@ -# {{ source_name.capitalize().replace("-", " ") }} source connector - -This directory contains the manifest-only connector for `source-{{ source_name }}`. -This _manifest-only_ connector is not a Python package on its own, as it runs inside of the base `source-declarative-manifest` image. - -For information about how to configure and use this connector within Airbyte, see [the connector's full documentation](https://docs.airbyte.com/integrations/sources/{{source_name}}). - -## Local development - -We recommend using the Connector Builder to edit this connector. -Using either Airbyte Cloud or your local Airbyte OSS instance, navigate to the **Builder** tab and select **Import a YAML**. -Then select the connector's `manifest.yaml` file to load the connector into the Builder. You're now ready to make changes to the connector! - -If you prefer to develop locally, you can follow the instructions below. - -### Building the docker image - -You can build any manifest-only connector with `airbyte-ci`: - -1. Install [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md) -2. Run the following command to build the docker image: - -```bash -airbyte-ci connectors --name=source-{{source_name}} build -``` - -An image will be available on your host with the tag `airbyte/source-{{source_name}}:dev`. - -### Creating credentials - -**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.com/integrations/sources/{{source_name}}) -to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `spec` object in the connector's `manifest.yaml` file. -Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. - -### Running as a docker container - -Then run any of the standard source connector commands: - -```bash -docker run --rm airbyte/source-{{source_name}}:dev spec -docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-{{source_name}}:dev check --config /secrets/config.json -docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-{{source_name}}:dev discover --config /secrets/config.json -docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-{{source_name}}:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json -``` - -### Running the CI test suite - -You can run our full test suite locally using [`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md): - -```bash -airbyte-ci connectors --name=source-{{source_name}} test -``` - -## Publishing a new version of the connector - -If you want to contribute changes to `source-{{source_name}}`, here's how you can do that: -1. Make your changes locally, or load the connector's manifest into Connector Builder and make changes there. -2. Make sure your changes are passing our test suite with `airbyte-ci connectors --name=source-{{source_name}} test` -3. Bump the connector version (please follow [semantic versioning for connectors](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#semantic-versioning-for-connectors)): - - bump the `dockerImageTag` value in in `metadata.yaml` -4. Make sure the connector documentation and its changelog is up to date (`docs/integrations/sources/{{ source_name}}.md`). -5. Create a Pull Request: use [our PR naming conventions](https://docs.airbyte.com/contributing-to-airbyte/resources/pull-requests-handbook/#pull-request-title-convention). -6. Pat yourself on the back for being an awesome contributor. -7. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. -8. Once your PR is merged, the new version of the connector will be automatically published to Docker Hub and our connector registry. diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/utils.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/utils.py deleted file mode 100644 index 2336de957705..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/migrate_to_manifest_only/utils.py +++ /dev/null @@ -1,88 +0,0 @@ -# -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. -# - -import subprocess -from pathlib import Path -from typing import Any, List, Mapping - -import jinja2 -import requests - - -def readme_for_connector(name: str) -> str: - """ - Generate a manifest-only README.md file for a connector using a Jinja2 template. - """ - dir_path = Path(__file__).parent / "templates" - env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=str(dir_path))) - template = env.get_template("README.md.j2") - readme_name = name.replace("source-", "") - rendered = template.render(source_name=readme_name) - return rendered - - -def get_latest_base_image(image_name: str) -> str: - """ - Fetch the latest base image from Docker Hub for a given image name. - """ - base_url = "https://hub.docker.com/v2/repositories/" - - tags_url = f"{base_url}{image_name}/tags/?page_size=10&ordering=last_updated" - response = requests.get(tags_url) - if response.status_code != 200: - raise requests.ConnectionError(f"Error fetching tags: {response.status_code}") - - tags_data = response.json() - if not tags_data["results"]: - raise ValueError("No tags found for the image") - - # iterate through the tags to find the latest one that doesn't contain "dev" or "latest" - for tag in tags_data["results"]: - if "dev" not in tag["name"] and "latest" not in tag["name"]: - latest_tag = tag["name"] - break - - if not latest_tag: - raise ValueError("No valid tags found for the image") - - manifest_url = f"{base_url}{image_name}/tags/{latest_tag}" - response = requests.get(manifest_url) - if response.status_code != 200: - raise requests.ConnectionError(f"Error fetching manifest: {response.status_code}") - - manifest_data = response.json() - digest = manifest_data.get("digest") - - if not digest: - raise ValueError("No digest found for the image") - - full_reference = f"docker.io/{image_name}:{latest_tag}@{digest}" - return full_reference - - -def revert_connector_directory(directory: Path) -> None: - """ - Revert changes to a connector directory to the state at the last commit. - Used as a cleanup step in the manifest-only pipeline. - """ - try: - # Restore the directory to its state at the last commit - subprocess.run(["git", "restore", directory], check=True) - # Remove untracked files and directories - subprocess.run(["git", "clean", "-fd", directory], check=True) - except subprocess.CalledProcessError as e: - # Handle errors in the subprocess calls - print(f"An error occurred while reverting changes: {str(e)}") - - -def remove_parameters_from_manifest(d: dict | List | Mapping[str, Any]) -> dict | List: - """ - Takes a dictionary (or a list) of keys and removes all instances of the key "$parameters" from it. - """ - if isinstance(d, dict) or isinstance(d, Mapping): - return {k: remove_parameters_from_manifest(v) for k, v in d.items() if k != "$parameters"} - elif isinstance(d, list): - return [remove_parameters_from_manifest(item) for item in d] - else: - return d diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pipeline.py deleted file mode 100644 index 2ca0f8c55b08..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pipeline.py +++ /dev/null @@ -1,126 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups the functions to run full pipelines for connector testing.""" - -from __future__ import annotations - -import sys -from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, List, Optional - -import anyio -import dagger -from connector_ops.utils import ConnectorLanguage # type: ignore -from dagger import Config - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.publish.context import PublishConnectorContext -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.steps.no_op import NoOpStep -from pipelines.consts import ContextState -from pipelines.dagger.actions.system import docker -from pipelines.helpers.utils import create_and_open_file -from pipelines.models.reports import Report -from pipelines.models.steps import StepResult, StepStatus - -if TYPE_CHECKING: - from pipelines.models.contexts.pipeline_context import PipelineContext - -GITHUB_GLOBAL_CONTEXT = "[POC please ignore] Connectors CI" -GITHUB_GLOBAL_DESCRIPTION = "Running connectors tests" - -CONNECTOR_LANGUAGE_TO_FORCED_CONCURRENCY_MAPPING = { - # We run the Java connectors tests sequentially because we currently have memory issues when Java integration tests are run in parallel. - # See https://github.com/airbytehq/airbyte/issues/27168 - ConnectorLanguage.JAVA: anyio.Semaphore(1), -} - - -async def context_to_step_result(context: PipelineContext) -> StepResult: - if context.state == ContextState.SUCCESSFUL: - return await NoOpStep(context, StepStatus.SUCCESS).run() - - if context.state == ContextState.FAILURE: - return await NoOpStep(context, StepStatus.FAILURE).run() - - if context.state == ContextState.ERROR: - return await NoOpStep(context, StepStatus.FAILURE).run() - - raise ValueError(f"Could not convert context state: {context.state} to step status") - - -# HACK: This is to avoid wrapping the whole pipeline in a dagger pipeline to avoid instability just prior to launch -# TODO (ben): Refactor run_connectors_pipelines to wrap the whole pipeline in a dagger pipeline once Steps are refactored -async def run_report_complete_pipeline( - dagger_client: dagger.Client, - contexts: List[ConnectorContext] | List[PublishConnectorContext] | List[PipelineContext] | List[ConnectorTestContext], -) -> None: - """Create and Save a report representing the run of the encompassing pipeline. - - This is to denote when the pipeline is complete, useful for long running pipelines like nightlies. - """ - - if not contexts: - return - - # Repurpose the first context to be the pipeline upload context to preserve timestamps - first_connector_context = contexts[0] - - pipeline_name = f"Report upload {first_connector_context.report_output_prefix}" - first_connector_context.pipeline_name = pipeline_name - - # Transform contexts into a list of steps - steps_results = [await context_to_step_result(context) for context in contexts] - - report = Report( - name=pipeline_name, - pipeline_context=first_connector_context, - steps_results=steps_results, - filename="complete", - ) - - await report.save() - - -async def run_connectors_pipelines( - contexts: List[ConnectorContext] | List[PublishConnectorContext] | List[ConnectorTestContext], - connector_pipeline: Callable, - pipeline_name: str, - concurrency: int, - dagger_logs_path: Optional[Path], - execute_timeout: Optional[int], - *args: Any, -) -> List[ConnectorContext] | List[PublishConnectorContext] | List[ConnectorTestContext]: - """Run a connector pipeline for all the connector contexts.""" - - default_connectors_semaphore = anyio.Semaphore(concurrency) - dagger_logs_output = sys.stderr if not dagger_logs_path else create_and_open_file(dagger_logs_path) - async with dagger.Connection(Config(log_output=dagger_logs_output, execute_timeout=execute_timeout)) as dagger_client: - docker_hub_username = contexts[0].docker_hub_username - docker_hub_password = contexts[0].docker_hub_password - - if docker_hub_username and docker_hub_password: - dockerd_service = docker.with_global_dockerd_service(dagger_client, docker_hub_username, docker_hub_password) - else: - dockerd_service = docker.with_global_dockerd_service(dagger_client) - - await dockerd_service.start() - - async with anyio.create_task_group() as tg_connectors: - for context in contexts: - context.dagger_client = dagger_client - context.dockerd_service = dockerd_service - tg_connectors.start_soon( - connector_pipeline, - context, - CONNECTOR_LANGUAGE_TO_FORCED_CONCURRENCY_MAPPING.get(context.connector.language, default_connectors_semaphore), - *args, - ) - - # When the connectors pipelines are done, we can stop the dockerd service - await dockerd_service.stop() - await run_report_complete_pipeline(dagger_client, contexts) - - return contexts diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/commands.py deleted file mode 100644 index 67df171d49b3..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/commands.py +++ /dev/null @@ -1,212 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from typing import Callable, Dict, Iterable, List - -import asyncclick as click - -from pipelines import main_logger -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.airbyte_ci.connectors.publish.context import PublishConnectorContext, RolloutMode -from pipelines.airbyte_ci.connectors.publish.pipeline import ( - reorder_contexts, - run_connector_promote_pipeline, - run_connector_publish_pipeline, - run_connector_rollback_pipeline, -) -from pipelines.cli.click_decorators import click_ci_requirements_option -from pipelines.cli.confirm_prompt import confirm -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.cli.secrets import wrap_gcp_credentials_in_secret, wrap_in_secret -from pipelines.consts import DEFAULT_PYTHON_PACKAGE_REGISTRY_CHECK_URL, DEFAULT_PYTHON_PACKAGE_REGISTRY_URL, ContextState -from pipelines.helpers.connectors.modifed import ConnectorWithModifiedFiles -from pipelines.helpers.utils import fail_if_missing_docker_hub_creds -from pipelines.models.secrets import Secret - -ROLLOUT_MODE_TO_PIPELINE_FUNCTION: Dict[RolloutMode, Callable] = { - RolloutMode.PUBLISH: run_connector_publish_pipeline, - RolloutMode.PROMOTE: run_connector_promote_pipeline, - RolloutMode.ROLLBACK: run_connector_rollback_pipeline, -} - - -# Third-party connectors can't be published with this pipeline, skip them. -# This is not the same as partner connectors. Partner connectors use our tech stack and can -# be published just fine. Third-party connectors are in their own subdirectory. -def filter_out_third_party_connectors( - selected_connectors_with_modified_files: Iterable[ConnectorWithModifiedFiles], -) -> List[ConnectorWithModifiedFiles]: - """ - Return the list of connectors filtering out the connectors stored in connectors/third-party directory. - """ - filtered_connectors = [] - for connector in selected_connectors_with_modified_files: - if connector.is_third_party: - main_logger.info(f"Skipping third party connector {connector.technical_name} from the list of connectors") - else: - filtered_connectors.append(connector) - return filtered_connectors - - -@click.command(cls=DaggerPipelineCommand, help="Publish all images for the selected connectors.") -@click_ci_requirements_option() -@click.option("--pre-release/--main-release", help="Use this flag if you want to publish pre-release images.", default=True, type=bool) -@click.option( - "--spec-cache-gcs-credentials", - help="The service account key to upload files to the GCS bucket hosting spec cache.", - type=click.STRING, - required=True, - envvar="SPEC_CACHE_GCS_CREDENTIALS", - callback=wrap_gcp_credentials_in_secret, -) -@click.option( - "--spec-cache-bucket-name", - help="The name of the GCS bucket where specs will be cached.", - type=click.STRING, - required=True, - envvar="SPEC_CACHE_BUCKET_NAME", -) -@click.option( - "--metadata-service-gcs-credentials", - help="The service account key to upload files to the GCS bucket hosting the metadata files.", - type=click.STRING, - required=True, - envvar="METADATA_SERVICE_GCS_CREDENTIALS", - callback=wrap_gcp_credentials_in_secret, -) -@click.option( - "--metadata-service-bucket-name", - help="The name of the GCS bucket where metadata files will be uploaded.", - type=click.STRING, - required=True, - envvar="METADATA_SERVICE_BUCKET_NAME", -) -@click.option( - "--slack-webhook", - help="The Slack webhook URL to send notifications to.", - type=click.STRING, - envvar="SLACK_WEBHOOK", -) -@click.option( - "--python-registry-token", - help="Access token for python registry", - type=click.STRING, - envvar="PYTHON_REGISTRY_TOKEN", - callback=wrap_in_secret, -) -@click.option( - "--python-registry-url", - help="Which python registry url to publish to. If not set, the default pypi is used. For test pypi, use https://test.pypi.org/legacy/", - type=click.STRING, - default=DEFAULT_PYTHON_PACKAGE_REGISTRY_URL, - envvar="PYTHON_REGISTRY_URL", -) -@click.option( - "--python-registry-check-url", - help="Which url to check whether a certain version is published already. If not set, the default pypi is used. For test pypi, use https://test.pypi.org/pypi/", - type=click.STRING, - default=DEFAULT_PYTHON_PACKAGE_REGISTRY_CHECK_URL, - envvar="PYTHON_REGISTRY_CHECK_URL", -) -@click.option( - "--promote-release-candidate", - help="Promote a release candidate to a main release.", - type=click.BOOL, - default=False, - is_flag=True, -) -@click.option( - "--rollback-release-candidate", - help="Rollback a release candidate to a previous version.", - type=click.BOOL, - default=False, - is_flag=True, -) -@click.pass_context -async def publish( - ctx: click.Context, - pre_release: bool, - spec_cache_gcs_credentials: Secret, - spec_cache_bucket_name: str, - metadata_service_bucket_name: str, - metadata_service_gcs_credentials: Secret, - slack_webhook: str, - python_registry_token: Secret, - python_registry_url: str, - python_registry_check_url: str, - promote_release_candidate: bool, - rollback_release_candidate: bool, -) -> bool: - if promote_release_candidate and rollback_release_candidate: - raise click.UsageError("You can't promote and rollback a release candidate at the same time.") - elif promote_release_candidate: - rollout_mode = RolloutMode.PROMOTE - elif rollback_release_candidate: - rollout_mode = RolloutMode.ROLLBACK - else: - rollout_mode = RolloutMode.PUBLISH - - ctx.obj["selected_connectors_with_modified_files"] = filter_out_third_party_connectors( - ctx.obj["selected_connectors_with_modified_files"] - ) - if not ctx.obj["selected_connectors_with_modified_files"]: - return True - - if ctx.obj["is_local"]: - confirm( - "Publishing from a local environment is not recommended and requires to be logged in Airbyte's DockerHub registry, do you want to continue?", - abort=True, - ) - - fail_if_missing_docker_hub_creds(ctx) - - publish_connector_contexts = reorder_contexts( - [ - PublishConnectorContext( - connector=connector, - pre_release=pre_release, - spec_cache_gcs_credentials=spec_cache_gcs_credentials, - spec_cache_bucket_name=spec_cache_bucket_name, - metadata_service_gcs_credentials=metadata_service_gcs_credentials, - metadata_bucket_name=metadata_service_bucket_name, - docker_hub_username=Secret("docker_hub_username", ctx.obj["secret_stores"]["in_memory"]), - docker_hub_password=Secret("docker_hub_password", ctx.obj["secret_stores"]["in_memory"]), - slack_webhook=slack_webhook, - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - pull_request=ctx.obj.get("pull_request"), - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - use_local_cdk=ctx.obj.get("use_local_cdk"), - use_cdk_ref=ctx.obj.get("use_cdk_ref"), - python_registry_token=python_registry_token, - python_registry_url=python_registry_url, - python_registry_check_url=python_registry_check_url, - rollout_mode=rollout_mode, - ci_github_access_token=ctx.obj.get("ci_github_access_token"), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - ) - main_logger.warn("Concurrency is forced to 1. For stability reasons we disable parallel publish pipelines.") - ctx.obj["concurrency"] = 1 - - ran_publish_connector_contexts = await run_connectors_pipelines( - publish_connector_contexts, - ROLLOUT_MODE_TO_PIPELINE_FUNCTION[rollout_mode], - f"{rollout_mode.value} connectors", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - ) - return all(context.state is ContextState.SUCCESSFUL for context in ran_publish_connector_contexts) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/context.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/context.py deleted file mode 100644 index 8eb1dc271897..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/context.py +++ /dev/null @@ -1,164 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""Module declaring context related classes.""" - -from enum import Enum -from typing import List, Optional - -import asyncclick as click -from github import PullRequest - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.consts import PUBLISH_FAILURE_SLACK_CHANNEL, PUBLISH_UPDATES_SLACK_CHANNEL, ContextState -from pipelines.helpers.connectors.modifed import ConnectorWithModifiedFiles -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL_PREFIX -from pipelines.helpers.utils import format_duration -from pipelines.models.secrets import Secret - - -class RolloutMode(Enum): - ROLLBACK = "Rollback" - PUBLISH = "Publish" - PROMOTE = "Promote" - - -class PublishConnectorContext(ConnectorContext): - def __init__( - self, - connector: ConnectorWithModifiedFiles, - pre_release: bool, - spec_cache_gcs_credentials: Secret, - spec_cache_bucket_name: str, - metadata_service_gcs_credentials: Secret, - metadata_bucket_name: str, - docker_hub_username: Secret, - docker_hub_password: Secret, - ci_gcp_credentials: Secret, - slack_webhook: str, - ci_report_bucket: str, - report_output_prefix: str, - is_local: bool, - git_branch: str, - git_revision: str, - diffed_branch: str, - git_repo_url: str, - python_registry_url: str, - python_registry_check_url: str, - rollout_mode: RolloutMode, - gha_workflow_run_url: Optional[str] = None, - dagger_logs_url: Optional[str] = None, - pipeline_start_timestamp: Optional[int] = None, - ci_context: Optional[str] = None, - pull_request: Optional[PullRequest.PullRequest] = None, - s3_build_cache_access_key_id: Optional[Secret] = None, - s3_build_cache_secret_key: Optional[Secret] = None, - use_local_cdk: bool = False, - use_cdk_ref: Optional[str] = None, - python_registry_token: Optional[Secret] = None, - ci_github_access_token: Optional[Secret] = None, - ) -> None: - self.pre_release = pre_release - self.spec_cache_bucket_name = spec_cache_bucket_name - self.metadata_bucket_name = metadata_bucket_name - self.spec_cache_gcs_credentials = spec_cache_gcs_credentials - self.metadata_service_gcs_credentials = metadata_service_gcs_credentials - self.python_registry_token = python_registry_token - self.python_registry_url = python_registry_url - self.python_registry_check_url = python_registry_check_url - self.rollout_mode = rollout_mode - - pipeline_name = f"{rollout_mode.value} {connector.technical_name}" - pipeline_name = pipeline_name + " (pre-release)" if pre_release else pipeline_name - - if (use_local_cdk or use_cdk_ref) and not self.pre_release: - raise click.UsageError("Publishing with CDK overrides is only supported for pre-release publishing.") - - super().__init__( - pipeline_name=pipeline_name, - connector=connector, - report_output_prefix=report_output_prefix, - ci_report_bucket=ci_report_bucket, - is_local=is_local, - git_branch=git_branch, - git_revision=git_revision, - diffed_branch=diffed_branch, - git_repo_url=git_repo_url, - gha_workflow_run_url=gha_workflow_run_url, - dagger_logs_url=dagger_logs_url, - pipeline_start_timestamp=pipeline_start_timestamp, - ci_context=ci_context, - slack_webhook=slack_webhook, - ci_gcp_credentials=ci_gcp_credentials, - should_save_report=True, - use_local_cdk=use_local_cdk, - use_cdk_ref=use_cdk_ref, - docker_hub_username=docker_hub_username, - docker_hub_password=docker_hub_password, - s3_build_cache_access_key_id=s3_build_cache_access_key_id, - s3_build_cache_secret_key=s3_build_cache_secret_key, - ci_github_access_token=ci_github_access_token, - ) - - # Reassigning current class required instance attribute - # Which are optional in the super class - # for type checking - self.docker_hub_username: Secret = docker_hub_username - self.docker_hub_password: Secret = docker_hub_password - self.ci_gcp_credentials: Secret = ci_gcp_credentials - - @property - def pre_release_suffix(self) -> str: - return self.git_revision[:7] - - @property - def docker_image_tag(self) -> str: - # get the docker image tag from the parent class - metadata_tag = super().docker_image_tag - if self.pre_release: - return f"{metadata_tag}-preview.{self.pre_release_suffix}" - else: - return metadata_tag - - @property - def should_send_slack_message(self) -> bool: - should_send = super().should_send_slack_message - if not should_send: - return False - if self.pre_release: - return False - return True - - def get_slack_channels(self) -> List[str]: - if self.state in [ContextState.FAILURE, ContextState.ERROR]: - return [PUBLISH_UPDATES_SLACK_CHANNEL, PUBLISH_FAILURE_SLACK_CHANNEL] - else: - return [PUBLISH_UPDATES_SLACK_CHANNEL] - - def create_slack_message(self) -> str: - docker_hub_url = f"https://hub.docker.com/r/{self.connector.metadata['dockerRepository']}/tags" - message = f"*{self.rollout_mode.value} <{docker_hub_url}|{self.docker_image}>*\n" - if self.is_ci: - message += f"🤖 <{self.gha_workflow_run_url}|GitHub Action workflow>\n" - else: - message += "🧑‍💻 Local run\n" - message += f"*Connector:* {self.connector.technical_name}\n" - message += f"*Version:* {self.connector.version}\n" - branch_url = f"{AIRBYTE_GITHUB_REPO_URL_PREFIX}/tree/{self.git_branch}" - message += f"*Branch:* <{branch_url}|{self.git_branch}>\n" - commit_url = f"{AIRBYTE_GITHUB_REPO_URL_PREFIX}/commit/{self.git_revision}" - message += f"*Commit:* <{commit_url}|{self.git_revision[:10]}>\n" - if self.state in [ContextState.INITIALIZED, ContextState.RUNNING]: - message += "🟠" - if self.state is ContextState.SUCCESSFUL: - message += "🟢" - if self.state in [ContextState.FAILURE, ContextState.ERROR]: - message += "🔴" - message += f" {self.state.value['description']}\n" - if self.state is ContextState.SUCCESSFUL: - assert self.report is not None, "Report should be set when state is successful" - message += f"⏲️ Run duration: {format_duration(self.report.run_duration)}\n" - if self.state is ContextState.FAILURE: - message += "\ncc. " - return message diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/pipeline.py deleted file mode 100644 index 85836d67441d..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/publish/pipeline.py +++ /dev/null @@ -1,819 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -import os -import uuid -from datetime import datetime -from pathlib import Path -from typing import Dict, Iterable, List, Tuple - -import anyio -import semver -import yaml -from airbyte_protocol.models.airbyte_protocol import ConnectorSpecification # type: ignore -from auto_merge.consts import AUTO_MERGE_BYPASS_CI_CHECKS_LABEL # type: ignore -from connector_ops.utils import METADATA_FILE_NAME, ConnectorLanguage # type: ignore -from dagger import ( - Container, - Directory, - ExecError, - File, - ImageLayerCompression, - Platform, - QueryError, -) -from pydantic import BaseModel, ValidationError - -from pipelines import consts -from pipelines.airbyte_ci.connectors.build_image import steps -from pipelines.airbyte_ci.connectors.publish.context import PublishConnectorContext, RolloutMode -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.airbyte_ci.metadata.pipeline import MetadataUpload, MetadataValidation -from pipelines.airbyte_ci.steps.bump_version import SetConnectorVersion -from pipelines.airbyte_ci.steps.changelog import AddChangelogEntry -from pipelines.airbyte_ci.steps.pull_request import CreateOrUpdatePullRequest -from pipelines.airbyte_ci.steps.python_registry import ( - PublishToPythonRegistry, - PythonRegistryPublishContext, -) -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.dagger.actions.remote_storage import upload_to_gcs -from pipelines.dagger.actions.system import docker -from pipelines.helpers.connectors.dagger_fs import dagger_read_file, dagger_write_file -from pipelines.helpers.pip import is_package_published -from pipelines.models.steps import Step, StepModifyingFiles, StepResult, StepStatus - - -class InvalidSpecOutputError(Exception): - pass - - -class CheckConnectorImageDoesNotExist(Step): - context: PublishConnectorContext - title = "Check if the connector docker image does not exist on the registry." - - async def _run(self) -> StepResult: - docker_repository, docker_tag = self.context.docker_image.split(":") - crane_ls = ( - docker.with_crane( - self.context, - ) - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - .with_exec(["ls", docker_repository], use_entrypoint=True) - ) - try: - crane_ls_stdout = await crane_ls.stdout() - except ExecError as e: - if "NAME_UNKNOWN" in e.stderr: - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"The docker repository {docker_repository} does not exist.") - else: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=e.stderr, stdout=e.stdout) - else: # The docker repo exists and ls was successful - existing_tags = crane_ls_stdout.split("\n") - docker_tag_already_exists = docker_tag in existing_tags - if docker_tag_already_exists: - return StepResult(step=self, status=StepStatus.SKIPPED, stderr=f"{self.context.docker_image} already exists.") - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"No manifest found for {self.context.docker_image}.") - - -class CheckPythonRegistryPackageDoesNotExist(Step): - context: PythonRegistryPublishContext - title = "Check if the connector is published on python registry" - - async def _run(self) -> StepResult: - is_published = is_package_published( - self.context.package_metadata.name, self.context.package_metadata.version, self.context.registry_check_url - ) - if is_published: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr=f"{self.context.package_metadata.name} already exists in version {self.context.package_metadata.version}.", - ) - else: - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=f"{self.context.package_metadata.name} does not exist in version {self.context.package_metadata.version}.", - ) - - -class ConnectorDependenciesMetadata(BaseModel): - connector_technical_name: str - connector_repository: str - connector_version: str - connector_definition_id: str - dependencies: List[Dict[str, str]] - generation_time: datetime = datetime.utcnow() - - -class UploadDependenciesToMetadataService(Step): - context: PublishConnectorContext - title = "Upload connector dependencies list to GCS." - key_prefix = "connector_dependencies" - - async def _run(self, built_containers_per_platform: Dict[Platform, Container]) -> StepResult: - assert self.context.connector.language in [ - ConnectorLanguage.PYTHON, - ConnectorLanguage.LOW_CODE, - ], "This step can only run for Python connectors." - built_container = built_containers_per_platform[LOCAL_BUILD_PLATFORM] - pip_freeze_output = await built_container.with_exec(["pip", "freeze"]).stdout() - dependencies = [ - {"package_name": line.split("==")[0], "version": line.split("==")[1]} for line in pip_freeze_output.splitlines() if "==" in line - ] - connector_technical_name = self.context.connector.technical_name - connector_version = self.context.metadata["dockerImageTag"] - dependencies_metadata = ConnectorDependenciesMetadata( - connector_technical_name=connector_technical_name, - connector_repository=self.context.metadata["dockerRepository"], - connector_version=connector_version, - connector_definition_id=self.context.metadata["definitionId"], - dependencies=dependencies, - ).json() - file = ( - (await self.context.get_connector_dir()) - .with_new_file("dependencies.json", contents=dependencies_metadata) - .file("dependencies.json") - ) - key = f"{self.key_prefix}/{connector_technical_name}/{connector_version}/dependencies.json" - exit_code, stdout, stderr = await upload_to_gcs( - self.context.dagger_client, - file, - key, - self.context.metadata_bucket_name, - self.context.metadata_service_gcs_credentials, - flags=['--cache-control="no-cache"'], - ) - if exit_code != 0: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=stdout, stderr=stderr) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout="Uploaded connector dependencies to metadata service bucket.") - - -class PushConnectorImageToRegistry(Step): - context: PublishConnectorContext - title = "Push connector image to registry" - - @property - def latest_docker_image_name(self) -> str: - return f"{self.context.docker_repository}:latest" - - @property - def should_push_latest_tag(self) -> bool: - """ - We don't want to push the latest tag for release candidates or pre-releases. - - Returns: - bool: True if the latest tag should be pushed, False otherwise. - """ - is_release_candidate = "-rc" in self.context.connector.version - is_pre_release = self.context.pre_release - return not (is_release_candidate or is_pre_release) - - async def _run(self, built_containers_per_platform: List[Container], attempts: int = 3) -> StepResult: - try: - image_ref = await built_containers_per_platform[0].publish( - f"docker.io/{self.context.docker_image}", - platform_variants=built_containers_per_platform[1:], - forced_compression=ImageLayerCompression.Gzip, - ) - if self.should_push_latest_tag: - image_ref = await built_containers_per_platform[0].publish( - f"docker.io/{self.latest_docker_image_name}", - platform_variants=built_containers_per_platform[1:], - forced_compression=ImageLayerCompression.Gzip, - ) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"Published {image_ref}") - except QueryError as e: - if attempts > 0: - self.context.logger.error(str(e)) - self.context.logger.warn(f"Failed to publish {self.context.docker_image}. Retrying. {attempts} attempts left.") - await anyio.sleep(5) - return await self._run(built_containers_per_platform, attempts - 1) - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - -class PushVersionImageAsLatest(Step): - context: PublishConnectorContext - title = "Push existing version image as latest" - - @property - def latest_docker_image_name(self) -> str: - return f"{self.context.docker_repository}:latest" - - async def _run(self, attempts: int = 3) -> StepResult: - per_platform_containers = [ - self.context.dagger_client.container(platform=platform).from_(f"docker.io/{self.context.docker_image}") - for platform in consts.BUILD_PLATFORMS - ] - - try: - image_ref = await per_platform_containers[0].publish( - f"docker.io/{self.latest_docker_image_name}", - platform_variants=per_platform_containers[1:], - forced_compression=ImageLayerCompression.Gzip, - ) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"Published {image_ref}") - except QueryError as e: - if attempts > 0: - self.context.logger.error(str(e)) - self.context.logger.warn(f"Failed to publish {self.context.docker_image}. Retrying. {attempts} attempts left.") - await anyio.sleep(5) - return await self._run(attempts - 1) - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - -class PullConnectorImageFromRegistry(Step): - context: PublishConnectorContext - title = "Pull connector image from registry" - - async def check_if_image_only_has_gzip_layers(self) -> bool: - """Check if the image only has gzip layers. - Docker version > 21 can create images that has some layers compressed with zstd. - These layers are not supported by previous docker versions. - We want to make sure that the image we are about to release is compatible with all docker versions. - We use crane to inspect the manifest of the image and check if it only has gzip layers. - """ - has_only_gzip_layers = True - for platform in consts.BUILD_PLATFORMS: - inspect = docker.with_crane(self.context).with_exec( - ["manifest", "--platform", f"{str(platform)}", f"docker.io/{self.context.docker_image}"], use_entrypoint=True - ) - try: - inspect_stdout = await inspect.stdout() - except ExecError as e: - raise Exception(f"Failed to inspect {self.context.docker_image}: {e.stderr}") from e - try: - for layer in json.loads(inspect_stdout)["layers"]: - if not layer["mediaType"].endswith("gzip"): - has_only_gzip_layers = False - break - except (KeyError, json.JSONDecodeError) as e: - raise Exception(f"Failed to parse manifest for {self.context.docker_image}: {inspect_stdout}") from e - return has_only_gzip_layers - - async def _run(self, attempt: int = 3) -> StepResult: - try: - try: - await ( - self.context.dagger_client.container() - .from_(f"docker.io/{self.context.docker_image}") - .with_exec(["spec"], use_entrypoint=True) - ) - except ExecError: - if attempt > 0: - await anyio.sleep(10) - return await self._run(attempt - 1) - else: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=f"Failed to pull {self.context.docker_image}") - if not await self.check_if_image_only_has_gzip_layers(): - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=f"Image {self.context.docker_image} does not only have gzip compressed layers. Please rebuild the connector with Docker < 21.", - ) - else: - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=f"Pulled {self.context.docker_image} and validated it has gzip only compressed layers and we can run spec on it.", - ) - except QueryError as e: - if attempt > 0: - await anyio.sleep(10) - return await self._run(attempt - 1) - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - -class UploadSpecToCache(Step): - context: PublishConnectorContext - title = "Upload connector spec to spec cache bucket" - default_spec_file_name = "spec.json" - cloud_spec_file_name = "spec.cloud.json" - - @property - def spec_key_prefix(self) -> str: - return "specs/" + self.context.docker_image.replace(":", "/") - - @property - def cloud_spec_key(self) -> str: - return f"{self.spec_key_prefix}/{self.cloud_spec_file_name}" - - @property - def oss_spec_key(self) -> str: - return f"{self.spec_key_prefix}/{self.default_spec_file_name}" - - def _parse_spec_output(self, spec_output: str) -> str: - parsed_spec_message = None - for line in spec_output.split("\n"): - try: - parsed_json = json.loads(line) - if parsed_json["type"] == "SPEC": - parsed_spec_message = parsed_json - break - except (json.JSONDecodeError, KeyError): - continue - if parsed_spec_message: - parsed_spec = parsed_spec_message["spec"] - try: - ConnectorSpecification.parse_obj(parsed_spec) - return json.dumps(parsed_spec) - except (ValidationError, ValueError) as e: - raise InvalidSpecOutputError(f"The SPEC message did not pass schema validation: {str(e)}.") - raise InvalidSpecOutputError("No spec found in the output of the SPEC command.") - - async def _get_connector_spec(self, connector: Container, deployment_mode: str) -> str: - """ - Get the connector spec by running the `spec` command in the connector container. - - Args: - connector (Container): The connector container. - deployment_mode (str): The deployment mode to run the spec command in. Valid values are "OSS" and "CLOUD". - """ - spec_output = ( - await connector.with_env_variable("DEPLOYMENT_MODE", deployment_mode).with_exec(["spec"], use_entrypoint=True).stdout() - ) - return self._parse_spec_output(spec_output) - - async def _get_spec_as_file(self, spec: str, name: str = "spec_to_cache.json") -> File: - return (await self.context.get_connector_dir()).with_new_file(name, contents=spec).file(name) - - async def _run(self, built_connector: Container) -> StepResult: - try: - oss_spec: str = await self._get_connector_spec(built_connector, "OSS") - cloud_spec: str = await self._get_connector_spec(built_connector, "CLOUD") - except InvalidSpecOutputError as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - specs_to_uploads: List[Tuple[str, File]] = [(self.oss_spec_key, await self._get_spec_as_file(oss_spec))] - - if oss_spec != cloud_spec: - specs_to_uploads.append((self.cloud_spec_key, await self._get_spec_as_file(cloud_spec, "cloud_spec_to_cache.json"))) - - for key, file in specs_to_uploads: - exit_code, stdout, stderr = await upload_to_gcs( - self.context.dagger_client, - file, - key, - self.context.spec_cache_bucket_name, - self.context.spec_cache_gcs_credentials, - flags=['--cache-control="no-cache"'], - ) - if exit_code != 0: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=stdout, stderr=stderr) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout="Uploaded connector spec to spec cache bucket.") - - -class UploadSbom(Step): - context: PublishConnectorContext - title = "Upload SBOM to metadata service bucket" - SBOM_KEY_PREFIX = "sbom" - SYFT_DOCKER_IMAGE = "anchore/syft:v1.6.0" - SBOM_FORMAT = "spdx-json" - IN_CONTAINER_SBOM_PATH = "sbom.json" - SBOM_EXTENSION = "spdx.json" - - def get_syft_container(self) -> Container: - home_dir = os.path.expanduser("~") - config_path = os.path.join(home_dir, ".docker", "config.json") - config_file = self.dagger_client.host().file(config_path) - return ( - self.dagger_client.container() - .from_(self.SYFT_DOCKER_IMAGE) - .with_mounted_file("/config/config.json", config_file) - .with_env_variable("DOCKER_CONFIG", "/config") - # Syft requires access to the docker daemon. We share the host's docker socket with the Syft container. - .with_unix_socket("/var/run/docker.sock", self.dagger_client.host().unix_socket("/var/run/docker.sock")) - ) - - async def _run(self) -> StepResult: - try: - syft_container = self.get_syft_container() - sbom_file = await syft_container.with_exec( - [self.context.docker_image, "-o", f"{self.SBOM_FORMAT}={self.IN_CONTAINER_SBOM_PATH}"], use_entrypoint=True - ).file(self.IN_CONTAINER_SBOM_PATH) - except ExecError as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e), exc_info=e) - - # This will lead to a key like: sbom/airbyte/source-faker/0.1.0.json - key = f"{self.SBOM_KEY_PREFIX}/{self.context.docker_image.replace(':', '/')}.{self.SBOM_EXTENSION}" - exit_code, stdout, stderr = await upload_to_gcs( - self.context.dagger_client, - sbom_file, - key, - self.context.metadata_bucket_name, - self.context.metadata_service_gcs_credentials, - flags=['--cache-control="no-cache"', "--content-type=application/json"], - ) - if exit_code != 0: - return StepResult(step=self, status=StepStatus.FAILURE, stdout=stdout, stderr=stderr) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout="Uploaded SBOM to metadata service bucket.") - - -class SetPromotedVersion(SetConnectorVersion): - context: PublishConnectorContext - title = "Promote release candidate" - - @property - def current_semver_version(self) -> semver.Version: - return semver.Version.parse(self.context.connector.version) - - @property - def promoted_semver_version(self) -> semver.Version: - return self.current_semver_version.replace(prerelease=None) - - @property - def promoted_version(self) -> str: - return str(self.promoted_semver_version) - - @property - def current_version_is_rc(self) -> bool: - return bool(self.current_semver_version.prerelease and "rc" in self.current_semver_version.prerelease) - - def __init__(self, context: PublishConnectorContext, connector_directory: Directory) -> None: - self.context = context - super().__init__(context, connector_directory, self.promoted_version) - - async def _run(self) -> StepResult: - if not self.current_version_is_rc: - return StepResult(step=self, status=StepStatus.SKIPPED, stdout="The connector version has no rc suffix.") - return await super()._run() - - -class DisableProgressiveRollout(StepModifyingFiles): - context: PublishConnectorContext - title = "Disable progressive rollout in metadata file" - - async def _run(self) -> StepResult: - raw_metadata = await dagger_read_file(await self.context.get_connector_dir(include=[METADATA_FILE_NAME]), METADATA_FILE_NAME) - current_metadata = yaml.safe_load(raw_metadata) - enable_progressive_rollout = ( - current_metadata.get("data", {}).get("releases", {}).get("rolloutConfiguration", {}).get("enableProgressiveRollout", False) - ) - if not enable_progressive_rollout: - return StepResult(step=self, status=StepStatus.SKIPPED, stdout="Progressive rollout is already disabled.") - # We do an in-place replacement instead of serializing back to yaml to preserve comments and formatting. - new_raw_metadata = raw_metadata.replace("enableProgressiveRollout: true", "enableProgressiveRollout: false") - self.modified_directory = dagger_write_file(self.modified_directory, METADATA_FILE_NAME, new_raw_metadata) - self.modified_files.append(METADATA_FILE_NAME) - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout="Set enableProgressiveRollout to false in connector metadata.", - output=self.modified_directory, - ) - - -# Helpers -def create_connector_report(results: List[StepResult], context: PublishConnectorContext) -> ConnectorReport: - """Generate a connector report from results and assign it to the context. - - Args: - results (List[StepResult]): List of step results. - context (PublishConnectorContext): The connector context to assign the report to. - - Returns: - ConnectorReport: The connector report. - """ - report = ConnectorReport(context, results, name="PUBLISH RESULTS") - context.report = report - return report - - -# Pipeline -async def run_connector_publish_pipeline(context: PublishConnectorContext, semaphore: anyio.Semaphore) -> ConnectorReport: - """Run a publish pipeline for a single connector. - - 1. Validate the metadata file. - 2. Check if the connector image already exists. - 3. Build the connector, with platform variants. - 4. Push the connector to DockerHub, with platform variants. - 5. Upload its spec to the spec cache bucket. - 6. Upload its metadata file to the metadata service bucket. - - Returns: - ConnectorReport: The reports holding publish results. - """ - - assert context.rollout_mode == RolloutMode.PUBLISH, "This pipeline can only run in publish mode." - - metadata_upload_step = MetadataUpload( - context=context, - metadata_service_gcs_credentials=context.metadata_service_gcs_credentials, - docker_hub_username=context.docker_hub_username, - docker_hub_password=context.docker_hub_password, - metadata_bucket_name=context.metadata_bucket_name, - pre_release=context.pre_release, - pre_release_tag=context.docker_image_tag, - ) - - upload_spec_to_cache_step = UploadSpecToCache(context) - - upload_sbom_step = UploadSbom(context) - - async with semaphore: - async with context: - results = [] - - # Check if the connector image is already published to the registry. - check_connector_image_results = await CheckConnectorImageDoesNotExist(context).run() - results.append(check_connector_image_results) - - python_registry_steps, terminate_early = await _run_python_registry_publish_pipeline(context) - results.extend(python_registry_steps) - - if terminate_early: - return create_connector_report(results, context) - - # If the connector image already exists, we don't need to build it, but we still need to upload the metadata file. - # We also need to upload the spec to the spec cache bucket. - # For pre-releases, rebuild all the time. - if check_connector_image_results.status is StepStatus.SKIPPED and not context.pre_release: - context.logger.info( - "The connector version is already published. Let's upload metadata.yaml and spec to GCS even if no version bump happened." - ) - already_published_connector = context.dagger_client.container().from_(context.docker_image) - upload_to_spec_cache_results = await upload_spec_to_cache_step.run(already_published_connector) - results.append(upload_to_spec_cache_results) - if upload_to_spec_cache_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - upload_sbom_results = await upload_sbom_step.run() - results.append(upload_sbom_results) - if upload_sbom_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - metadata_upload_results = await metadata_upload_step.run() - results.append(metadata_upload_results) - - # Exit early if the connector image already exists - if check_connector_image_results.status is not StepStatus.SUCCESS and not context.pre_release: - return create_connector_report(results, context) - - build_connector_results = await steps.run_connector_build(context) - results.append(build_connector_results) - - # Exit early if the connector image failed to build - if build_connector_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - if context.connector.language in [ConnectorLanguage.PYTHON, ConnectorLanguage.LOW_CODE]: - upload_dependencies_step = await UploadDependenciesToMetadataService(context).run(build_connector_results.output) - results.append(upload_dependencies_step) - - built_connector_platform_variants = list(build_connector_results.output.values()) - push_connector_image_results = await PushConnectorImageToRegistry(context).run(built_connector_platform_variants) - results.append(push_connector_image_results) - - # Exit early if the connector image failed to push - if push_connector_image_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - # Make sure the image published is healthy by pulling it and running SPEC on it. - # See https://github.com/airbytehq/airbyte/issues/26085 - pull_connector_image_results = await PullConnectorImageFromRegistry(context).run() - results.append(pull_connector_image_results) - - # Exit early if the connector image failed to pull - if pull_connector_image_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - upload_to_spec_cache_results = await upload_spec_to_cache_step.run(built_connector_platform_variants[0]) - results.append(upload_to_spec_cache_results) - if upload_to_spec_cache_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - upload_sbom_results = await upload_sbom_step.run() - results.append(upload_sbom_results) - if upload_sbom_results.status is not StepStatus.SUCCESS: - return create_connector_report(results, context) - - metadata_upload_results = await metadata_upload_step.run() - results.append(metadata_upload_results) - connector_report = create_connector_report(results, context) - return connector_report - - -async def _run_python_registry_publish_pipeline(context: PublishConnectorContext) -> Tuple[List[StepResult], bool]: - """ - Run the python registry publish pipeline for a single connector. - Return the results of the steps and a boolean indicating whether there was an error and the pipeline should be stopped. - """ - results: List[StepResult] = [] - # Try to convert the context to a PythonRegistryPublishContext. If it returns None, it means we don't need to publish to a python registry. - python_registry_context = await PythonRegistryPublishContext.from_publish_connector_context(context) - if not python_registry_context: - return results, False - - if not context.python_registry_token or not context.python_registry_url: - # If the python registry token or url are not set, we can't publish to the python registry - stop the pipeline. - return [ - StepResult( - step=PublishToPythonRegistry(python_registry_context), - status=StepStatus.FAILURE, - stderr="Pypi publishing is enabled, but python registry token or url are not set.", - ) - ], True - - check_python_registry_package_exists_results = await CheckPythonRegistryPackageDoesNotExist(python_registry_context).run() - results.append(check_python_registry_package_exists_results) - if check_python_registry_package_exists_results.status is StepStatus.SKIPPED: - context.logger.info("The connector version is already published on python registry.") - elif check_python_registry_package_exists_results.status is StepStatus.SUCCESS: - context.logger.info("The connector version is not published on python registry. Let's build and publish it.") - publish_to_python_registry_results = await PublishToPythonRegistry(python_registry_context).run() - results.append(publish_to_python_registry_results) - if publish_to_python_registry_results.status is StepStatus.FAILURE: - return results, True - elif check_python_registry_package_exists_results.status is StepStatus.FAILURE: - return results, True - - return results, False - - -def get_rollback_pr_creation_arguments( - modified_files: Iterable[Path], - context: PublishConnectorContext, - step_results: Iterable[StepResult], - release_candidate_version: str, -) -> Tuple[Tuple, Dict]: - return ( - (modified_files,), - { - "branch_id": f"{context.connector.technical_name}/rollback-{release_candidate_version}", - "commit_message": "[auto-publish] " # << We can skip Vercel builds if this is in the commit message - + "; ".join(step_result.step.title for step_result in step_results if step_result.success), - "pr_title": f"revert({context.connector.technical_name}): 🐙 stop progressive rollout for {release_candidate_version}", - "pr_body": f"The release candidate version {release_candidate_version} has been deemed unstable. This PR stops its progressive rollout. This PR will be automatically merged as part of the `auto-merge` workflow. This workflow runs every 2 hours.", - }, - ) - - -async def run_connector_rollback_pipeline(context: PublishConnectorContext, semaphore: anyio.Semaphore) -> ConnectorReport: - """Run a rollback pipeline for a single connector. - - 1. Disable progressive rollout in metadata file. - 2. Open a PR with the updated metadata, set the auto-merge label. - - Returns: - ConnectorReport: The reports holding promote results. - """ - - results = [] - current_version = context.connector.version - all_modified_files = set() - async with semaphore: - async with context: - assert context.rollout_mode == RolloutMode.ROLLBACK, "This pipeline can only run in rollback mode." - original_connector_directory = await context.get_connector_dir() - - # Disable progressive rollout in metadata file - reset_release_candidate = DisableProgressiveRollout(context, original_connector_directory) - reset_release_candidate_results = await reset_release_candidate.run() - results.append(reset_release_candidate_results) - if reset_release_candidate_results.success: - all_modified_files.update(await reset_release_candidate.export_modified_files(context.connector.code_directory)) - - if not all([result.success for result in results]): - context.logger.error("The metadata update failed. Skipping PR creation.") - connector_report = create_connector_report(results, context) - return connector_report - - # Open PR when all previous steps are successful - initial_pr_creation = CreateOrUpdatePullRequest( - context, - # We will merge even if the CI checks fail, due to this label: - labels=[AUTO_MERGE_BYPASS_CI_CHECKS_LABEL, "rollback-rc"], - # Let GitHub auto-merge this if all checks pass before the next run - # of our auto-merge workflow: - github_auto_merge=True, - # Don't skip CI, as it prevents the PR from auto-merging naturally: - skip_ci=False, - ) - pr_creation_args, pr_creation_kwargs = get_rollback_pr_creation_arguments(all_modified_files, context, results, current_version) - initial_pr_creation_result = await initial_pr_creation.run(*pr_creation_args, **pr_creation_kwargs) - results.append(initial_pr_creation_result) - - connector_report = create_connector_report(results, context) - return connector_report - - -def get_promotion_pr_creation_arguments( - modified_files: Iterable[Path], - context: PublishConnectorContext, - step_results: Iterable[StepResult], - release_candidate_version: str, - promoted_version: str, -) -> Tuple[Tuple, Dict]: - return ( - (modified_files,), - { - "branch_id": f"{context.connector.technical_name}/{promoted_version}", - "commit_message": "[auto-publish] " # << We can skip Vercel builds if this is in the commit message - + "; ".join(step_result.step.title for step_result in step_results if step_result.success), - "pr_title": f"release({context.connector.technical_name}): 🐙 promote {promoted_version}", - "pr_body": f"The release candidate version {release_candidate_version} has been deemed stable and is now ready to be promoted to an official release ({promoted_version}). This PR will be automatically merged as part of the `auto-merge` workflow. This workflow runs every 2 hours.", - }, - ) - - -async def run_connector_promote_pipeline(context: PublishConnectorContext, semaphore: anyio.Semaphore) -> ConnectorReport: - """Run a promote pipeline for a single connector. - - 1. Update connector metadata to: - * Remove the RC suffix from the version. - * Disable progressive rollout. - 2. Open a PR with the updated metadata. - 3. Add a changelog entry to the documentation. - 4. Update the PR with the updated changelog, set the auto-merge label. - - Returns: - ConnectorReport: The reports holding promote results. - """ - - results = [] - current_version = context.connector.version - all_modified_files = set() - async with semaphore: - async with context: - assert context.rollout_mode == RolloutMode.PROMOTE, "This pipeline can only run in promote mode." - original_connector_directory = await context.get_connector_dir() - # Remove RC suffix - set_promoted_version = SetPromotedVersion(context, original_connector_directory) - set_promoted_version_results = await set_promoted_version.run() - results.append(set_promoted_version_results) - if set_promoted_version_results.success: - all_modified_files.update(await set_promoted_version.export_modified_files(context.connector.code_directory)) - - # Disable progressive rollout in metadata file - reset_release_candidate = DisableProgressiveRollout(context, set_promoted_version_results.output) - reset_release_candidate_results = await reset_release_candidate.run() - results.append(reset_release_candidate_results) - if reset_release_candidate_results.success: - all_modified_files.update(await reset_release_candidate.export_modified_files(context.connector.code_directory)) - - if not all([result.success for result in results]): - context.logger.error("The metadata update failed. Skipping PR creation.") - connector_report = create_connector_report(results, context) - return connector_report - - # Open PR when all previous steps are successful - promoted_version = set_promoted_version.promoted_version - initial_pr_creation = CreateOrUpdatePullRequest(context, skip_ci=False) - pr_creation_args, pr_creation_kwargs = get_promotion_pr_creation_arguments( - all_modified_files, context, results, current_version, promoted_version - ) - initial_pr_creation_result = await initial_pr_creation.run(*pr_creation_args, **pr_creation_kwargs) - results.append(initial_pr_creation_result) - # Update changelog and update PR - if initial_pr_creation_result.success: - created_pr = initial_pr_creation_result.output - documentation_directory = await context.get_repo_dir( - include=[str(context.connector.local_connector_documentation_directory)] - ).directory(str(context.connector.local_connector_documentation_directory)) - add_changelog_entry = AddChangelogEntry( - context, - documentation_directory, - promoted_version, - f"Promoting release candidate {current_version} to a main version.", - created_pr.number, - ) - add_changelog_entry_result = await add_changelog_entry.run() - results.append(add_changelog_entry_result) - if add_changelog_entry_result.success: - all_modified_files.update( - await add_changelog_entry.export_modified_files(context.connector.local_connector_documentation_directory) - ) - post_changelog_pr_update = CreateOrUpdatePullRequest( - context, - skip_ci=False, # Don't skip CI, as it prevents the PR from auto-merging naturally. - # We will merge even if the CI checks fail, due to the "bypass-ci-checks" label: - labels=[AUTO_MERGE_BYPASS_CI_CHECKS_LABEL, "promoted-rc"], - github_auto_merge=True, # Let GitHub auto-merge this if/when all required checks have passed. - ) - pr_creation_args, pr_creation_kwargs = get_promotion_pr_creation_arguments( - all_modified_files, context, results, current_version, promoted_version - ) - post_changelog_pr_update_result = await post_changelog_pr_update.run(*pr_creation_args, **pr_creation_kwargs) - results.append(post_changelog_pr_update_result) - - connector_report = create_connector_report(results, context) - return connector_report - - -def reorder_contexts(contexts: List[PublishConnectorContext]) -> List[PublishConnectorContext]: - """Reorder contexts so that the ones that are for strict-encrypt/secure connectors come first. - The metadata upload on publish checks if the the connectors referenced in the metadata file are already published to DockerHub. - Non strict-encrypt variant reference the strict-encrypt variant in their metadata file for cloud. - So if we publish the non strict-encrypt variant first, the metadata upload will fail if the strict-encrypt variant is not published yet. - As strict-encrypt variant are often modified in the same PR as the non strict-encrypt variant, we want to publish them first. - """ - - def is_secure_variant(context: PublishConnectorContext) -> bool: - SECURE_VARIANT_KEYS = ["secure", "strict-encrypt"] - return any(key in context.connector.technical_name for key in SECURE_VARIANT_KEYS) - - return sorted(contexts, key=lambda context: (is_secure_variant(context), context.connector.technical_name), reverse=True) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/commands.py deleted file mode 100644 index 6237ffdc1786..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/commands.py +++ /dev/null @@ -1,82 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.pull_request.pipeline import run_connector_pull_request_pipeline -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.helpers.connectors.command import run_connector_pipeline - - -@click.command( - cls=DaggerPipelineCommand, - short_help="Create a pull request for changed files in the connector repository.", -) -@click.option( - "-m", - "--message", - help="Commit message and pull request title and changelog (if enabled).", - type=str, - required=True, -) -@click.option( - "-b", - "--branch_id", - help="update a branch named / instead generating one from the message.", - type=str, - required=True, -) -@click.option( - "--report", - is_flag=True, - type=bool, - default=False, - help="Auto open report browser.", -) -@click.option( - "--title", - help="Title of the PR to be created or edited (optional - defaults to message or no change).", - type=str, - required=False, -) -@click.option( - "--body", - help="Body of the PR to be created or edited (optional - defaults to empty or not change).", - type=str, - required=False, -) -@click.option( - "--changelog", - help="Add message to the changelog for this version.", - type=bool, - is_flag=True, - required=False, - default=False, -) -@click.option( - "--bump", - help="Bump the metadata.yaml version. Can be `major`, `minor`, or `patch`.", - type=click.Choice(["patch", "minor", "major"]), - required=False, - default=None, -) -@click.pass_context -async def pull_request( - ctx: click.Context, message: str, branch_id: str, report: bool, title: str, body: str, changelog: bool, bump: str | None -) -> bool: - if not ctx.obj["ci_github_access_token"]: - raise click.ClickException( - "GitHub access token is required to create or simulate a pull request. Set the CI_GITHUB_ACCESS_TOKEN environment variable." - ) - return await run_connector_pipeline( - ctx, - "Create pull request", - report, - run_connector_pull_request_pipeline, - message, - branch_id, - title, - body, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/pipeline.py deleted file mode 100644 index 7ff2a1eb0ec4..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/pull_request/pipeline.py +++ /dev/null @@ -1,108 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Set - -from pipelines import main_logger -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.reports import ConnectorReport, Report -from pipelines.airbyte_ci.steps.pull_request import CreateOrUpdatePullRequest -from pipelines.consts import CIContext -from pipelines.helpers.git import get_modified_files -from pipelines.helpers.utils import transform_strs_to_paths - -if TYPE_CHECKING: - from anyio import Semaphore - - -## HELPER FUNCTIONS -async def get_connector_changes(context: ConnectorContext) -> Set[Path]: - logger = main_logger - all_modified_files = set( - transform_strs_to_paths( - await get_modified_files( - context.git_branch, - context.git_revision, - context.diffed_branch, - context.is_local, - CIContext(context.ci_context), - context.git_repo_url, - ) - ) - ) - - directory = context.connector.code_directory - logger.info(f"Filtering to changes in {directory}") - # get a list of files that are a child of this path - connector_files = set([file for file in all_modified_files if directory in file.parents]) - # get doc too - doc_path = context.connector.documentation_file_path - - if doc_path in all_modified_files: - connector_files.add(doc_path) - - return connector_files - - -def replace_placeholder_with_pr_number(context: ConnectorContext, pr_number: int) -> Set[Path]: - current_doc = context.connector.documentation_file_path.read_text() - updated_doc = current_doc.replace("*PR_NUMBER_PLACEHOLDER*", str(pr_number)) - context.connector.documentation_file_path.write_text(updated_doc) - return {context.connector.documentation_file_path} - - -async def run_connector_pull_request_pipeline( - context: ConnectorContext, - semaphore: "Semaphore", - message: str, - branch_id: str, - title: str | None = None, - body: str | None = None, -) -> Report | ConnectorReport | None: - title = title or message - body = body or "" - async with semaphore: - async with context: - step_results = [] - modified_files = await get_connector_changes(context) - - create_or_update_pull_request = CreateOrUpdatePullRequest(context, skip_ci=True) - - if not modified_files: - step_results.append(create_or_update_pull_request.skip("No changes detected in the connector directory.")) - context.report = ConnectorReport(context, step_results, name="PULL REQUEST") - return context.report - - create_or_update_pull_request_result = await create_or_update_pull_request.run( - modified_files, - branch_id, - message, - title, - body, - ) - step_results.append(create_or_update_pull_request_result) - - if not create_or_update_pull_request_result.success: - return ConnectorReport( - context, - step_results, - name="PULL REQUEST", - ) - - created_pr = create_or_update_pull_request_result.output - modified_files.update(replace_placeholder_with_pr_number(context, created_pr.number)) - update_pull_request = CreateOrUpdatePullRequest(context, skip_ci=False) - update_pull_request_result = await update_pull_request.run( - modified_files, - branch_id, - message, - title, - body, - ) - step_results.append(update_pull_request_result) - - context.report = ConnectorReport(context, step_results, name="PULL REQUEST") - return context.report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/reports.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/reports.py deleted file mode 100644 index 1447a10fcee8..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/reports.py +++ /dev/null @@ -1,195 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -import webbrowser -from dataclasses import dataclass -from pathlib import Path -from types import MappingProxyType -from typing import TYPE_CHECKING, Dict - -from connector_ops.utils import console # type: ignore -from jinja2 import Environment, PackageLoader, select_autoescape -from rich.console import Group -from rich.panel import Panel -from rich.style import Style -from rich.table import Table -from rich.text import Text - -from pipelines.consts import GCS_PUBLIC_DOMAIN -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL_PREFIX, AIRBYTE_GITHUBUSERCONTENT_URL_PREFIX -from pipelines.helpers.utils import format_duration -from pipelines.models.artifacts import Artifact -from pipelines.models.reports import Report -from pipelines.models.steps import StepStatus - -if TYPE_CHECKING: - from typing import List - - from rich.tree import RenderableType - - from pipelines.airbyte_ci.connectors.context import ConnectorContext - - -@dataclass(frozen=True) -class ConnectorReport(Report): - """A dataclass to build connector test reports to share pipelines executions results with the user.""" - - pipeline_context: ConnectorContext - - @property - def report_output_prefix(self) -> str: - return f"{self.pipeline_context.report_output_prefix}/{self.pipeline_context.connector.technical_name}/{self.pipeline_context.connector.version}" - - @property - def html_report_file_name(self) -> str: - return self.filename + ".html" - - def file_remote_storage_key(self, file_name: str) -> str: - return f"{self.report_output_prefix}/{file_name}" - - @property - def html_report_remote_storage_key(self) -> str: - return self.file_remote_storage_key(self.html_report_file_name) - - def file_url(self, file_name: str) -> str: - return f"{GCS_PUBLIC_DOMAIN}/{self.pipeline_context.ci_report_bucket}/{self.file_remote_storage_key(file_name)}" - - @property - def html_report_url(self) -> str: - return self.file_url(self.html_report_file_name) - - def to_json(self) -> str: - """Create a JSON representation of the connector test report. - - Returns: - str: The JSON representation of the report. - """ - assert self.pipeline_context.pipeline_start_timestamp is not None, "The pipeline start timestamp must be set to save reports." - - return json.dumps( - { - "connector_technical_name": self.pipeline_context.connector.technical_name, - "connector_version": self.pipeline_context.connector.version, - "run_timestamp": self.created_at.isoformat(), - "run_duration": self.run_duration.total_seconds(), - "success": self.success, - "failed_steps": [s.step.__class__.__name__ for s in self.failed_steps], - "successful_steps": [s.step.__class__.__name__ for s in self.successful_steps], - "skipped_steps": [s.step.__class__.__name__ for s in self.skipped_steps], - "gha_workflow_run_url": self.pipeline_context.gha_workflow_run_url, - "pipeline_start_timestamp": self.pipeline_context.pipeline_start_timestamp, - "pipeline_end_timestamp": round(self.created_at.timestamp()), - "pipeline_duration": round(self.created_at.timestamp()) - self.pipeline_context.pipeline_start_timestamp, - "git_branch": self.pipeline_context.git_branch, - "git_revision": self.pipeline_context.git_revision, - "ci_context": self.pipeline_context.ci_context, - "cdk_version": self.pipeline_context.cdk_version, - "html_report_url": self.html_report_url, - "dagger_cloud_url": self.pipeline_context.dagger_cloud_url, - } - ) - - def to_html(self) -> str: - env = Environment( - loader=PackageLoader("pipelines.airbyte_ci.connectors.test.steps"), - autoescape=select_autoescape(), - trim_blocks=False, - lstrip_blocks=True, - ) - template = env.get_template("test_report.html.j2") - template.globals["StepStatus"] = StepStatus - template.globals["format_duration"] = format_duration - local_icon_path = Path(f"{self.pipeline_context.connector.code_directory}/icon.svg").resolve() - step_result_to_artifact_links: Dict[str, List[Dict]] = {} - for step_result in self.steps_results: - for artifact in step_result.artifacts: - if artifact.gcs_url: - url = artifact.gcs_url - elif artifact.local_path: - url = artifact.local_path.resolve().as_uri() - else: - continue - step_result_to_artifact_links.setdefault(step_result.step.title, []).append({"name": artifact.name, "url": url}) - - template_context = { - "connector_name": self.pipeline_context.connector.technical_name, - "step_results": self.steps_results, - "run_duration": self.run_duration, - "created_at": self.created_at.isoformat(), - "connector_version": self.pipeline_context.connector.version, - "gha_workflow_run_url": None, - "dagger_logs_url": None, - "git_branch": self.pipeline_context.git_branch, - "git_revision": self.pipeline_context.git_revision, - "commit_url": None, - "icon_url": local_icon_path.as_uri(), - "report": self, - "step_result_to_artifact_links": MappingProxyType(step_result_to_artifact_links), - } - - if self.pipeline_context.is_ci: - template_context["commit_url"] = f"{AIRBYTE_GITHUB_REPO_URL_PREFIX}/commit/{self.pipeline_context.git_revision}" - template_context["gha_workflow_run_url"] = self.pipeline_context.gha_workflow_run_url - template_context["dagger_logs_url"] = self.pipeline_context.dagger_logs_url - template_context["dagger_cloud_url"] = self.pipeline_context.dagger_cloud_url - template_context["icon_url"] = ( - f"{AIRBYTE_GITHUBUSERCONTENT_URL_PREFIX}/{self.pipeline_context.git_revision}/{self.pipeline_context.connector.code_directory}/icon.svg" - ) - return template.render(template_context) - - async def save_html_report(self) -> None: - """Save the report as HTML, upload it to GCS if the pipeline is running in CI""" - - html_report_path = self.report_dir_path / self.html_report_file_name - report_dir = self.pipeline_context.dagger_client.host().directory(str(self.report_dir_path)) - local_html_report_file = report_dir.with_new_file(self.html_report_file_name, self.to_html()).file(self.html_report_file_name) - html_report_artifact = Artifact(name="HTML Report", content_type="text/html", content=local_html_report_file) - await html_report_artifact.save_to_local_path(html_report_path) - absolute_path = html_report_path.absolute() - self.pipeline_context.logger.info(f"Report saved locally at {absolute_path}") - if self.pipeline_context.remote_storage_enabled: - gcs_url = await html_report_artifact.upload_to_gcs( - dagger_client=self.pipeline_context.dagger_client, - bucket=self.pipeline_context.ci_report_bucket, # type: ignore - key=self.html_report_remote_storage_key, - gcs_credentials=self.pipeline_context.ci_gcp_credentials, # type: ignore - ) - self.pipeline_context.logger.info(f"HTML report uploaded to {gcs_url}") - - elif self.pipeline_context.enable_report_auto_open: - self.pipeline_context.logger.info("Opening HTML report in browser.") - webbrowser.open(absolute_path.as_uri()) - - async def save(self) -> None: - await super().save() - await self.save_html_report() - - def print(self) -> None: - """Print the test report to the console in a nice way.""" - connector_name = self.pipeline_context.connector.technical_name - main_panel_title = Text(f"{connector_name.upper()} - {self.name}") - main_panel_title.stylize(Style(color="blue", bold=True)) - duration_subtitle = Text(f"⏲️ Total pipeline duration for {connector_name}: {format_duration(self.run_duration)}") - step_results_table = Table(title="Steps results") - step_results_table.add_column("Step") - step_results_table.add_column("Result") - step_results_table.add_column("Duration") - - for step_result in self.steps_results: - step = Text(step_result.step.title) - step.stylize(step_result.status.get_rich_style()) - result = Text(step_result.status.value) - result.stylize(step_result.status.get_rich_style()) - step_results_table.add_row(step, result, format_duration(step_result.step.run_duration)) - - details_instructions = Text("ℹ️ You can find more details with step executions logs in the saved HTML report.") - to_render: List[RenderableType] = [step_results_table, details_instructions] - - if self.pipeline_context.dagger_cloud_url: - self.pipeline_context.logger.info(f"🔗 View runs for commit in Dagger Cloud: {self.pipeline_context.dagger_cloud_url}") - - main_panel = Panel(Group(*to_render), title=main_panel_title, subtitle=duration_subtitle) - console.print(main_panel) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py deleted file mode 100644 index 83e26f5888da..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/commands.py +++ /dev/null @@ -1,200 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import shutil -from typing import Dict, List - -import asyncclick as click - -from pipelines import main_logger -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.connectors.test.pipeline import run_connector_test_pipeline -from pipelines.airbyte_ci.connectors.test.steps.common import LiveTests -from pipelines.cli.click_decorators import click_ci_requirements_option -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.consts import LOCAL_BUILD_PLATFORM, MAIN_CONNECTOR_TESTING_SECRET_STORE_ALIAS, ContextState -from pipelines.hacks import do_regression_test_status_check -from pipelines.helpers.execution import argument_parsing -from pipelines.helpers.execution.run_steps import RunStepOptions -from pipelines.helpers.github import update_global_commit_status_check_for_tests -from pipelines.helpers.utils import fail_if_missing_docker_hub_creds -from pipelines.models.secrets import GSMSecretStore -from pipelines.models.steps import STEP_PARAMS - -GITHUB_GLOBAL_CONTEXT_FOR_TESTS = "Connectors CI tests" -GITHUB_GLOBAL_DESCRIPTION_FOR_TESTS = "Running connectors tests" -REGRESSION_TEST_MANUAL_APPROVAL_CONTEXT = "Regression Test Results Reviewed and Approved" - - -@click.command( - cls=DaggerPipelineCommand, - help="Test all the selected connectors.", - context_settings=dict( - ignore_unknown_options=True, - ), -) -@click_ci_requirements_option() -@click.option( - "--code-tests-only", - is_flag=True, - help=("Only execute code tests. " "Metadata checks, QA, and acceptance tests will be skipped."), - default=False, - type=bool, -) -@click.option( - "--fail-fast", - help="When enabled, tests will fail fast.", - default=False, - type=bool, - is_flag=True, -) -@click.option( - "--concurrent-cat", - help="When enabled, the CAT tests will run concurrently. Be careful about rate limits", - default=False, - type=bool, - is_flag=True, -) -@click.option( - "--skip-step", - "-x", - "skip_steps", - multiple=True, - type=click.Choice([step_id.value for step_id in CONNECTOR_TEST_STEP_ID]), - help="Skip a step by name. Can be used multiple times to skip multiple steps.", -) -@click.option( - "--only-step", - "-k", - "only_steps", - multiple=True, - type=click.Choice([step_id.value for step_id in CONNECTOR_TEST_STEP_ID]), - help="Only run specific step by name. Can be used multiple times to keep multiple steps.", -) -@click.option( - "--global-status-check-context", - "global_status_check_context", - help="The context of the global status check which will be sent to GitHub status API.", - default=GITHUB_GLOBAL_CONTEXT_FOR_TESTS, -) -@click.option( - "--global-status-check-description", - "global_status_check_description", - help="The description of the global status check which will be sent to GitHub status API.", - default=GITHUB_GLOBAL_DESCRIPTION_FOR_TESTS, -) -@click.argument( - "extra_params", nargs=-1, type=click.UNPROCESSED, callback=argument_parsing.build_extra_params_mapping(CONNECTOR_TEST_STEP_ID) -) -@click.pass_context -async def test( - ctx: click.Context, - code_tests_only: bool, - fail_fast: bool, - concurrent_cat: bool, - skip_steps: List[str], - only_steps: List[str], - global_status_check_context: str, - global_status_check_description: str, - extra_params: Dict[CONNECTOR_TEST_STEP_ID, STEP_PARAMS], -) -> bool: - """Runs a test pipeline for the selected connectors. - - Args: - ctx (click.Context): The click context. - """ - ctx.obj["global_status_check_context"] = global_status_check_context - ctx.obj["global_status_check_description"] = global_status_check_description - - if ctx.obj["ci_gcp_credentials"]: - ctx.obj["secret_stores"][MAIN_CONNECTOR_TESTING_SECRET_STORE_ALIAS] = GSMSecretStore(ctx.obj["ci_gcp_credentials"]) - else: - main_logger.warn(f"The credentials to connect to {MAIN_CONNECTOR_TESTING_SECRET_STORE_ALIAS} were are not defined.") - - if only_steps and skip_steps: - raise click.UsageError("Cannot use both --only-step and --skip-step at the same time.") - if not only_steps: - skip_steps = list(skip_steps) - if ctx.obj["is_ci"]: - fail_if_missing_docker_hub_creds(ctx) - - do_regression_test_status_check(ctx, REGRESSION_TEST_MANUAL_APPROVAL_CONTEXT, main_logger) - if ctx.obj["selected_connectors_with_modified_files"]: - update_global_commit_status_check_for_tests(ctx.obj, "pending") - else: - main_logger.warn("No connector were selected for testing.") - update_global_commit_status_check_for_tests(ctx.obj, "success") - return True - - run_step_options = RunStepOptions( - fail_fast=fail_fast, - skip_steps=[CONNECTOR_TEST_STEP_ID(step_id) for step_id in skip_steps], - keep_steps=[CONNECTOR_TEST_STEP_ID(step_id) for step_id in only_steps], - step_params=extra_params, - ) - - connectors_tests_contexts = [ - ConnectorTestContext( - pipeline_name=f"{global_status_check_context} on {connector.technical_name}", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_git_user=ctx.obj["ci_git_user"], - ci_github_access_token=ctx.obj["ci_github_access_token"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - pull_request=ctx.obj.get("pull_request"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - code_tests_only=code_tests_only, - use_local_cdk=ctx.obj.get("use_local_cdk"), - use_cdk_ref=ctx.obj.get("use_cdk_ref"), - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - docker_hub_username=ctx.obj.get("docker_hub_username"), - docker_hub_password=ctx.obj.get("docker_hub_password"), - concurrent_cat=concurrent_cat, - run_step_options=run_step_options, - targeted_platforms=[LOCAL_BUILD_PLATFORM], - secret_stores=ctx.obj["secret_stores"], - enable_report_auto_open=ctx.obj.get("enable_report_auto_open", True), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - - try: - await run_connectors_pipelines( - [connector_context for connector_context in connectors_tests_contexts], - run_connector_test_pipeline, - "Test Pipeline", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - ) - except Exception as e: - main_logger.error("An error occurred while running the test pipeline", exc_info=e) - update_global_commit_status_check_for_tests(ctx.obj, "failure") - return False - - finally: - if LiveTests.local_tests_artifacts_dir.exists(): - shutil.rmtree(LiveTests.local_tests_artifacts_dir) - main_logger.info(f" Test artifacts cleaned up from {LiveTests.local_tests_artifacts_dir}") - - @ctx.call_on_close - def send_commit_status_check() -> None: - if ctx.obj["is_ci"]: - global_success = all(connector_context.state is ContextState.SUCCESSFUL for connector_context in connectors_tests_contexts) - update_global_commit_status_check_for_tests(ctx.obj, "success" if global_success else "failure") - - # If we reach this point, it means that all the connectors have been tested so the pipeline did its job and can exit with success. - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/context.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/context.py deleted file mode 100644 index 7bdb40270095..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/context.py +++ /dev/null @@ -1,198 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""Module declaring context related classes.""" - -from copy import deepcopy -from logging import Logger -from typing import Any, Dict, List, Optional - -from pydash import find # type: ignore - -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.helpers.execution.run_steps import RunStepOptions -from pipelines.models.secrets import Secret, SecretNotFoundError, SecretStore - -# These test suite names are declared in metadata.yaml files -TEST_SUITE_NAME_TO_STEP_ID = { - "unitTests": CONNECTOR_TEST_STEP_ID.UNIT, - "integrationTests": CONNECTOR_TEST_STEP_ID.INTEGRATION, - "acceptanceTests": CONNECTOR_TEST_STEP_ID.ACCEPTANCE, - "liveTests": CONNECTOR_TEST_STEP_ID.CONNECTOR_LIVE_TESTS, -} - - -class ConnectorTestContext(ConnectorContext): - def __init__( - self, - *args: Any, - **kwargs: Any, - ) -> None: - super().__init__(*args, **kwargs) - self.run_step_options = self._skip_metadata_disabled_test_suites(self.run_step_options) - self.step_id_to_secrets_mapping = self._get_step_id_to_secret_mapping() - - @property - def test_only_change(self) -> bool: - """Check if the only modified files are in unit_tests or integration_tests directories. - - Returns: - bool: True if all modified files are in unit_tests or integration_tests, False otherwise. - """ - test_directories = ("unit_tests", "integration_tests") - for modified_file in self.modified_files: - try: - rel_path = modified_file.relative_to(self.connector.code_directory) - top_level_dir = rel_path.parts[0] if rel_path.parts else "" - if top_level_dir not in test_directories: - return False - except ValueError: - # File not under connector directory, treat as non-test change - return False - return True - - @staticmethod - def _handle_missing_secret_store( - secret_info: Dict[str, str | Dict[str, str]], raise_on_missing: bool, logger: Optional[Logger] = None - ) -> None: - assert isinstance(secret_info["secretStore"], dict), "The secretStore field must be a dict" - message = f"Secret {secret_info['name']} can't be retrieved as {secret_info['secretStore']['alias']} is not available" - if raise_on_missing: - raise SecretNotFoundError(message) - if logger is not None: - logger.warn(message) - - @staticmethod - def _process_secret( - secret_info: Dict[str, str | Dict[str, str]], - secret_stores: Dict[str, SecretStore], - raise_on_missing: bool, - logger: Optional[Logger] = None, - ) -> Optional[Secret]: - assert isinstance(secret_info["secretStore"], dict), "The secretStore field must be a dict" - secret_store_alias = secret_info["secretStore"]["alias"] - if secret_store_alias not in secret_stores: - ConnectorTestContext._handle_missing_secret_store(secret_info, raise_on_missing, logger) - return None - else: - # All these asserts and casting are there to make MyPy happy - # The dict structure being nested MyPy can't figure if the values are str or dict - assert isinstance(secret_info["name"], str), "The secret name field must be a string" - if file_name := secret_info.get("fileName"): - assert isinstance(secret_info["fileName"], str), "The secret fileName must be a string" - file_name = str(secret_info["fileName"]) - else: - file_name = None - return Secret(secret_info["name"], secret_stores[secret_store_alias], file_name=file_name) - - @staticmethod - def get_secrets_from_connector_test_suites_option( - connector_test_suites_options: List[Dict[str, str | Dict[str, List[Dict[str, str | Dict[str, str]]]]]], - suite_name: str, - secret_stores: Dict[str, SecretStore], - raise_on_missing_secret_store: bool = True, - logger: Logger | None = None, - ) -> List[Secret]: - """Get secrets declared in metadata connectorTestSuitesOptions for a test suite name. - It will use the secret store alias declared in connectorTestSuitesOptions. - If the secret store is not available a warning or and error could be raised according to the raise_on_missing_secret_store parameter value. - We usually want to raise an error when running in CI context and log a warning when running locally, as locally we can fallback on local secrets. - - Args: - connector_test_suites_options (List[Dict[str, str | Dict]]): The connector under test test suite options - suite_name (str): The test suite name - secret_stores (Dict[str, SecretStore]): The available secrets stores - raise_on_missing_secret_store (bool, optional): Raise an error if the secret store declared in the connectorTestSuitesOptions is not available. Defaults to True. - logger (Logger | None, optional): Logger to log a warning if the secret store declared in the connectorTestSuitesOptions is not available. Defaults to None. - - Raises: - SecretNotFoundError: Raised if the secret store declared in the connectorTestSuitesOptions is not available and raise_on_missing_secret_store is truthy. - - Returns: - List[Secret]: List of secrets declared in the connectorTestSuitesOptions for a test suite name. - """ - secrets: List[Secret] = [] - enabled_test_suite = find(connector_test_suites_options, lambda x: x["suite"] == suite_name) - - if enabled_test_suite and "testSecrets" in enabled_test_suite: - for secret_info in enabled_test_suite["testSecrets"]: - if secret := ConnectorTestContext._process_secret(secret_info, secret_stores, raise_on_missing_secret_store, logger): - secrets.append(secret) - return secrets - - def get_connector_secrets_for_test_suite( - self, test_suite_name: str, connector_test_suites_options: List, local_secrets: List[Secret] - ) -> List[Secret]: - """Get secrets to use for a test suite. - Always merge secrets declared in metadata's connectorTestSuiteOptions with secrets declared locally. - - Args: - test_suite_name (str): Name of the test suite to get secrets for - context (ConnectorTestContext): The current connector context - connector_test_suites_options (Dict): The current connector test suite options (from metadata) - local_secrets (List[Secret]): The local connector secrets. - - Returns: - List[Secret]: Secrets to use to run the passed test suite name. - """ - return ( - self.get_secrets_from_connector_test_suites_option( - connector_test_suites_options, - test_suite_name, - self.secret_stores, - raise_on_missing_secret_store=self.is_ci, - logger=self.logger, - ) - + local_secrets - ) - - def _get_step_id_to_secret_mapping(self) -> Dict[CONNECTOR_TEST_STEP_ID, List[Secret]]: - step_id_to_secrets: Dict[CONNECTOR_TEST_STEP_ID, List[Secret]] = { - CONNECTOR_TEST_STEP_ID.UNIT: [], - CONNECTOR_TEST_STEP_ID.INTEGRATION: [], - CONNECTOR_TEST_STEP_ID.ACCEPTANCE: [], - } - local_secrets = self.local_secret_store.get_all_secrets() if self.local_secret_store else [] - connector_test_suites_options = self.metadata.get("connectorTestSuitesOptions", []) - - keep_steps = set(self.run_step_options.keep_steps or []) - skip_steps = set(self.run_step_options.skip_steps or []) - - for test_suite_name, step_id in TEST_SUITE_NAME_TO_STEP_ID.items(): - if step_id in keep_steps or (not keep_steps and step_id not in skip_steps): - step_id_to_secrets[step_id] = self.get_connector_secrets_for_test_suite( - test_suite_name, connector_test_suites_options, local_secrets - ) - return step_id_to_secrets - - def get_secrets_for_step_id(self, step_id: CONNECTOR_TEST_STEP_ID) -> List[Secret]: - return self.step_id_to_secrets_mapping.get(step_id, []) - - def _get_step_id_to_skip_according_to_metadata(self) -> List[CONNECTOR_TEST_STEP_ID]: - """The connector metadata have a connectorTestSuitesOptions field. - It allows connector developers to declare the test suites that are enabled for a connector. - This function retrieved enabled test suites according to this field value and returns the test suites steps that are skipped (because they're not declared in this field.) - The skippable test suites steps are declared in TEST_SUITE_NAME_TO_STEP_ID. - - Returns: - List[CONNECTOR_TEST_STEP_ID]: List of step ids that should be skipped according to connector metadata. - """ - enabled_test_suites = [option["suite"] for option in self.metadata.get("connectorTestSuitesOptions", [])] - return [step_id for test_suite_name, step_id in TEST_SUITE_NAME_TO_STEP_ID.items() if test_suite_name not in enabled_test_suites] - - def _skip_metadata_disabled_test_suites(self, run_step_options: RunStepOptions) -> RunStepOptions: - """Updated the original run_step_options to skip the disabled test suites according to connector metadata. - - Args: - run_step_options (RunStepOptions): Original run step options. - - Returns: - RunStepOptions: Updated run step options. - """ - run_step_options = deepcopy(run_step_options) - # If any `skip_steps` are present, we will run everything except the skipped steps, instead of just `keep_steps`. - if not run_step_options.keep_steps: - run_step_options.skip_steps += self._get_step_id_to_skip_according_to_metadata() - return run_step_options diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py deleted file mode 100644 index 0aad2fa7167f..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/pipeline.py +++ /dev/null @@ -1,77 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -"""This module groups factory like functions to dispatch tests steps according to the connector under test language.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import anyio -from connector_ops.utils import ConnectorLanguage # type: ignore - -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.connectors.test.steps import java_connectors, manifest_only_connectors, python_connectors -from pipelines.airbyte_ci.connectors.test.steps.common import VersionIncrementCheck -from pipelines.helpers.execution.run_steps import StepToRun, run_steps - -if TYPE_CHECKING: - from pipelines.helpers.execution.run_steps import STEP_TREE - -LANGUAGE_MAPPING = { - "get_test_steps": { - ConnectorLanguage.PYTHON: python_connectors.get_test_steps, - ConnectorLanguage.LOW_CODE: python_connectors.get_test_steps, - ConnectorLanguage.MANIFEST_ONLY: manifest_only_connectors.get_test_steps, - ConnectorLanguage.JAVA: java_connectors.get_test_steps, - }, -} - - -def get_test_steps(context: ConnectorTestContext) -> STEP_TREE: - """Get all the tests steps according to the connector language. - - Args: - context (ConnectorTestContext): The current connector context. - - Returns: - STEP_TREE: The list of tests steps. - """ - if _get_test_steps := LANGUAGE_MAPPING["get_test_steps"].get(context.connector.language): - return _get_test_steps(context) - else: - context.logger.warning(f"No tests defined for connector language {context.connector.language}!") - return [] - - -async def run_connector_test_pipeline(context: ConnectorTestContext, semaphore: anyio.Semaphore) -> ConnectorReport: - """ - Compute the steps to run for a connector test pipeline. - """ - all_steps_to_run: STEP_TREE = [] - - all_steps_to_run += get_test_steps(context) - - # Skip static analysis steps when only test files are modified - if not context.code_tests_only and not context.test_only_change: - static_analysis_steps_to_run = [ - [ - StepToRun(id=CONNECTOR_TEST_STEP_ID.VERSION_INC_CHECK, step=VersionIncrementCheck(context)), - ] - ] - all_steps_to_run += static_analysis_steps_to_run - - async with semaphore: - async with context: - result_dict = await run_steps( - runnables=all_steps_to_run, - options=context.run_step_options, - ) - - results = list(result_dict.values()) - report = ConnectorReport(context, steps_results=results, name="TEST RESULTS") - context.report = report - - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/common.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/common.py deleted file mode 100644 index 5b9a91bf9d28..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/common.py +++ /dev/null @@ -1,838 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups steps made to run tests agnostic to a connector language.""" - -import datetime -import json -import os -import time -from abc import ABC, abstractmethod -from enum import Enum -from functools import cached_property -from pathlib import Path -from textwrap import dedent -from typing import Any, Dict, List, Optional, Set - -import dagger -import requests # type: ignore -import semver -import yaml # type: ignore -from dagger import Container, Directory - -# This slugify lib has to be consistent with the slugify lib used in live_tests -# live_test can't resolve the passed connector container otherwise. -from slugify import slugify # type: ignore - -from pipelines import hacks, main_logger -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.steps.docker import SimpleDockerStep -from pipelines.consts import INTERNAL_TOOL_PATHS, CIContext -from pipelines.dagger.actions import secrets -from pipelines.dagger.actions.python.poetry import with_poetry -from pipelines.helpers.github import AIRBYTE_GITHUBUSERCONTENT_URL_PREFIX -from pipelines.helpers.utils import METADATA_FILE_NAME, get_exec_result -from pipelines.models.artifacts import Artifact -from pipelines.models.secrets import Secret -from pipelines.models.steps import STEP_PARAMS, MountPath, Step, StepResult, StepStatus - -GITHUB_URL_PREFIX_FOR_CONNECTORS = f"{AIRBYTE_GITHUBUSERCONTENT_URL_PREFIX}/master/airbyte-integrations/connectors" - - -class VersionCheck(Step, ABC): - """A step to validate the connector version was bumped if files were modified""" - - context: ConnectorContext - - @property - def should_run(self) -> bool: - return True - - @property - def github_master_metadata_url(self) -> str: - return f"{GITHUB_URL_PREFIX_FOR_CONNECTORS}/{self.context.connector.technical_name}/{METADATA_FILE_NAME}" - - @cached_property - def master_metadata(self) -> Optional[dict]: - response = requests.get(self.github_master_metadata_url) - - # New connectors will not have a metadata file in master - if not response.ok: - return None - return yaml.safe_load(response.text) - - @property - def master_connector_version(self) -> semver.Version: - metadata = self.master_metadata - if not metadata: - return semver.Version.parse("0.0.0") - - return semver.Version.parse(str(metadata["data"]["dockerImageTag"])) - - @property - def current_connector_version(self) -> semver.Version: - return semver.Version.parse(str(self.context.metadata["dockerImageTag"])) - - @property - def success_result(self) -> StepResult: - return StepResult(step=self, status=StepStatus.SUCCESS) - - def _get_failure_result(self, failure_message: str) -> StepResult: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=failure_message) - - @abstractmethod - def validate(self) -> StepResult: - raise NotImplementedError() - - async def _run(self) -> StepResult: - if not self.should_run: - return StepResult(step=self, status=StepStatus.SKIPPED, stdout="No modified files required a version bump.") - if self.context.ci_context == CIContext.MASTER: - return StepResult(step=self, status=StepStatus.SKIPPED, stdout="Version check are not running in master context.") - try: - return self.validate() - except (requests.HTTPError, ValueError, TypeError) as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - -class VersionIncrementCheck(VersionCheck): - context: ConnectorContext - title = "Connector version increment check" - - BYPASS_CHECK_FOR = [ - METADATA_FILE_NAME, - "acceptance-test-config.yml", - "README.md", - "bootstrap.md", - ".dockerignore", - "unit_tests", - "integration_tests", - "src/test", - "src/test-integration", - "src/test-performance", - "build.gradle", - "erd", - "build_customization.py", - ] - - @property - def should_run(self) -> bool: - # Skip if connector opts out of version checks - if self.context.metadata and self.context.metadata.get("ab_internal", {}).get("requireVersionIncrementsInPullRequests") is False: - return False - - for filename in self.context.modified_files: - relative_path = str(filename).replace(str(self.context.connector.code_directory) + "/", "") - if not any([relative_path.startswith(to_bypass) for to_bypass in self.BYPASS_CHECK_FOR]): - return True - return False - - def is_version_not_incremented(self) -> bool: - return self.master_connector_version >= self.current_connector_version - - def get_failure_message_for_no_increment(self) -> str: - return ( - f"The dockerImageTag in {METADATA_FILE_NAME} was not incremented. " - f"Master version is {self.master_connector_version}, current version is {self.current_connector_version}" - ) - - def are_both_versions_release_candidates(self) -> bool: - return bool( - self.master_connector_version.prerelease - and self.current_connector_version.prerelease - and "rc" in self.master_connector_version.prerelease - and "rc" in self.current_connector_version.prerelease - ) - - def have_same_major_minor_patch(self) -> bool: - return ( - self.master_connector_version.major == self.current_connector_version.major - and self.master_connector_version.minor == self.current_connector_version.minor - and self.master_connector_version.patch == self.current_connector_version.patch - ) - - @staticmethod - def _get_registry_override_tag(metadata: Optional[dict], channel: str) -> Optional[str]: - """Extract the dockerImageTag from registryOverrides for a given channel. - - Args: - metadata: The metadata dictionary (master or current). - channel: The channel to extract from ("cloud" or "oss"). - - Returns: - The dockerImageTag value if present, None otherwise. - """ - if metadata is None: - return None - try: - return metadata.get("data", {}).get("registryOverrides", {}).get(channel, {}).get("dockerImageTag") - except (TypeError, AttributeError): - return None - - def _has_registry_override_docker_tag_change(self) -> bool: - """Check if registryOverrides.cloud.dockerImageTag or registryOverrides.oss.dockerImageTag has changed. - - Returns: - bool: True if either cloud or oss dockerImageTag has been added, removed, or changed. - """ - master_cloud = self._get_registry_override_tag(self.master_metadata, "cloud") - current_cloud = self._get_registry_override_tag(self.context.metadata, "cloud") - - master_oss = self._get_registry_override_tag(self.master_metadata, "oss") - current_oss = self._get_registry_override_tag(self.context.metadata, "oss") - - return (master_cloud != current_cloud) or (master_oss != current_oss) - - def validate(self) -> StepResult: - if self.is_version_not_incremented(): - # Allow version to stay the same if registryOverrides.cloud.dockerImageTag or - # registryOverrides.oss.dockerImageTag has been added, removed, or changed - if self._has_registry_override_docker_tag_change(): - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout=( - f"The current change is modifying the registryOverrides pinned version on Cloud or OSS. Skipping this check " - f"because the defined version {self.current_connector_version} is allowed to be unchanged" - ), - ) - return self._get_failure_result( - ( - f"The dockerImageTag in {METADATA_FILE_NAME} was not incremented. " - f"Master version is {self.master_connector_version}, current version is {self.current_connector_version}" - ) - ) - - if self.are_both_versions_release_candidates(): - if not self.have_same_major_minor_patch(): - return self._get_failure_result( - ( - f"Master and current version are release candidates but they have different major, minor or patch versions. " - f"Release candidates should only differ in the prerelease part. Master version is {self.master_connector_version}, " - f"current version is {self.current_connector_version}" - ) - ) - - return self.success_result - - -class AcceptanceTests(Step): - """A step to run acceptance tests for a connector if it has an acceptance test config file.""" - - context: ConnectorContext - title = "Acceptance tests" - CONTAINER_TEST_INPUT_DIRECTORY = "/test_input" - CONTAINER_SECRETS_DIRECTORY = "/test_input/secrets" - REPORT_LOG_PATH = "/tmp/report_log.jsonl" - skipped_exit_code = 5 - accept_extra_params = True - - @property - def default_params(self) -> STEP_PARAMS: - """Default pytest options. - - Returns: - dict: The default pytest options. - """ - return super().default_params | { - "-ra": [], # Show extra test summary info in the report for all but the passed tests - "--disable-warnings": [], # Disable warnings in the pytest report - "--durations": ["3"], # Show the 3 slowest tests in the report - } - - @property - def base_cat_command(self) -> List[str]: - command = [ - "python", - "-m", - "pytest", - # Write the test report in jsonl format - f"--report-log={self.REPORT_LOG_PATH}", - "-p", # Load the connector_acceptance_test plugin - "connector_acceptance_test.plugin", - "--acceptance-test-config", - self.CONTAINER_TEST_INPUT_DIRECTORY, - ] - - if self.concurrent_test_run: - command += ["--numprocesses=auto"] # Using pytest-xdist to run tests in parallel, auto means using all available cores - return command - - def __init__(self, context: ConnectorContext, secrets: List[Secret], concurrent_test_run: Optional[bool] = False) -> None: - """Create a step to run acceptance tests for a connector if it has an acceptance test config file. - - Args: - context (ConnectorContext): The current test context, providing a connector object, a dagger client and a repository directory. - secrets (List[Secret]): List of secrets to mount to the connector container under test. - concurrent_test_run (Optional[bool], optional): Whether to run acceptance tests in parallel. Defaults to False. - """ - super().__init__(context, secrets) - self.concurrent_test_run = concurrent_test_run - - async def get_cat_command(self, connector_dir: Directory) -> List[str]: - """ - Connectors can optionally setup or teardown resources before and after the acceptance tests are run. - This is done via the acceptance.py file in their integration_tests directory. - We append this module as a plugin the acceptance will use. - """ - cat_command = self.base_cat_command - if "integration_tests" in await connector_dir.entries(): - if "acceptance.py" in await connector_dir.directory("integration_tests").entries(): - cat_command += ["-p", "integration_tests.acceptance"] - return cat_command + self.params_as_cli_options - - async def _run(self, connector_under_test_container: Container) -> StepResult: - """Run the acceptance test suite on a connector dev image. Build the connector acceptance test image if the tag is :dev. - - Args: - connector_under_test_container (Container): The container holding the connector under test image. - - Returns: - StepResult: Failure or success of the acceptances tests with stdout and stderr. - """ - - if not self.context.connector.acceptance_test_config: - return StepResult(step=self, status=StepStatus.SKIPPED) - connector_dir = await self.context.get_connector_dir() - cat_container = await self._build_connector_acceptance_test(connector_under_test_container, connector_dir) - cat_command = await self.get_cat_command(connector_dir) - cat_container = cat_container.with_(hacks.never_fail_exec(cat_command)) - step_result = await self.get_step_result(cat_container) - secret_dir = cat_container.directory(self.CONTAINER_SECRETS_DIRECTORY) - - if secret_files := await secret_dir.entries(): - for file_path in secret_files: - if file_path.startswith("updated_configurations"): - self.context.updated_secrets_dir = secret_dir - break - return step_result - - def get_cache_buster(self) -> str: - """ - This bursts the CAT cached results everyday and on new version or image size change. - It's cool because in case of a partially failing nightly build the connectors that already ran CAT won't re-run CAT. - We keep the guarantee that a CAT runs everyday. - - Returns: - str: A string representing the cachebuster value. - """ - return datetime.datetime.utcnow().strftime("%Y%m%d") + self.context.connector.version - - async def _build_connector_acceptance_test(self, connector_under_test_container: Container, test_input: Directory) -> Container: - """Create a container to run connector acceptance tests. - - Args: - connector_under_test_container (Container): The container holding the connector under test image. - test_input (Directory): The connector under test directory. - Returns: - Container: A container with connector acceptance tests installed. - """ - - if self.context.connector_acceptance_test_image.endswith(":dev"): - cat_container = self.context.connector_acceptance_test_source_dir.docker_build() - else: - cat_container = self.dagger_client.container().from_(self.context.connector_acceptance_test_image) - - connector_container_id = await connector_under_test_container.id() - cat_container = ( - cat_container.with_env_variable("RUN_IN_AIRBYTE_CI", "1") - .with_exec(["mkdir", "/dagger_share"]) - .with_env_variable("CACHEBUSTER", self.get_cache_buster()) - .with_new_file("/tmp/container_id.txt", contents=str(connector_container_id)) - .with_workdir("/test_input") - .with_mounted_directory("/test_input", test_input) - .with_(await secrets.mounted_connector_secrets(self.context, self.CONTAINER_SECRETS_DIRECTORY, self.secrets)) - ) - if "_EXPERIMENTAL_DAGGER_RUNNER_HOST" in os.environ: - self.context.logger.info("Using experimental dagger runner host to run CAT with dagger-in-dagger") - cat_container = cat_container.with_env_variable( - "_EXPERIMENTAL_DAGGER_RUNNER_HOST", "unix:///var/run/buildkit/buildkitd.sock" - ).with_unix_socket( - "/var/run/buildkit/buildkitd.sock", self.context.dagger_client.host().unix_socket("/var/run/buildkit/buildkitd.sock") - ) - - return cat_container.with_unix_socket("/var/run/docker.sock", self.context.dagger_client.host().unix_socket("/var/run/docker.sock")) - - def get_is_hard_failure(self) -> bool: - """When a connector is not certified or the CI context is master, we consider the acceptance tests as hard failures: - The overall status of the pipeline will be FAILURE if the acceptance tests fail. - For marketplace connectors we defer to the IncrementalAcceptanceTests step to determine if the acceptance tests are hard failures: - If a new test is failing compared to the released version of the connector. - - Returns: - bool: Whether a failure of acceptance tests should be considered a hard failures. - """ - return self.context.connector.metadata.get("supportLevel") == "certified" or self.context.ci_context == CIContext.MASTER - - async def get_step_result(self, container: Container) -> StepResult: - """Retrieve stdout, stderr and exit code from the executed CAT container. - Pull the report logs from the container and create an Artifact object from it. - Build and return a step result object from these objects. - - Args: - container (Container): The CAT container to get the results from. - - Returns: - StepResult: The step result object. - """ - exit_code, stdout, stderr = await get_exec_result(container) - report_log_artifact = Artifact( - name="cat_report_log.jsonl", - content_type="text/jsonl", - content=container.file(self.REPORT_LOG_PATH), - to_upload=True, - ) - status = self.get_step_status_from_exit_code(exit_code) - - is_hard_failure = status is StepStatus.FAILURE and self.get_is_hard_failure() - - return StepResult( - step=self, - status=self.get_step_status_from_exit_code(exit_code), - stderr=stderr, - stdout=stdout, - output={"report_log": report_log_artifact}, - artifacts=[report_log_artifact], - consider_in_overall_status=status is StepStatus.SUCCESS or is_hard_failure, - ) - - -class IncrementalAcceptanceTests(Step): - """This step runs the acceptance tests on the released image of the connector and compares the results with the current acceptance tests report log. - It fails if there are new failing tests in the current acceptance tests report log. - """ - - title = "Incremental Acceptance Tests" - context: ConnectorContext - - async def get_failed_pytest_node_ids(self, current_acceptance_tests_report_log: Artifact) -> Set[str]: - """Parse the report log of the acceptance tests and return the pytest node ids of the failed tests. - - Args: - current_acceptance_tests_report_log (Artifact): The report log of the acceptance tests. - - Returns: - List[str]: The pytest node ids of the failed tests. - """ - current_report_lines = (await current_acceptance_tests_report_log.content.contents()).splitlines() - failed_nodes = set() - for line in current_report_lines: - single_test_report = json.loads(line) - if "nodeid" not in single_test_report or "outcome" not in single_test_report: - continue - if single_test_report["outcome"] == "failed": - failed_nodes.add(single_test_report["nodeid"]) - return failed_nodes - - def _get_master_metadata(self) -> Dict[str, Any]: - metadata_response = requests.get(f"{GITHUB_URL_PREFIX_FOR_CONNECTORS}/{self.context.connector.technical_name}/metadata.yaml") - if not metadata_response.ok: - raise FileNotFoundError(f"Could not fetch metadata file for {self.context.connector.technical_name} on master.") - return yaml.safe_load(metadata_response.text) - - async def get_result_log_on_master(self, master_metadata: dict) -> Artifact: - """Runs acceptance test on the released image of the connector and returns the report log. - The released image version is fetched from the master metadata file of the connector. - We're not using the online connector registry here as some connectors might not be released to OSS nor Airbyte Cloud. - Thanks to Dagger caching subsequent runs of this step will be cached if the released image did not change. - - Returns: - Artifact: The report log of the acceptance tests run on the released image. - """ - master_docker_image_tag = master_metadata["data"]["dockerImageTag"] - released_image = f'{master_metadata["data"]["dockerRepository"]}:{master_docker_image_tag}' - released_container = self.dagger_client.container().from_(released_image) - self.logger.info(f"Running acceptance tests on released image: {released_image}") - acceptance_tests_results_on_master = await AcceptanceTests(self.context, self.secrets).run(released_container) - return acceptance_tests_results_on_master.output["report_log"] - - async def _run(self, current_acceptance_tests_result: StepResult) -> StepResult: - """Compare the acceptance tests report log of the current image with the one of the released image. - Fails if there are new failing tests in the current acceptance tests report log. - """ - - if current_acceptance_tests_result.consider_in_overall_status: - return StepResult( - step=self, status=StepStatus.SKIPPED, stdout="Skipping because the current acceptance tests are hard failures." - ) - - current_acceptance_tests_report_log = current_acceptance_tests_result.output["report_log"] - current_failing_nodes = await self.get_failed_pytest_node_ids(current_acceptance_tests_report_log) - if not current_failing_nodes: - return StepResult( - step=self, status=StepStatus.SKIPPED, stdout="No failing acceptance tests were detected on the current version." - ) - try: - master_metadata = self._get_master_metadata() - except FileNotFoundError as exc: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout="The connector does not have a metadata file on master. Skipping incremental acceptance tests.", - exc_info=exc, - ) - - master_result_logs = await self.get_result_log_on_master(master_metadata) - - master_failings = await self.get_failed_pytest_node_ids(master_result_logs) - new_failing_nodes = current_failing_nodes - master_failings - if not new_failing_nodes: - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=dedent( - f""" - No new failing acceptance tests were detected. - Acceptance tests are still failing with {len(current_failing_nodes)} failing tests but the AcceptanceTests step is not a hard failure for this connector. - Please checkout the original acceptance tests failures and assess how critical they are. - """ - ), - ) - else: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stdout=f"{len(new_failing_nodes)} new failing acceptance tests detected:\n-" - + "\n-".join(current_failing_nodes) - + "\nPlease fix the new failing tests before merging this PR." - + f"\nPlease also check the original {len(current_failing_nodes)} acceptance tests failures and assess how critical they are.", - ) - - -class LiveTestSuite(Enum): - ALL = "live" - REGRESSION = "regression" - VALIDATION = "validation" - - -class LiveTests(Step): - """A step to run live tests for a connector.""" - - context: ConnectorContext - skipped_exit_code = 5 - accept_extra_params = True - local_tests_artifacts_dir = Path("/tmp/live_tests_artifacts") - working_directory = "/app" - github_user = "octavia-squidington-iii" - platform_repo_url = "airbytehq/airbyte-platform-internal" - test_suite_to_dir = { - LiveTestSuite.ALL: "src/live_tests", - LiveTestSuite.REGRESSION: "src/live_tests/regression_tests", - LiveTestSuite.VALIDATION: "src/live_tests/validation_tests", - } - - @property - def default_params(self) -> STEP_PARAMS: - """Default pytest options. - - Returns: - dict: The default pytest options. - """ - return super().default_params | { - "-ra": [], # Show extra test summary info in the report for all but the passed tests - "--disable-warnings": [], # Disable warnings in the pytest report - "--durations": ["3"], # Show the 3 slowest tests in the report - } - - @property - def title(self) -> str: - return f"Connector {self.test_suite.title()} Tests" - - def _test_command(self) -> List[str]: - """ - The command used to run the tests - """ - base_command = [ - "poetry", - "run", - "pytest", - self.test_dir, - "--connector-image", - self.connector_image, - ] - return base_command + self._get_command_options() - - def _get_command_options(self) -> List[str]: - command_options = [] - if self.connection_id: - command_options += ["--connection-id", self.connection_id] - if self.control_version: - command_options += ["--control-version", self.control_version] - if self.target_version: - command_options += ["--target-version", self.target_version] - if self.pr_url: - command_options += ["--pr-url", self.pr_url] - if self.run_id: - command_options += ["--run-id", self.run_id] - if self.should_read_with_state: - command_options += ["--should-read-with-state=1"] - if self.disable_proxy: - command_options += ["--disable-proxy=1"] - if self.test_evaluation_mode: - command_options += ["--test-evaluation-mode", self.test_evaluation_mode] - if self.selected_streams: - command_options += ["--stream", self.selected_streams] - command_options += ["--connection-subset", self.connection_subset] - return command_options - - def _run_command_with_proxy(self, command: str) -> List[str]: - """ - This command: - - 1. Starts a Google Cloud SQL proxy running on localhost, which is used by the connection-retriever to connect to postgres. - This is required for secure access to our internal tools. - 2. Gets the PID of the proxy so it can be killed once done. - 3. Runs the command that was passed in as input. - 4. Kills the proxy, and waits for it to exit. - 5. Exits with the command's exit code. - We need to explicitly kill the proxy in order to allow the GitHub Action to exit. - An alternative that we can consider is to run the proxy as a separate service. - - (See https://docs.dagger.io/manuals/developer/python/328492/services/ and https://cloud.google.com/sql/docs/postgres/sql-proxy#cloud-sql-auth-proxy-docker-image) - """ - run_proxy = "./cloud-sql-proxy prod-ab-cloud-proj:us-west3:prod-pgsql-replica --credentials-file /tmp/credentials.json" - run_pytest_with_proxy = dedent( - f""" - {run_proxy} & - proxy_pid=$! - {command} - pytest_exit=$? - kill $proxy_pid - wait $proxy_pid - exit $pytest_exit - """ - ) - return ["bash", "-c", f"'{run_pytest_with_proxy}'"] - - def __init__(self, context: ConnectorContext) -> None: - """Create a step to run live tests for a connector. - - Args: - context (ConnectorContext): The current test context, providing a connector object, a dagger client and a repository directory. - """ - super().__init__(context) - self.connector_image = context.docker_image.split(":")[0] - options = self.context.run_step_options.step_params.get(CONNECTOR_TEST_STEP_ID.CONNECTOR_LIVE_TESTS, {}) - - self.test_suite = self.context.run_step_options.get_item_or_default(options, "test-suite", LiveTestSuite.REGRESSION.value) - self.connection_id = self._get_connection_id(options) - self.pr_url = self._get_pr_url(options) - - self.test_dir = self.test_suite_to_dir[LiveTestSuite(self.test_suite)] - self.control_version = self.context.run_step_options.get_item_or_default(options, "control-version", None) - self.target_version = self.context.run_step_options.get_item_or_default(options, "target-version", "dev") - self.should_read_with_state = "should-read-with-state" in options - self.disable_proxy = "disable-proxy" in options - self.selected_streams = self.context.run_step_options.get_item_or_default(options, "selected-streams", None) - self.test_evaluation_mode = "strict" if self.context.connector.metadata.get("supportLevel") == "certified" else "diagnostic" - self.connection_subset = self.context.run_step_options.get_item_or_default(options, "connection-subset", "sandboxes") - self.run_id = os.getenv("GITHUB_RUN_ID") or str(int(time.time())) - - def _get_connection_id(self, options: Dict[str, List[Any]]) -> Optional[str]: - if self.context.is_pr: - connection_id = self._get_connection_from_test_connections() - self.logger.info( - f"Context is {self.context.ci_context}; got connection_id={connection_id} from metadata.yaml liveTests testConnections." - ) - else: - connection_id = self.context.run_step_options.get_item_or_default(options, "connection-id", None) - self.logger.info(f"Context is {self.context.ci_context}; got connection_id={connection_id} from input options.") - return connection_id - - def _get_pr_url(self, options: Dict[str, List[Any]]) -> Optional[str]: - if self.context.is_pr: - pull_request = self.context.pull_request.url if self.context.pull_request else None - self.logger.info(f"Context is {self.context.ci_context}; got pull_request={pull_request} from context.") - else: - pull_request = self.context.run_step_options.get_item_or_default(options, "pr-url", None) - self.logger.info(f"Context is {self.context.ci_context}; got pull_request={pull_request} from input options.") - return pull_request - - def _validate_job_can_run(self) -> None: - connector_type = self.context.connector.metadata.get("connectorType") - connector_subtype = self.context.connector.metadata.get("connectorSubtype") - assert connector_type == "source", f"Live tests can only run against source connectors, got `connectorType={connector_type}`." - if connector_subtype == "database": - assert ( - self.connection_subset == "sandboxes" - ), f"Live tests for database sources may only be run against sandbox connections, got `connection_subset={self.connection_subset}`." - - assert self.connection_id, "`connection-id` is required to run live tests." - assert self.pr_url, "`pr_url` is required to run live tests." - - if self.context.is_pr: - connection_id_is_valid = False - for test_suite in self.context.connector.metadata.get("connectorTestSuitesOptions", []): - if test_suite["suite"] == "liveTests": - assert self.connection_id in [ - option["id"] for option in test_suite.get("testConnections", []) - ], f"Connection ID {self.connection_id} was not in the list of valid test connections." - connection_id_is_valid = True - break - assert connection_id_is_valid, f"Connection ID {self.connection_id} is not a valid sandbox connection ID." - - def _get_connection_from_test_connections(self) -> Optional[str]: - for test_suite in self.context.connector.metadata.get("connectorTestSuitesOptions", []): - if test_suite["suite"] == "liveTests": - for option in test_suite.get("testConnections", []): - connection_id = option["id"] - connection_name = option["name"] - self.logger.info(f"Using connection name={connection_name}; id={connection_id}") - return connection_id - return None - - async def _run(self, connector_under_test_container: Container) -> StepResult: - """Run the regression test suite. - - Args: - connector_under_test (Container): The container holding the target connector test image. - - Returns: - StepResult: Failure or success of the regression tests with stdout and stderr. - """ - try: - self._validate_job_can_run() - except AssertionError as exc: - self.logger.info(f"Skipping live tests for {self.context.connector.technical_name} due to validation error {str(exc)}.") - return StepResult( - step=self, - status=StepStatus.SKIPPED, - exc_info=exc, - ) - - container = await self._build_test_container(await connector_under_test_container.id()) - command = self._run_command_with_proxy(" ".join(self._test_command())) - main_logger.info(f"Running command {command}") - container = container.with_(hacks.never_fail_exec(command)) - tests_artifacts_dir = str(self.local_tests_artifacts_dir) - path_to_report = f"{tests_artifacts_dir}/session_{self.run_id}/report.html" - - exit_code, stdout, stderr = await get_exec_result(container) - - try: - if ( - f"session_{self.run_id}" not in await container.directory(f"{tests_artifacts_dir}").entries() - or "report.html" not in await container.directory(f"{tests_artifacts_dir}/session_{self.run_id}").entries() - ): - main_logger.exception( - "The report file was not generated, an unhandled error likely happened during regression test execution, please check the step stderr and stdout for more details" - ) - regression_test_report = None - else: - await container.file(path_to_report).export(path_to_report) - with open(path_to_report, "r") as fp: - regression_test_report = fp.read() - except dagger.QueryError as exc: - regression_test_report = None - main_logger.exception( - "The test artifacts directory was not generated, an unhandled error likely happened during setup, please check the step stderr and stdout for more details", - exc_info=exc, - ) - - return StepResult( - step=self, - status=self.get_step_status_from_exit_code(exit_code), - stderr=stderr, - stdout=stdout, - output=container, - report=regression_test_report, - consider_in_overall_status=False if self.context.is_pr else True, - ) - - async def _build_test_container(self, target_container_id: str) -> Container: - """Create a container to run regression tests.""" - container = with_poetry(self.context) - container_requirements = ["apt-get", "install", "-y", "git", "curl", "docker.io"] - if not self.context.is_ci: - # Outside of CI we use ssh to get the connection-retriever package from airbyte-platform-internal - container_requirements += ["openssh-client"] - container = ( - container.with_exec(["apt-get", "update"], use_entrypoint=True) - .with_exec(container_requirements) - .with_exec(["bash", "-c", "curl https://sdk.cloud.google.com | bash"], use_entrypoint=True) - .with_env_variable("PATH", "/root/google-cloud-sdk/bin:$PATH", expand=True) - .with_mounted_directory("/app", self.context.live_tests_dir) - .with_workdir("/app") - # Enable dagger-in-dagger - .with_unix_socket("/var/run/docker.sock", self.dagger_client.host().unix_socket("/var/run/docker.sock")) - .with_env_variable("RUN_IN_AIRBYTE_CI", "1") - .with_file( - "/tmp/record_obfuscator.py", - self.context.get_repo_dir("tools/bin", include=["record_obfuscator.py"]).file("record_obfuscator.py"), - ) - # The connector being tested is already built and is stored in a location accessible to an inner dagger kicked off by - # regression tests. The connector can be found if you know the container ID, so we write the container ID to a file and put - # it in the regression test container. This way regression tests will use the already-built connector instead of trying to - # build their own. - .with_new_file( - f"/tmp/{slugify(self.connector_image + ':' + self.target_version)}_container_id.txt", contents=str(target_container_id) - ) - ) - - if self.context.is_ci: - container = ( - container.with_exec( - [ - "sed", - "-i", - "-E", - rf"s,git@github\.com:{self.platform_repo_url},https://github.com/{self.platform_repo_url}.git,", - "pyproject.toml", - ], - use_entrypoint=True, - ) - .with_exec( - [ - "poetry", - "source", - "add", - "--priority=supplemental", - "airbyte-platform-internal-source", - "https://github.com/airbytehq/airbyte-platform-internal.git", - ], - use_entrypoint=True, - ) - .with_secret_variable( - "CI_GITHUB_ACCESS_TOKEN", - self.context.dagger_client.set_secret( - "CI_GITHUB_ACCESS_TOKEN", self.context.ci_github_access_token.value if self.context.ci_github_access_token else "" - ), - ) - .with_exec( - [ - "/bin/sh", - "-c", - f"poetry config http-basic.airbyte-platform-internal-source {self.github_user} $CI_GITHUB_ACCESS_TOKEN", - ], - use_entrypoint=True, - ) - # Add GCP credentials from the environment and point google to their location (also required for connection-retriever) - .with_new_file("/tmp/credentials.json", contents=os.getenv("GCP_INTEGRATION_TESTER_CREDENTIALS", "")) - .with_env_variable("GOOGLE_APPLICATION_CREDENTIALS", "/tmp/credentials.json") - .with_exec( - [ - "curl", - "-o", - "cloud-sql-proxy", - "https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.11.0/cloud-sql-proxy.linux.amd64", - ], - use_entrypoint=True, - ) - .with_exec(["chmod", "+x", "cloud-sql-proxy"], use_entrypoint=True) - .with_env_variable("CI", "1") - ) - - else: - container = ( - container.with_mounted_file("/root/.ssh/id_rsa", self.dagger_client.host().file(str(Path("~/.ssh/id_rsa").expanduser()))) - .with_mounted_file("/root/.ssh/known_hosts", self.dagger_client.host().file(str(Path("~/.ssh/known_hosts").expanduser()))) - .with_mounted_file( - "/root/.config/gcloud/application_default_credentials.json", - self.dagger_client.host().file(str(Path("~/.config/gcloud/application_default_credentials.json").expanduser())), - ) - ) - - container = container.with_exec(["poetry", "lock"], use_entrypoint=True).with_exec(["poetry", "install"], use_entrypoint=True) - return container diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/java_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/java_connectors.py deleted file mode 100644 index 5c5ea5dcbe4b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/java_connectors.py +++ /dev/null @@ -1,142 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups steps made to run tests for a specific Java connector given a test context.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import anyio -from dagger import File, QueryError - -from pipelines.airbyte_ci.connectors.build_image.steps.common import LoadContainerToLocalDockerHost -from pipelines.airbyte_ci.connectors.build_image.steps.java_connectors import ( - BuildConnectorDistributionTar, - BuildConnectorImages, - dist_tar_directory_path, -) -from pipelines.airbyte_ci.connectors.build_image.steps.normalization import BuildOrPullNormalization -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.connectors.test.steps.common import AcceptanceTests -from pipelines.airbyte_ci.steps.gradle import GradleTask -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.dagger.actions.system import docker -from pipelines.helpers.execution.run_steps import StepToRun -from pipelines.helpers.utils import export_container_to_tarball -from pipelines.models.steps import STEP_PARAMS, StepResult, StepStatus - -if TYPE_CHECKING: - from typing import Callable, Dict, List, Optional - - from pipelines.helpers.execution.run_steps import RESULTS_DICT, STEP_TREE - - -class IntegrationTests(GradleTask): - """A step to run integrations tests for Java connectors using the integrationTestJava Gradle task.""" - - title = "Java Connector Integration Tests" - gradle_task_name = "integrationTestJava" - mount_connector_secrets = True - bind_to_docker_host = True - with_test_artifacts = True - - @property - def default_params(self) -> STEP_PARAMS: - return super().default_params | { - # Exclude the assemble task to avoid a circular dependency on airbyte-ci. - # The integrationTestJava gradle task depends on assemble, which in turns - # depends on buildConnectorImage to build the connector's docker image. - # At this point, the docker image has already been built. - "-x": ["assemble"], - } - - -class UnitTests(GradleTask): - """A step to run unit tests for Java connectors.""" - - title = "Java Connector Unit Tests" - gradle_task_name = "test" - bind_to_docker_host = True - with_test_artifacts = True - - -def _get_normalization_steps(context: ConnectorTestContext) -> List[StepToRun]: - normalization_image = f"{context.connector.normalization_repository}:dev" - context.logger.info(f"This connector supports normalization: will build {normalization_image}.") - normalization_steps = [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.BUILD_NORMALIZATION, - step=BuildOrPullNormalization(context, normalization_image, LOCAL_BUILD_PLATFORM), - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ) - ] - - return normalization_steps - - -def _get_acceptance_test_steps(context: ConnectorTestContext) -> List[StepToRun]: - """ - Generate the steps to run the acceptance tests for a Java connector. - """ - - # Run tests in parallel - return [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INTEGRATION, - step=IntegrationTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.INTEGRATION)), - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.ACCEPTANCE, - step=AcceptanceTests( - context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.ACCEPTANCE), concurrent_test_run=False - ), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ] - - -def get_test_steps(context: ConnectorTestContext) -> STEP_TREE: - """ - Get all the tests steps for a Java connector. - """ - - steps: STEP_TREE = [ - [StepToRun(id=CONNECTOR_TEST_STEP_ID.BUILD_TAR, step=BuildConnectorDistributionTar(context))], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.UNIT, - step=UnitTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.UNIT)), - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD_TAR], - ) - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.BUILD, - step=BuildConnectorImages(context), - args=lambda results: {"dist_dir": results[CONNECTOR_TEST_STEP_ID.BUILD_TAR].output.directory("build/distributions")}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD_TAR], - ) - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.LOAD_IMAGE_TO_LOCAL_DOCKER_HOST, - step=LoadContainerToLocalDockerHost(context, image_tag="dev"), - args=lambda results: {"containers": results[CONNECTOR_TEST_STEP_ID.BUILD].output}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ], - ] - - if context.connector.supports_normalization: - normalization_steps = _get_normalization_steps(context) - steps.append(normalization_steps) - - acceptance_test_steps = _get_acceptance_test_steps(context) - steps.append(acceptance_test_steps) - - return steps diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py deleted file mode 100644 index 5c7aa88ced54..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py +++ /dev/null @@ -1,172 +0,0 @@ -# -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. -# - -"""This module groups steps made to run tests for a specific manifest only connector given a test context.""" - -from typing import List, Sequence, Tuple - -from dagger import Container, File - -from pipelines.airbyte_ci.connectors.build_image.steps.manifest_only_connectors import BuildConnectorImages -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.connectors.test.steps.common import AcceptanceTests, IncrementalAcceptanceTests, LiveTests -from pipelines.airbyte_ci.connectors.test.steps.python_connectors import PytestStep -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.dagger.actions import secrets -from pipelines.helpers.execution.run_steps import STEP_TREE, StepToRun - - -def get_test_steps(context: ConnectorTestContext) -> STEP_TREE: - """ - Get all the tests steps for a Python connector. - """ - - return [ - [StepToRun(id=CONNECTOR_TEST_STEP_ID.BUILD, step=BuildConnectorImages(context))], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.UNIT, - step=ManifestOnlyConnectorUnitTests(context), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.ACCEPTANCE, - step=AcceptanceTests( - context, - concurrent_test_run=context.concurrent_cat, - secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.ACCEPTANCE), - ), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.CONNECTOR_LIVE_TESTS, - step=LiveTests(context), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INCREMENTAL_ACCEPTANCE, - step=IncrementalAcceptanceTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.ACCEPTANCE)), - args=lambda results: {"current_acceptance_tests_result": results[CONNECTOR_TEST_STEP_ID.ACCEPTANCE]}, - depends_on=[CONNECTOR_TEST_STEP_ID.ACCEPTANCE], - ) - ], - ] - - -class ManifestOnlyConnectorUnitTests(PytestStep): - """A step to run unit tests for a manifest-only connector""" - - title = "Manifest-only unit tests" - test_directory_name = "unit_tests" - common_test_dependencies = ["freezegun", "pytest", "pytest-mock", "requests-mock"] - - async def install_testing_environment( - self, - built_connector_container: Container, - test_config_file_name: str, - test_config_file: File, - extra_dependencies_names: Sequence[str], - ) -> Container: - """Install the testing environment for manifest-only connectors.""" - - connector_name = self.context.connector.technical_name - # Use a simpler path structure to match what the CDK expects - test_dir = "/tmp/test_environment" - connector_base_path = f"{test_dir}/airbyte-integrations/connectors" - connector_path = f"{connector_base_path}/{connector_name}" - - # Get the proper user from the container - user = await built_connector_container.user() - if not user: - user = "root" - - # Set up base test environment with reset entrypoint - test_environment = built_connector_container.with_entrypoint([]) - - # Create test directories with proper permissions - test_environment = ( - test_environment.with_user("root") # Temporarily switch to root to create directories - .with_exec(["mkdir", "-p", test_dir, connector_base_path, connector_path, f"{connector_path}/{self.test_directory_name}"]) - .with_workdir(test_dir) - ) - - # Mount the connector directory and files - connector_dir = await self.context.get_connector_dir() - - # Check what files are in the connector directory to identify components.py - connector_entries = await connector_dir.entries() - self.logger.info(f"Files in connector directory: {connector_entries}") - - # Mount the entire connector directory to ensure all files (especially components.py) are available - test_environment = test_environment.with_mounted_directory(connector_path, connector_dir) - - # Get and mount the unit_tests directory specifically - unit_tests_dir = connector_dir.directory(self.test_directory_name) - unit_tests_path = f"{connector_path}/{self.test_directory_name}" - - # Mount secrets - secret_mounting_function = await secrets.mounted_connector_secrets(self.context, f"{test_dir}/secrets", self.secrets, owner=user) - - # Apply secrets and set up Python path - test_environment = test_environment.with_(secret_mounting_function).with_env_variable( - "PYTHONPATH", f"{connector_base_path}:{connector_path}:{unit_tests_path}:{test_dir}" - ) - - # Create symlink to source-declarative-manifest - test_environment = test_environment.with_exec(["ln", "-s", "/source-declarative-manifest", connector_path]) - - # Set working directory to unit tests path - test_environment = test_environment.with_workdir(unit_tests_path) - - # Install Poetry - test_environment = test_environment.with_exec(["echo", "=== INSTALLING POETRY ==="]).with_exec(["pip", "install", "poetry"]) - - # Install dependencies directly with Poetry - test_environment = test_environment.with_exec( - ["poetry", "config", "virtualenvs.create", "false"] # Disable virtualenv creation - ).with_exec( - ["poetry", "install", "--no-root"] # Install dependencies without the root package - ) - - # Install common test dependencies. This shouldn't be needed as we're now - # using the connector's pyproject.toml, but it's here to support MO connectors - # that might have dependencies not listed in the pyproject.toml. - if self.common_test_dependencies: - test_environment = test_environment.with_exec(["echo", "=== INSTALLING COMMON TEST DEPENDENCIES ==="]).with_exec( - ["pip", "install"] + self.common_test_dependencies - ) - - # Set ownership of all files to the proper user and switch to that user - test_environment = test_environment.with_exec(["chown", "-R", f"{user}:{user}", test_dir]).with_user(user) - - return test_environment - - async def get_config_file_name_and_file(self) -> Tuple[str, File]: - """ - Get the config file name and file to use for pytest. - For manifest-only connectors, we expect the poetry config to be found - in the unit_tests directory. - """ - connector_name = self.context.connector.technical_name - connector_dir = await self.context.get_connector_dir() - unit_tests_dir = connector_dir.directory(self.test_directory_name) - unit_tests_entries = await unit_tests_dir.entries() - if self.PYPROJECT_FILE_NAME in unit_tests_entries: - config_file_name = self.PYPROJECT_FILE_NAME - test_config = unit_tests_dir.file(self.PYPROJECT_FILE_NAME) - self.logger.info(f"Found {self.PYPROJECT_FILE_NAME} in the unit_tests directory for {connector_name}, using it for testing.") - return config_file_name, test_config - else: - raise FileNotFoundError(f"Could not find {self.PYPROJECT_FILE_NAME} in the unit_tests directory for {connector_name}.") - - def get_pytest_command(self, test_config_file_name: str) -> List[str]: - """Get the pytest command to run.""" - cmd = ["pytest", "-v", ".", "-c", test_config_file_name] + self.params_as_cli_options - return ["poetry", "run"] + cmd diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/python_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/python_connectors.py deleted file mode 100644 index 5bb2f962f3c0..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/python_connectors.py +++ /dev/null @@ -1,326 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups steps made to run tests for a specific Python connector given a test context.""" - -from abc import ABC, abstractmethod -from typing import List, Sequence, Tuple - -import dpath.util -from dagger import Container, File - -import pipelines.dagger.actions.python.common -import pipelines.dagger.actions.system.docker -from pipelines import hacks -from pipelines.airbyte_ci.connectors.build_image.steps.python_connectors import BuildConnectorImages -from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID -from pipelines.airbyte_ci.connectors.test.context import ConnectorTestContext -from pipelines.airbyte_ci.connectors.test.steps.common import AcceptanceTests, IncrementalAcceptanceTests, LiveTests -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.dagger.actions import secrets -from pipelines.dagger.actions.python.poetry import with_poetry -from pipelines.helpers.execution.run_steps import STEP_TREE, StepToRun -from pipelines.helpers.utils import raise_if_not_user -from pipelines.models.steps import STEP_PARAMS, Step, StepResult - -# Pin the PyAirbyte version to avoid updates from breaking CI -PYAIRBYTE_VERSION = "0.35.1" - - -class PytestStep(Step, ABC): - """An abstract class to run pytest tests and evaluate success or failure according to pytest logs.""" - - context: ConnectorTestContext - - PYTEST_INI_FILE_NAME = "pytest.ini" - PYPROJECT_FILE_NAME = "pyproject.toml" - common_test_dependencies: List[str] = [] - - skipped_exit_code = 5 - bind_to_docker_host = False - accept_extra_params = True - - @property - def default_params(self) -> STEP_PARAMS: - """Default pytest options. - - Returns: - dict: The default pytest options. - """ - return super().default_params | { - "-s": [], # Disable capturing stdout/stderr in pytest - } - - @property - @abstractmethod - def test_directory_name(self) -> str: - raise NotImplementedError("test_directory_name must be implemented in the child class.") - - @property - def extra_dependencies_names(self) -> Sequence[str]: - if self.context.connector.is_using_poetry: - return ("dev",) - return ("dev", "tests") - - async def _run(self, connector_under_test: Container) -> StepResult: - """Run all pytest tests declared in the test directory of the connector code. - - Args: - connector_under_test (Container): The connector under test container. - - Returns: - StepResult: Failure or success of the unit tests with stdout and stdout. - """ - if not await self.check_if_tests_are_available(self.test_directory_name): - return self.skip(f"No {self.test_directory_name} directory found in the connector.") - - test_config_file_name, test_config_file = await self.get_config_file_name_and_file() - test_environment = await self.install_testing_environment( - connector_under_test, test_config_file_name, test_config_file, self.extra_dependencies_names - ) - - pytest_command = self.get_pytest_command(test_config_file_name) - - if self.bind_to_docker_host: - test_environment = await pipelines.dagger.actions.system.docker.with_bound_docker_host(self.context, test_environment) - - test_execution = test_environment.with_exec(pytest_command) - - return await self.get_step_result(test_execution) - - def get_pytest_command(self, test_config_file_name: str) -> List[str]: - """Get the pytest command to run. - - Returns: - List[str]: The pytest command to run. - """ - cmd = ["pytest", self.test_directory_name, "-c", test_config_file_name] + self.params_as_cli_options - if self.context.connector.is_using_poetry: - return ["poetry", "run"] + cmd - return cmd - - async def check_if_tests_are_available(self, test_directory_name: str) -> bool: - """Check if the tests are available in the connector directory. - - Returns: - bool: True if the tests are available. - """ - connector_dir = await self.context.get_connector_dir() - connector_dir_entries = await connector_dir.entries() - return test_directory_name in connector_dir_entries - - async def get_config_file_name_and_file(self) -> Tuple[str, File]: - """Get the config file name and file to use for pytest. - - The order of priority is: - - pytest.ini file in the connector directory - - pyproject.toml file in the connector directory - - pyproject.toml file in the repository directory - - Returns: - Tuple[str, File]: The config file name and file to use for pytest. - """ - connector_dir = await self.context.get_connector_dir() - connector_dir_entries = await connector_dir.entries() - if self.PYTEST_INI_FILE_NAME in connector_dir_entries: - config_file_name = self.PYTEST_INI_FILE_NAME - test_config = (await self.context.get_connector_dir(include=[self.PYTEST_INI_FILE_NAME])).file(self.PYTEST_INI_FILE_NAME) - self.logger.info(f"Found {self.PYTEST_INI_FILE_NAME}, using it for testing.") - elif self.PYPROJECT_FILE_NAME in connector_dir_entries: - config_file_name = self.PYPROJECT_FILE_NAME - test_config = (await self.context.get_connector_dir(include=[self.PYPROJECT_FILE_NAME])).file(self.PYPROJECT_FILE_NAME) - self.logger.info(f"Found {self.PYPROJECT_FILE_NAME} at connector level, using it for testing.") - else: - config_file_name = f"global_{self.PYPROJECT_FILE_NAME}" - test_config = (await self.context.get_repo_dir(include=[self.PYPROJECT_FILE_NAME])).file(self.PYPROJECT_FILE_NAME) - self.logger.info(f"Found {self.PYPROJECT_FILE_NAME} at repo level, using it for testing.") - return config_file_name, test_config - - async def install_testing_environment( - self, - built_connector_container: Container, - test_config_file_name: str, - test_config_file: File, - extra_dependencies_names: Sequence[str], - ) -> Container: - """Install the connector with the extra dependencies in /test_environment. - - Args: - extra_dependencies_names (List[str]): Extra dependencies to install. - - Returns: - Container: The container with the test environment installed. - """ - user = await BuildConnectorImages.get_image_user(built_connector_container) - secret_mounting_function = await secrets.mounted_connector_secrets(self.context, "secrets", self.secrets, owner=user) - - container_with_test_deps = ( - # Install the connector python package in /test_environment with the extra dependencies - await pipelines.dagger.actions.python.common.with_python_connector_installed( - self.context, - # Reset the entrypoint to run non airbyte commands - built_connector_container.with_entrypoint([]), - str(self.context.connector.code_directory), - user, - additional_dependency_groups=extra_dependencies_names, - ) - ) - if self.common_test_dependencies: - container_with_test_deps = container_with_test_deps.with_user("root").with_exec( - ["pip", "install"] + self.common_test_dependencies - ) - - container_with_test_deps = ( - container_with_test_deps - # Mount the test config file - .with_mounted_file(test_config_file_name, test_config_file, owner=user) - # Mount the secrets - .with_(secret_mounting_function) - .with_env_variable("PYTHONPATH", ".") - # Make sure all files that were created or mounted under /airbyte are owned by the user - .with_user("root") - .with_exec(["chown", "-R", f"{user}:{user}", "/airbyte"]) - .with_user(user) - ) - - await raise_if_not_user(container_with_test_deps, user) - return container_with_test_deps - - -class UnitTests(PytestStep): - """A step to run the connector unit tests with Pytest.""" - - title = "Unit tests" - test_directory_name = "unit_tests" - - common_test_dependencies = ["pytest-cov==4.1.0"] - MINIMUM_COVERAGE_FOR_CERTIFIED_CONNECTORS = 90 - - @property - def default_params(self) -> STEP_PARAMS: - """Make sure the coverage computation is run for the unit tests. - - Returns: - dict: The default pytest options. - """ - coverage_options = {"--cov": [self.context.connector.technical_name.replace("-", "_")]} - if self.context.connector.support_level == "certified": - coverage_options["--cov-fail-under"] = [str(self.MINIMUM_COVERAGE_FOR_CERTIFIED_CONNECTORS)] - return super().default_params | coverage_options - - -class PyAirbyteValidation(Step): - """Validate the connector can be installed and invoked via Python, using PyAirbyte. - - When this fails, it generally signals that the connector is not installable or not - runnable in a Python environment. The most common reasons for this are: - 1. Conflicting dependencies. - 2. Missing dependency declarations. - 3. Incorrect or invalid CLI entrypoints. - """ - - title = "Python CLI smoke test using PyAirbyte" - - context: ConnectorTestContext - - async def _run(self, connector_under_test: Container) -> StepResult: - """Run all pytest tests declared in the test directory of the connector code. - Args: - connector_under_test (Container): The connector under test container. - Returns: - StepResult: Failure or success of the unit tests with stdout and stdout. - """ - if dpath.util.get(self.context.connector.metadata, "remoteRegistries/pypi/enabled", default=False) is False: - return self.skip("Connector is not flagged for PyPI publish, skipping Python CLI validation.") - - test_environment = await self.install_testing_environment(with_poetry(self.context)) - test_execution = test_environment.with_( - hacks.never_fail_exec( - [ - "pyab", - "validate", - f"--connector={self.context.connector.technical_name}", - "--pip-url='.'", - ] - ) - ) - - return await self.get_step_result(test_execution) - - async def install_testing_environment( - self, - built_connector_container: Container, - ) -> Container: - """Add PyAirbyte and secrets to the test environment.""" - context: ConnectorTestContext = self.context - - container_with_test_deps = await pipelines.dagger.actions.python.common.with_python_package( - self.context, built_connector_container.with_entrypoint([]), str(context.connector.code_directory) - ) - return container_with_test_deps.with_exec(["pip", "install", f"airbyte=={PYAIRBYTE_VERSION}"], use_entrypoint=True) - - -class IntegrationTests(PytestStep): - """A step to run the connector integration tests with Pytest.""" - - title = "Integration tests" - test_directory_name = "integration_tests" - - bind_to_docker_host = True - - -def get_test_steps(context: ConnectorTestContext) -> STEP_TREE: - """ - Get all the tests steps for a Python connector. - """ - - return [ - [StepToRun(id=CONNECTOR_TEST_STEP_ID.BUILD, step=BuildConnectorImages(context))], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.UNIT, - step=UnitTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.UNIT)), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ) - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INTEGRATION, - step=IntegrationTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.INTEGRATION)), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.PYTHON_CLI_VALIDATION, - step=PyAirbyteValidation(context), - args=lambda results: {"connector_under_test": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.ACCEPTANCE, - step=AcceptanceTests( - context, - concurrent_test_run=context.concurrent_cat, - secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.ACCEPTANCE), - ), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - StepToRun( - id=CONNECTOR_TEST_STEP_ID.CONNECTOR_LIVE_TESTS, - step=LiveTests(context), - args=lambda results: {"connector_under_test_container": results[CONNECTOR_TEST_STEP_ID.BUILD].output[LOCAL_BUILD_PLATFORM]}, - depends_on=[CONNECTOR_TEST_STEP_ID.BUILD], - ), - ], - [ - StepToRun( - id=CONNECTOR_TEST_STEP_ID.INCREMENTAL_ACCEPTANCE, - step=IncrementalAcceptanceTests(context, secrets=context.get_secrets_for_step_id(CONNECTOR_TEST_STEP_ID.ACCEPTANCE)), - args=lambda results: {"current_acceptance_tests_result": results[CONNECTOR_TEST_STEP_ID.ACCEPTANCE]}, - depends_on=[CONNECTOR_TEST_STEP_ID.ACCEPTANCE], - ) - ], - ] diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/templates/test_report.html.j2 b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/templates/test_report.html.j2 deleted file mode 100644 index ea732afba2eb..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/templates/test_report.html.j2 +++ /dev/null @@ -1,201 +0,0 @@ - - - - {{ connector_name }} test report - - - - - - -

{{ connector_name }} test report

-
    -
  • Created at: {{ created_at }} UTC
  • -
  • Run duration: {{ format_duration(run_duration) }}
  • - {% if commit_url %} -
  • Commit: {{ git_revision[:10] }}
  • - {% else %} -
  • Commit: {{ git_revision[:10] }}
  • - {% endif %} -
  • Branch: {{ git_branch }}
  • - {% if gha_workflow_run_url %} -
    -
  • Github Actions logs
  • - {% endif %} - {% if dagger_logs_url %} -
  • Dagger logs
  • - {% endif %} - {% if dagger_cloud_url %} -
  • Dagger Cloud UI
  • - {% endif %} -
-

Summary

- - - - - - - {% for step_result in step_results %} - - - - - - {% endfor %} -
StepStatusDuration
{{ step_result.step.title }}{{ step_result.status }}{{ format_duration(step_result.step.run_duration) }}
-

Step details

- {% for step_result in step_results %} -
- - {% if step_result.status == StepStatus.SUCCESS %} - - {% elif step_result.status == StepStatus.FAILURE %} - {% if not step_result.consider_in_overall_status %} - - {% else %} - - {% endif %} - {% else %} - - {% endif %} -
- {% if step_result_to_artifact_links[step_result.step.title] %} -

Artifacts

-
    - {% for artifact in step_result_to_artifact_links[step_result.step.title] %} -
  • {{ artifact.name }}
  • - {% endfor %} -
- {% endif %} -
- {% if step_result.report %} -
{{ step_result.report }}
- {% else %} - {% if step_result.stdout %} - Standard output(): -
{{ step_result.stdout|e }}
- {% endif %} - {% if step_result.stderr %} - Standard error(): -
{{ step_result.stderr|e }}
- {% endif %} - {% endif %} -
-
-
- {% endfor %} -

These reports are generated from this code, please reach out to the Connector Operations team for support.

- - diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/commands.py deleted file mode 100644 index 03111abf2ef1..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/commands.py +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import List - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.up_to_date.pipeline import run_connector_up_to_date_pipeline -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.helpers.connectors.command import run_connector_pipeline - - -@click.command( - cls=DaggerPipelineCommand, - short_help="Get the selected connectors up to date.", -) -@click.option( - "--dep", - type=str, - multiple=True, - default=[], - help="Give a specific set of `poetry add` dependencies to update. For example: --dep airbyte-cdk==0.80.0 --dep pytest@^6.2", -) -@click.option( - "--create-prs", - is_flag=True, - type=bool, - default=False, - help="Create pull requests for updated connectors", -) -@click.option( - "--auto-merge", - is_flag=True, - type=bool, - default=False, - help="Set the auto-merge label on the create pull requests", -) -@click.option( - "--no-bump", - is_flag=True, - type=bool, - default=False, - help="Don't bump or changelog", -) -@click.option( - "--open-reports", - is_flag=True, - type=bool, - default=False, - help="Auto open reports in browser", -) -@click.pass_context -async def up_to_date( - ctx: click.Context, - dep: List[str], - create_prs: bool, - auto_merge: bool, - no_bump: bool, - open_reports: bool, -) -> bool: - if create_prs and not ctx.obj["ci_github_access_token"]: - raise click.ClickException( - "GitHub access token is required to create or simulate a pull request. Set the CI_GITHUB_ACCESS_TOKEN environment variable." - ) - - return await run_connector_pipeline( - ctx, - "Get Python connector up to date", - open_reports, - run_connector_up_to_date_pipeline, - create_prs, - auto_merge, - dep, - not no_bump, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/pipeline.py deleted file mode 100644 index ec91373fb279..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/pipeline.py +++ /dev/null @@ -1,204 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from datetime import datetime, timezone -from pathlib import Path -from typing import TYPE_CHECKING - -from jinja2 import Environment, PackageLoader, select_autoescape - -from pipelines import hacks -from pipelines.airbyte_ci.connectors.build_image.steps import run_connector_build -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.airbyte_ci.steps.base_image import UpdateBaseImageMetadata -from pipelines.airbyte_ci.steps.bump_version import BumpConnectorVersion -from pipelines.airbyte_ci.steps.changelog import AddChangelogEntry -from pipelines.airbyte_ci.steps.pull_request import CreateOrUpdatePullRequest -from pipelines.consts import LOCAL_BUILD_PLATFORM - -from .steps import DependencyUpdate, GetDependencyUpdates, PoetryUpdate - -if TYPE_CHECKING: - from typing import Dict, Iterable, List, Set, Tuple - - from anyio import Semaphore - from github import PullRequest - - from pipelines.models.steps import StepResult - -UP_TO_DATE_PR_LABEL = "up-to-date" -AUTO_MERGE_PR_LABEL = "auto-merge" -DEFAULT_PR_LABELS = [UP_TO_DATE_PR_LABEL] -BUMP_TYPE = "patch" -CHANGELOG_ENTRY_COMMENT = "Update dependencies" - - -## HELPER FUNCTIONS - - -def get_pr_body(context: ConnectorContext, step_results: Iterable[StepResult], dependency_updates: Iterable[DependencyUpdate]) -> str: - env = Environment( - loader=PackageLoader("pipelines.airbyte_ci.connectors.up_to_date"), - autoescape=select_autoescape(), - trim_blocks=False, - lstrip_blocks=True, - ) - template = env.get_template("up_to_date_pr_body.md.j2") - latest_docker_image = f"{context.connector.metadata['dockerRepository']}:latest" - return template.render( - connector_technical_name=context.connector.technical_name, - step_results=step_results, - dependency_updates=dependency_updates, - connector_latest_docker_image=latest_docker_image, - ) - - -def get_pr_creation_arguments( - modified_files: Iterable[Path], - context: ConnectorContext, - step_results: Iterable[StepResult], - dependency_updates: Iterable[DependencyUpdate], -) -> Tuple[Tuple, Dict]: - return ( - (modified_files,), - { - "branch_id": f"up-to-date/{context.connector.technical_name}", - "commit_message": "[up-to-date]" # << We can skip Vercel builds if this is in the commit message - + "; ".join(step_result.step.title for step_result in step_results if step_result.success), - "pr_title": f"deps({context.connector.technical_name}): 🐙 update dependencies [{datetime.now(timezone.utc).strftime('%Y-%m-%d')}]", - "pr_body": get_pr_body(context, step_results, dependency_updates), - }, - ) - - -## MAIN FUNCTION -async def run_connector_up_to_date_pipeline( - context: ConnectorContext, - semaphore: "Semaphore", - create_pull_request: bool = False, - auto_merge: bool = False, - specific_dependencies: List[str] = [], - bump_connector_version: bool = True, -) -> ConnectorReport: - async with semaphore: - async with context: - step_results: List[StepResult] = [] - all_modified_files: Set[Path] = set() - created_pr: PullRequest.PullRequest | None = None - new_version: str | None = None - - connector_directory = await context.get_connector_dir() - upgrade_base_image_in_metadata = UpdateBaseImageMetadata(context, connector_directory) - upgrade_base_image_in_metadata_result = await upgrade_base_image_in_metadata.run() - step_results.append(upgrade_base_image_in_metadata_result) - if upgrade_base_image_in_metadata_result.success: - connector_directory = upgrade_base_image_in_metadata_result.output["updated_connector_directory"] - exported_modified_files = await upgrade_base_image_in_metadata.export_modified_files(context.connector.code_directory) - context.logger.info(f"Exported files following the base image upgrade: {exported_modified_files}") - all_modified_files.update(exported_modified_files) - - if context.connector.is_using_poetry: - # We run the poetry update step after the base image upgrade because the base image upgrade may change the python environment - poetry_update = PoetryUpdate(context, connector_directory, specific_dependencies=specific_dependencies) - poetry_update_result = await poetry_update.run() - step_results.append(poetry_update_result) - if poetry_update_result.success: - exported_modified_files = await poetry_update.export_modified_files(context.connector.code_directory) - context.logger.info(f"Exported files following the Poetry update: {exported_modified_files}") - all_modified_files.update(exported_modified_files) - connector_directory = poetry_update_result.output - - one_previous_step_is_successful = any(step_result.success for step_result in step_results) - - # NOTE: - # BumpConnectorVersion will already work for manifest-only and Java connectors too - if bump_connector_version and one_previous_step_is_successful: - bump_version = BumpConnectorVersion(context, connector_directory, BUMP_TYPE) - bump_version_result = await bump_version.run() - step_results.append(bump_version_result) - if bump_version_result.success: - new_version = bump_version.new_version - exported_modified_files = await bump_version.export_modified_files(context.connector.code_directory) - context.logger.info(f"Exported files following the version bump: {exported_modified_files}") - all_modified_files.update(exported_modified_files) - - # Only create the PR if the flag is on, and if there's anything to contribute - create_pull_request = create_pull_request and one_previous_step_is_successful and bump_version_result.success - - # We run build and get dependency updates only if we are creating a pull request, - # to fill the PR body with the correct information about what exactly got updated. - if create_pull_request: - # Building connector images is also universal across connector technologies. - build_result = await run_connector_build(context) - step_results.append(build_result) - dependency_updates: List[DependencyUpdate] = [] - - if build_result.success: - built_connector_container = build_result.output[LOCAL_BUILD_PLATFORM] - - # Dependencies here mean Syft deps in the container image itself, not framework-level deps. - get_dependency_updates = GetDependencyUpdates(context) - dependency_updates_result = await get_dependency_updates.run(built_connector_container) - step_results.append(dependency_updates_result) - dependency_updates = dependency_updates_result.output - - # We open a PR even if build is failing. - # This might allow a developer to fix the build in the PR. - initial_labels = DEFAULT_PR_LABELS + ([AUTO_MERGE_PR_LABEL] if auto_merge else []) - initial_pr_creation = CreateOrUpdatePullRequest( - context, - labels=initial_labels, - # Reduce pressure on rate limit, since we need to push a - # a follow-on commit anyway once we have the PR number: - skip_ci=True, - ) - pr_creation_args, pr_creation_kwargs = get_pr_creation_arguments( - all_modified_files, context, step_results, dependency_updates - ) - initial_pr_creation_result = await initial_pr_creation.run(*pr_creation_args, **pr_creation_kwargs) - step_results.append(initial_pr_creation_result) - if initial_pr_creation_result.success: - created_pr = initial_pr_creation_result.output - - if new_version and created_pr: - documentation_directory = await context.get_repo_dir( - include=[str(context.connector.local_connector_documentation_directory)] - ).directory(str(context.connector.local_connector_documentation_directory)) - - changelog_entry_comment = hacks.determine_changelog_entry_comment( - upgrade_base_image_in_metadata_result, CHANGELOG_ENTRY_COMMENT - ) - add_changelog_entry = AddChangelogEntry( - context, documentation_directory, new_version, changelog_entry_comment, created_pr.number - ) - add_changelog_entry_result = await add_changelog_entry.run() - step_results.append(add_changelog_entry_result) - if add_changelog_entry_result.success: - # File path modified by the changelog entry step are relative to the repo root - exported_modified_files = await add_changelog_entry.export_modified_files( - context.connector.local_connector_documentation_directory - ) - context.logger.info(f"Exported files following the changelog entry: {exported_modified_files}") - all_modified_files.update(exported_modified_files) - final_labels = DEFAULT_PR_LABELS + [AUTO_MERGE_PR_LABEL] if auto_merge else DEFAULT_PR_LABELS - post_changelog_pr_update = CreateOrUpdatePullRequest( - context, - labels=final_labels, - # For this 'up-to-date' pipeline, we want GitHub to merge organically - # if/when all required checks pass. Maintainers can also easily disable - # auto-merge if they want to review or update the PR before merging. - github_auto_merge=auto_merge, - skip_ci=False, - ) - pr_creation_args, pr_creation_kwargs = get_pr_creation_arguments( - all_modified_files, context, step_results, dependency_updates - ) - post_changelog_pr_update_result = await post_changelog_pr_update.run(*pr_creation_args, **pr_creation_kwargs) - step_results.append(post_changelog_pr_update_result) - - report = ConnectorReport(context, step_results, name="UP-TO-DATE RESULTS") - context.report = report - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/steps.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/steps.py deleted file mode 100644 index 45bfecc49cfa..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/steps.py +++ /dev/null @@ -1,173 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -import os -import re -from dataclasses import dataclass -from enum import Enum -from pathlib import Path -from typing import TYPE_CHECKING - -import dagger -from connector_ops.utils import POETRY_LOCK_FILE_NAME, PYPROJECT_FILE_NAME # type: ignore -from deepdiff import DeepDiff # type: ignore - -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.models.steps import Step, StepModifyingFiles, StepResult, StepStatus - -if TYPE_CHECKING: - from typing import List - - -class PoetryUpdate(StepModifyingFiles): - context: ConnectorContext - dev: bool - specified_versions: dict[str, str] - title = "Update versions of libraries in poetry" - - def __init__( - self, - context: PipelineContext, - connector_directory: dagger.Directory, - dev: bool = False, - specific_dependencies: List[str] | None = None, - ) -> None: - super().__init__(context, connector_directory) - self.dev = dev - self.specified_versions = self.parse_specific_dependencies(specific_dependencies) if specific_dependencies else {} - - @staticmethod - def parse_specific_dependencies(specific_dependencies: List[str]) -> dict[str, str]: - package_name_pattern = r"^(\w+)[@><=]([^\s]+)$" - versions: dict[str, str] = {} - for dep in specific_dependencies: - match = re.match(package_name_pattern, dep) - if match: - package = match.group(1) - versions[package] = dep - else: - raise ValueError(f"Invalid dependency name: {dep}") - return versions - - async def _run(self) -> StepResult: - connector_directory = self.modified_directory - if PYPROJECT_FILE_NAME not in await connector_directory.entries(): - return StepResult(step=self, status=StepStatus.SKIPPED, stderr=f"Connector does not have a {PYPROJECT_FILE_NAME}") - - base_image_name = self.context.connector.metadata["connectorBuildOptions"]["baseImage"] - base_container = self.dagger_client.container(platform=LOCAL_BUILD_PLATFORM).from_(base_image_name) - connector_container = base_container.with_mounted_directory("/connector", connector_directory).with_workdir("/connector") - original_poetry_lock = await connector_container.file(POETRY_LOCK_FILE_NAME).contents() - original_pyproject_file = await connector_container.file(PYPROJECT_FILE_NAME).contents() - - try: - for package, version in self.specified_versions.items(): - if not self.dev: - self.logger.info(f"Add {package} {version} to main dependencies") - connector_container = await connector_container.with_exec(["poetry", "add", f"{package}{version}"], use_entrypoint=True) - else: - self.logger.info(f"Add {package} {version} to dev dependencies") - connector_container = await connector_container.with_exec( - ["poetry", "add", f"{package}{version}", "--group=dev"], use_entrypoint=True - ) - - connector_container = await connector_container.with_exec(["poetry", "update"], use_entrypoint=True) - self.logger.info(await connector_container.stdout()) - except dagger.ExecError as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e)) - - if ( - original_poetry_lock != await connector_container.file(POETRY_LOCK_FILE_NAME).contents() - or original_pyproject_file != await connector_container.file(PYPROJECT_FILE_NAME).contents() - ): - self.modified_directory = await connector_container.directory(".") - self.modified_files = [POETRY_LOCK_FILE_NAME, PYPROJECT_FILE_NAME] - return StepResult(step=self, status=StepStatus.SUCCESS, output=connector_container.directory(".")) - - return StepResult(step=self, status=StepStatus.SKIPPED, stdout="No changes in poetry.lock or pyproject.toml") - - -class DependencyUpdateType(Enum): - ADDED = "added" - REMOVED = "removed" - UPDATED = "updated" - - -@dataclass -class DependencyUpdate: - package_type: str - package_name: str - update_type: DependencyUpdateType - new_version: str | None = None - previous_version: str | None = None - - -class GetDependencyUpdates(Step): - SYFT_DOCKER_IMAGE = "anchore/syft:v1.6.0" - context: ConnectorContext - title: str = "Get dependency updates" - - def get_syft_container(self) -> dagger.Container: - home_dir = os.path.expanduser("~") - config_path = os.path.join(home_dir, ".docker", "config.json") - config_file = self.dagger_client.host().file(config_path) - return ( - self.dagger_client.container() - .from_(self.SYFT_DOCKER_IMAGE) - .with_mounted_file("/config/config.json", config_file) - .with_env_variable("DOCKER_CONFIG", "/config") - # Syft requires access to the docker daemon. We share the host's docker socket with the Syft container. - .with_unix_socket("/var/run/docker.sock", self.dagger_client.host().unix_socket("/var/run/docker.sock")) - ) - - @property - def latest_connector_docker_image_address(self) -> str: - docker_image_name = self.context.connector.metadata["dockerRepository"] - return f"{docker_image_name}:latest" - - async def get_sbom_from_latest_image(self) -> str: - syft_container = self.get_syft_container() - return await syft_container.with_exec([self.latest_connector_docker_image_address, "-o", "syft-json"], use_entrypoint=True).stdout() - - async def get_sbom_from_container(self, container: dagger.Container) -> str: - oci_tarball_path = Path(f"/tmp/{self.context.connector.technical_name}-{self.context.connector.version}.tar") - await container.export(str(oci_tarball_path)) - syft_container = self.get_syft_container() - container_sbom = await ( - syft_container.with_mounted_file("/tmp/oci.tar", self.dagger_client.host().file(str(oci_tarball_path))) - .with_exec(["/tmp/oci.tar", "-o", "syft-json"], use_entrypoint=True) - .stdout() - ) - oci_tarball_path.unlink() - return container_sbom - - def get_dependency_updates(self, raw_latest_sbom: str, raw_current_sbom: str) -> List[DependencyUpdate]: - latest_sbom = json.loads(raw_latest_sbom) - current_sbom = json.loads(raw_current_sbom) - latest_packages = {(dep["type"], dep["name"]): dep["version"] for dep in latest_sbom["artifacts"]} - current_packages = {(dep["type"], dep["name"]): dep["version"] for dep in current_sbom["artifacts"]} - diff = DeepDiff(latest_packages, current_packages) - dependency_updates = [] - diff_type_to_update_type = [ - ("values_changed", DependencyUpdateType.UPDATED), - ("dictionary_item_added", DependencyUpdateType.ADDED), - ("dictionary_item_removed", DependencyUpdateType.REMOVED), - ] - for diff_type, update_type in diff_type_to_update_type: - for change in diff.tree.get(diff_type, []): - package_type, package_name = change.get_root_key() - previous_version, new_version = change.t1, change.t2 - dependency_updates.append( - DependencyUpdate(package_type, package_name, update_type, previous_version=previous_version, new_version=new_version) - ) - return dependency_updates - - async def _run(self, target_connector_container: dagger.Container) -> StepResult: - latest_sbom = await self.get_sbom_from_latest_image() - current_sbom = await self.get_sbom_from_container(target_connector_container) - dependency_updates = self.get_dependency_updates(latest_sbom, current_sbom) - return StepResult(step=self, status=StepStatus.SUCCESS, output=dependency_updates) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/templates/up_to_date_pr_body.md.j2 b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/templates/up_to_date_pr_body.md.j2 deleted file mode 100644 index 6cfe122e6029..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/up_to_date/templates/up_to_date_pr_body.md.j2 +++ /dev/null @@ -1,32 +0,0 @@ -# Update {{ connector_technical_name }} - -This PR was autogenerated by running `airbyte-ci connectors --name={{ connector_technical_name }} up_to_date --pull` - -We've set the `auto-merge` label on it, so it will be automatically merged if the CI pipelines pass. -If you don't want to merge it automatically, please remove the `auto-merge` label. -Please reach out to the Airbyte Connector Tooling team if you have any questions or concerns. - -{% if step_results %} -## Operations -{% for step_result in step_results %} -- {{step_result.step.title}}: {{ step_result.status.value }} -{% endfor %} -{% endif %} - -{% if dependency_updates %} -## Dependency updates - -We use [`syft`](https://github.com/anchore/syft) to generate a SBOM for the latest connector version and the one from the PR. -It allows us to spot the dependencies that have been updated at all levels and for all types of dependencies (system, python, java etc.). -Here are the dependencies that have been updated compared to `{{ connector_latest_docker_image }}`. -Keep in mind that `:latest` does not always match the connector code on the main branch. -It is the latest released connector image when the head commit of this branch was created. - -| Type | Name | State | Previous Version | New Version | -|------|------|-------|-------------|------------------| -{%- for dependency_update in dependency_updates %} -| {{ dependency_update.package_type }} | {{ dependency_update.package_name }} | {{ dependency_update.update_type.value }} | {{ dependency_update.previous_version }} | **{{ dependency_update.new_version }}** | -{%- endfor %} -{% endif %} - - diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/commands.py deleted file mode 100644 index 16519b4e6d6e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/commands.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.airbyte_ci.connectors.upgrade_cdk.pipeline import run_connector_cdk_upgrade_pipeline -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand - - -@click.command(cls=DaggerPipelineCommand, short_help="Upgrade CDK version") -@click.argument("target-cdk-version", type=str, default="latest") -@click.pass_context -async def upgrade_cdk( - ctx: click.Context, - target_cdk_version: str, -) -> bool: - """Upgrade CDK version""" - - connectors_contexts = [ - ConnectorContext( - pipeline_name=f"Upgrade CDK version of connector {connector.technical_name}", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - ci_git_user=ctx.obj["ci_git_user"], - ci_github_access_token=ctx.obj["ci_github_access_token"], - enable_report_auto_open=False, - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - - await run_connectors_pipelines( - connectors_contexts, - run_connector_cdk_upgrade_pipeline, - "Upgrade CDK version pipeline", - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - target_cdk_version, - ) - - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/pipeline.py deleted file mode 100644 index 193ee6885e2c..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/upgrade_cdk/pipeline.py +++ /dev/null @@ -1,177 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import os -import re -from typing import TYPE_CHECKING - -import toml -from connector_ops.utils import ConnectorLanguage # type: ignore -from dagger import Directory - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.consts import LOCAL_BUILD_PLATFORM -from pipelines.helpers import git -from pipelines.helpers.connectors import cdk_helpers -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from typing import Optional - - from anyio import Semaphore - -# GLOBALS - -POETRY_LOCK_FILENAME = "poetry.lock" -PYPROJECT_FILENAME = "pyproject.toml" - - -class SetCDKVersion(Step): - context: ConnectorContext - title = "Set CDK Version" - - def __init__( - self, - context: ConnectorContext, - new_version: str, - ) -> None: - super().__init__(context) - self.new_version = new_version - - async def _run(self) -> StepResult: - context = self.context - - try: - og_connector_dir = await context.get_connector_dir() - if self.context.connector.language in [ConnectorLanguage.PYTHON, ConnectorLanguage.LOW_CODE]: - updated_connector_dir = await self.upgrade_cdk_version_for_python_connector(og_connector_dir) - elif self.context.connector.language is ConnectorLanguage.JAVA: - updated_connector_dir = await self.upgrade_cdk_version_for_java_connector(og_connector_dir) - else: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout=f"The upgrade-cdk command does not support {self.context.connector.language} connectors.", - ) - - if updated_connector_dir is None: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=f"No changes were made to the CDK version for connector {self.context.connector.technical_name}", - ) - diff = og_connector_dir.diff(updated_connector_dir) - exported_successfully = await diff.export(os.path.join(git.get_git_repo_path(), context.connector.code_directory)) - if not exported_successfully: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stdout="Could not export diff to local git repo.", - ) - return StepResult(step=self, status=StepStatus.SUCCESS, stdout=f"Updated CDK version to {self.new_version}", output=diff) - except ValueError as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=f"Could not set CDK version: {e}", - exc_info=e, - ) - - async def upgrade_cdk_version_for_java_connector(self, og_connector_dir: Directory) -> Directory: - if "build.gradle" not in await og_connector_dir.entries(): - raise ValueError(f"Java connector {self.context.connector.technical_name} does not have a build.gradle file.") - - build_gradle = og_connector_dir.file("build.gradle") - build_gradle_content = await build_gradle.contents() - - old_cdk_version_required = re.search(r"cdkVersionRequired *= *'(?P[0-9]*\.[0-9]*\.[0-9]*)?'", build_gradle_content) - # If there is no airbyte-cdk dependency, add the version - if old_cdk_version_required is None: - raise ValueError("Could not find airbyte-cdk dependency in build.gradle") - - if self.new_version == "latest": - new_version = await cdk_helpers.get_latest_java_cdk_version(self.context.get_repo_dir()) - else: - new_version = self.new_version - - updated_build_gradle = build_gradle_content.replace(old_cdk_version_required.group("version"), new_version) - - use_local_cdk = re.search(r"useLocalCdk *=.*", updated_build_gradle) - if use_local_cdk is not None: - updated_build_gradle = updated_build_gradle.replace(use_local_cdk.group(), "useLocalCdk = false") - - return og_connector_dir.with_new_file("build.gradle", updated_build_gradle) - - async def upgrade_cdk_version_for_python_connector(self, og_connector_dir: Directory) -> Optional[Directory]: - context = self.context - og_connector_dir = await context.get_connector_dir() - - # Check for existing pyproject file and load contents - pyproject_toml = og_connector_dir.file(PYPROJECT_FILENAME) - if not pyproject_toml: - raise ValueError(f"Could not find 'pyproject.toml' for {context.connector.technical_name}") - pyproject_content = await pyproject_toml.contents() - pyproject_data = toml.loads(pyproject_content) - - # Grab the airbyte-cdk dependency from pyproject - dependencies = pyproject_data.get("tool", {}).get("poetry", {}).get("dependencies", {}) - airbyte_cdk_dependency = dependencies.get("airbyte-cdk") - if not airbyte_cdk_dependency: - raise ValueError("Could not find a valid airbyte-cdk dependency in 'pyproject.toml'") - - # Set the new version. If not explicitly provided, set it to the latest version and allow non-breaking changes - if self.new_version == "latest": - new_version = f"^{cdk_helpers.get_latest_python_cdk_version()}" - else: - new_version = self.new_version - self.logger.info(f"Setting CDK version to {new_version}") - dependencies["airbyte-cdk"] = new_version - - updated_pyproject_toml_content = toml.dumps(pyproject_data) - updated_connector_dir = og_connector_dir.with_new_file(PYPROJECT_FILENAME, updated_pyproject_toml_content) - - # Create a new container to run poetry lock - base_image = self.context.connector.metadata["connectorBuildOptions"]["baseImage"] - base_container = self.dagger_client.container(platform=LOCAL_BUILD_PLATFORM).from_(base_image) - connector_container = base_container.with_mounted_directory("/connector", updated_connector_dir).with_workdir("/connector") - - poetry_lock_file = await connector_container.file(POETRY_LOCK_FILENAME).contents() - updated_container = await connector_container.with_exec(["poetry", "lock"], use_entrypoint=True) - updated_poetry_lock_file = await updated_container.file(POETRY_LOCK_FILENAME).contents() - - if poetry_lock_file != updated_poetry_lock_file: - updated_connector_dir = updated_connector_dir.with_new_file(POETRY_LOCK_FILENAME, updated_poetry_lock_file) - else: - raise ValueError("Lockfile did not change after running poetry lock") - - return updated_connector_dir - - -async def run_connector_cdk_upgrade_pipeline( - context: ConnectorContext, - semaphore: Semaphore, - target_version: str, -) -> ConnectorReport: - """Run a pipeline to upgrade the CDK version for a single connector. - - Args: - context (ConnectorContext): The initialized connector context. - - Returns: - Report: The reports holding the CDK version set results. - """ - async with semaphore: - steps_results = [] - async with context: - set_cdk_version = SetCDKVersion( - context, - target_version, - ) - set_cdk_version_result = await set_cdk_version.run() - steps_results.append(set_cdk_version_result) - report = ConnectorReport(context, steps_results, name="CONNECTOR VERSION CDK UPGRADE RESULTS") - context.report = report - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/commands.py deleted file mode 100644 index 8796340abffa..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/commands.py +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import asyncclick as click - -from pipelines.cli.click_decorators import click_ci_requirements_option - -# MAIN GROUP - - -@click.group(help="Commands related to the metadata service.") -@click_ci_requirements_option() -@click.pass_context -def metadata(ctx: click.Context) -> None: - pass diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/pipeline.py deleted file mode 100644 index 4c6c6b593292..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/metadata/pipeline.py +++ /dev/null @@ -1,160 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import uuid -from typing import Optional - -import dagger - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.steps.docker import SimpleDockerStep -from pipelines.consts import DOCS_DIRECTORY_ROOT_PATH, GIT_DIRECTORY_ROOT_PATH, INTERNAL_TOOL_PATHS -from pipelines.models.secrets import Secret -from pipelines.models.steps import MountPath - -# STEPS - - -class MetadataValidation(SimpleDockerStep): - def __init__(self, context: ConnectorContext) -> None: - super().__init__( - title=f"Validate metadata for {context.connector.technical_name}", - context=context, - paths_to_mount=[ - MountPath(context.connector.code_directory), - MountPath(DOCS_DIRECTORY_ROOT_PATH), - ], - internal_tools=[ - MountPath(INTERNAL_TOOL_PATHS.METADATA_SERVICE.value), - ], - secret_env_variables={"DOCKER_HUB_USERNAME": context.docker_hub_username, "DOCKER_HUB_PASSWORD": context.docker_hub_password} - if context.docker_hub_username and context.docker_hub_password - else None, - command=[ - "metadata_service", - "validate", - str(context.connector.metadata_file_path), - DOCS_DIRECTORY_ROOT_PATH, - ], - ) - - -class MetadataUpload(SimpleDockerStep): - # When the metadata service exits with this code, it means the metadata is valid but the upload was skipped because the metadata is already uploaded - skipped_exit_code = 5 - - def __init__( - self, - context: ConnectorContext, - metadata_bucket_name: str, - metadata_service_gcs_credentials: Secret, - docker_hub_username: Secret, - docker_hub_password: Secret, - pre_release: bool = False, - pre_release_tag: Optional[str] = None, - ) -> None: - title = f"Upload metadata for {context.connector.technical_name} v{context.connector.version}" - command_to_run = [ - "metadata_service", - "upload", - str(context.connector.metadata_file_path), - DOCS_DIRECTORY_ROOT_PATH, - metadata_bucket_name, - ] - - if pre_release and pre_release_tag: - command_to_run += ["--prerelease", pre_release_tag] - - super().__init__( - title=title, - context=context, - paths_to_mount=[ - MountPath(GIT_DIRECTORY_ROOT_PATH), - MountPath(DOCS_DIRECTORY_ROOT_PATH), - MountPath(context.connector.code_directory), - ], - internal_tools=[ - MountPath(INTERNAL_TOOL_PATHS.METADATA_SERVICE.value), - ], - secret_env_variables={ - "DOCKER_HUB_USERNAME": docker_hub_username, - "DOCKER_HUB_PASSWORD": docker_hub_password, - "GCS_CREDENTIALS": metadata_service_gcs_credentials, - }, - env_variables={ - # The cache buster ensures we always run the upload command (in case of remote bucket change) - "CACHEBUSTER": str(uuid.uuid4()), - }, - command=command_to_run, - ) - - -class MetadataRollbackReleaseCandidate(SimpleDockerStep): - def __init__( - self, - context: ConnectorContext, - metadata_bucket_name: str, - metadata_service_gcs_credentials: Secret, - ) -> None: - docker_repository = context.connector.metadata["dockerRepository"] - version = context.connector.metadata["dockerImageTag"] - title = f"Rollback release candidate for {docker_repository} v{version}" - command_to_run = [ - "metadata_service", - "rollback-release-candidate", - docker_repository, - version, - metadata_bucket_name, - ] - - super().__init__( - title=title, - context=context, - internal_tools=[ - MountPath(INTERNAL_TOOL_PATHS.METADATA_SERVICE.value), - ], - secret_env_variables={ - "GCS_CREDENTIALS": metadata_service_gcs_credentials, - }, - env_variables={ - # The cache buster ensures we always run the rollback command (in case of remote bucket change) - "CACHEBUSTER": str(uuid.uuid4()), - }, - command=command_to_run, - ) - - -class MetadataPromoteReleaseCandidate(SimpleDockerStep): - def __init__( - self, - context: ConnectorContext, - metadata_bucket_name: str, - metadata_service_gcs_credentials: Secret, - ) -> None: - docker_repository = context.connector.metadata["dockerRepository"] - version = context.connector.metadata["dockerImageTag"] - title = f"Promote release candidate for {docker_repository} v{version}" - command_to_run = [ - "metadata_service", - "promote-release-candidate", - docker_repository, - version, - metadata_bucket_name, - ] - - super().__init__( - title=title, - context=context, - internal_tools=[ - MountPath(INTERNAL_TOOL_PATHS.METADATA_SERVICE.value), - ], - secret_env_variables={ - "GCS_CREDENTIALS": metadata_service_gcs_credentials, - }, - env_variables={ - # The cache buster ensures we always run the rollback command (in case of remote bucket change) - "CACHEBUSTER": str(uuid.uuid4()), - }, - command=command_to_run, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/commands.py deleted file mode 100644 index e40905692072..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/commands.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -""" -Module exposing the format commands. -""" - -from __future__ import annotations - -import asyncclick as click - -from pipelines.cli.click_decorators import click_ignore_unused_kwargs, click_merge_args_into_context_obj -from pipelines.cli.lazy_group import LazyGroup -from pipelines.models.contexts.click_pipeline_context import ClickPipelineContext, pass_pipeline_context - - -@click.group( - name="poetry", - help="Commands related to running poetry commands.", - cls=LazyGroup, - lazy_subcommands={ - "publish": "pipelines.airbyte_ci.poetry.publish.commands.publish", - }, -) -@click.option( - "--package-path", - help="The path to publish", - type=click.STRING, - required=True, -) -@click_merge_args_into_context_obj -@pass_pipeline_context -@click_ignore_unused_kwargs -async def poetry(pipeline_context: ClickPipelineContext) -> None: - pass diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/commands.py deleted file mode 100644 index d822ae8e890b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/poetry/publish/commands.py +++ /dev/null @@ -1,114 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -""" -Module exposing the format commands. -""" - -from __future__ import annotations - -from typing import Optional - -import asyncclick as click -from packaging import version - -from pipelines.airbyte_ci.steps.python_registry import PublishToPythonRegistry -from pipelines.cli.confirm_prompt import confirm -from pipelines.cli.dagger_pipeline_command import DaggerPipelineCommand -from pipelines.cli.secrets import wrap_in_secret -from pipelines.consts import DEFAULT_PYTHON_PACKAGE_REGISTRY_CHECK_URL, DEFAULT_PYTHON_PACKAGE_REGISTRY_URL -from pipelines.models.contexts.click_pipeline_context import ClickPipelineContext, pass_pipeline_context -from pipelines.models.contexts.python_registry_publish import PythonRegistryPublishContext -from pipelines.models.secrets import Secret -from pipelines.models.steps import StepStatus - - -async def _has_metadata_yaml(context: PythonRegistryPublishContext) -> bool: - dir_to_publish = context.get_repo_dir(context.package_path) - return "metadata.yaml" in await dir_to_publish.entries() - - -def _validate_python_version(_ctx: dict, _param: dict, value: Optional[str]) -> Optional[str]: - """ - Check if an given version is valid. - """ - if value is None: - return value - try: - version.Version(value) - return value - except version.InvalidVersion: - raise click.BadParameter(f"Version {value} is not a valid version.") - - -@click.command(cls=DaggerPipelineCommand, name="publish", help="Publish a Python package to a registry.") -@click.option( - "--python-registry-token", - help="Access token", - type=click.STRING, - required=True, - envvar="PYTHON_REGISTRY_TOKEN", - callback=wrap_in_secret, -) -@click.option( - "--python-registry-url", - help="Which registry to publish to. If not set, the default pypi is used. For test pypi, use https://test.pypi.org/legacy/", - type=click.STRING, - default=DEFAULT_PYTHON_PACKAGE_REGISTRY_URL, - envvar="PYTHON_REGISTRY_URL", -) -@click.option( - "--publish-name", - help="The name of the package to publish. If not set, the name will be inferred from the pyproject.toml file of the package.", - type=click.STRING, -) -@click.option( - "--publish-version", - help="The version of the package to publish. If not set, the version will be inferred from the pyproject.toml file of the package.", - type=click.STRING, - callback=_validate_python_version, -) -@pass_pipeline_context -@click.pass_context -async def publish( - ctx: click.Context, - click_pipeline_context: ClickPipelineContext, - python_registry_token: Secret, - python_registry_url: str, - publish_name: Optional[str], - publish_version: Optional[str], -) -> bool: - context = PythonRegistryPublishContext( - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - python_registry_token=python_registry_token, - registry=python_registry_url, - registry_check_url=DEFAULT_PYTHON_PACKAGE_REGISTRY_CHECK_URL, - package_path=ctx.obj["package_path"], - package_name=publish_name, - version=publish_version, - ) - - dagger_client = await click_pipeline_context.get_dagger_client() - context.dagger_client = dagger_client - - if await _has_metadata_yaml(context): - confirm( - "It looks like you are trying to publish a connector. In most cases, the `connectors` command group should be used instead. Do you want to continue?", - abort=True, - ) - - publish_result = await PublishToPythonRegistry(context).run() - - return publish_result.status is StepStatus.SUCCESS diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/base_image.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/base_image.py deleted file mode 100644 index 34d04d231f77..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/base_image.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2025 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -from typing import TYPE_CHECKING, List, Optional - -import dagger -import semver -import yaml -from connector_ops.utils import METADATA_FILE_NAME, ConnectorLanguage # type: ignore - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.dagger.actions.system.docker import with_crane -from pipelines.helpers.connectors.dagger_fs import dagger_read_file, dagger_write_file -from pipelines.models.steps import StepModifyingFiles, StepResult, StepStatus - -if TYPE_CHECKING: - import dagger - - -class NoBaseImageAddressInMetadataError(Exception): - pass - - -class UpdateBaseImageMetadata(StepModifyingFiles): - BASE_IMAGE_LIST_CACHE_TTL_SECONDS = 60 * 60 * 24 # 1 day - - context: ConnectorContext - - title = "Upgrade the base image to the latest version in metadata.yaml" - - def __init__( - self, - context: ConnectorContext, - connector_directory: dagger.Directory, - set_if_not_exists: bool = False, - ) -> None: - super().__init__(context, connector_directory) - self.set_if_not_exists = set_if_not_exists - self.modified_files = [] - self.connector_directory = connector_directory - - def _get_repository_for_language(self, language: ConnectorLanguage) -> str: - """Map connector language to DockerHub repository.""" - if language in [ConnectorLanguage.PYTHON, ConnectorLanguage.LOW_CODE]: - return "airbyte/python-connector-base" - elif language is ConnectorLanguage.MANIFEST_ONLY: - return "airbyte/source-declarative-manifest" - elif language is ConnectorLanguage.JAVA: - return "airbyte/java-connector-base" - else: - raise NotImplementedError(f"Registry for language {language} is not implemented yet.") - - def _parse_latest_stable_tag(self, tags: List[str]) -> Optional[str]: - """Parse tags to find latest stable (non-prerelease) version.""" - valid_versions = [] - for tag in tags: - try: - version = semver.VersionInfo.parse(tag) - if not version.prerelease: # Exclude pre-release versions - valid_versions.append(version) - except ValueError: - continue # Skip non-semver tags - - if valid_versions: - return str(max(valid_versions)) - return None - - async def get_latest_base_image_address(self) -> Optional[str]: - try: - if not (self.context.docker_hub_username and self.context.docker_hub_password): - raise ValueError("Docker Hub credentials are required to get the latest base image address") - - repository = self._get_repository_for_language(self.context.connector.language) - crane_container = with_crane(self.context) - - # List all tags - tags_output = await crane_container.with_exec(["crane", "ls", f"docker.io/{repository}"]).stdout() - tags = [tag.strip() for tag in tags_output.strip().split("\n") if tag.strip()] - - latest_tag = self._parse_latest_stable_tag(tags) - if latest_tag: - # Get the digest for the specific tag to ensure immutable reference - digest_output = await crane_container.with_exec(["crane", "digest", f"docker.io/{repository}:{latest_tag}"]).stdout() - digest = digest_output.strip() - return f"docker.io/{repository}:{latest_tag}@{digest}" - return None - except NotImplementedError: - return None - - @staticmethod - async def update_base_image_in_metadata( - connector_directory: dagger.Directory, latest_base_image_version_address: str, set_if_not_exists: bool = False - ) -> dagger.Directory: - raw_metadata = await dagger_read_file(connector_directory, METADATA_FILE_NAME) - current_metadata = yaml.safe_load(raw_metadata) - - current_base_image_version_address = current_metadata.get("data").get("connectorBuildOptions", {}).get("baseImage") - if not current_base_image_version_address: - if set_if_not_exists: - current_metadata["data"]["connectorBuildOptions"] = {"baseImage": latest_base_image_version_address} - new_raw_metadata = yaml.dump(current_metadata) - else: - raise NoBaseImageAddressInMetadataError("No base image address found in metadata file") - else: - new_raw_metadata = raw_metadata.replace(current_base_image_version_address, latest_base_image_version_address) - updated_connector_dir = dagger_write_file(connector_directory, METADATA_FILE_NAME, new_raw_metadata) - return updated_connector_dir - - async def _run(self) -> StepResult: - latest_base_image_address = await self.get_latest_base_image_address() - updated_base_image_address = None - if latest_base_image_address is None: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout="Could not find a base image for this connector language.", - ) - - current_base_image_address = self.context.connector.metadata.get("connectorBuildOptions", {}).get("baseImage") - - if not self.set_if_not_exists and current_base_image_address is None: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout="This connector does not have a base image set in metadata.yaml.", - ) - - if current_base_image_address == latest_base_image_address: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stdout=f"Base image is already up to date: {latest_base_image_address}", - ) - - original_connector_directory = self.connector_directory or await self.context.get_connector_dir() - try: - updated_connector_directory = await self.update_base_image_in_metadata( - original_connector_directory, latest_base_image_address, self.set_if_not_exists - ) - updated_base_image_address = latest_base_image_address - except NoBaseImageAddressInMetadataError: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr="No base image address found in metadata file", - ) - self.modified_files.append(METADATA_FILE_NAME) - - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=f"Updated base image to {latest_base_image_address} in {METADATA_FILE_NAME}", - output={ - "updated_connector_directory": updated_connector_directory, - "updated_base_image_address": updated_base_image_address, - }, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/bump_version.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/bump_version.py deleted file mode 100644 index e26c10c292c1..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/bump_version.py +++ /dev/null @@ -1,151 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Mapping - -import dagger -import semver -import yaml # type: ignore -from connector_ops.utils import METADATA_FILE_NAME, PYPROJECT_FILE_NAME # type: ignore - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.dagger.actions.python.poetry import with_poetry -from pipelines.helpers.connectors.dagger_fs import dagger_read_file, dagger_write_file -from pipelines.models.steps import StepModifyingFiles, StepResult, StepStatus - -BUMP_VERSION_METHOD_MAPPING: Mapping[str, Any] = { - "patch": semver.Version.bump_patch, - "minor": semver.Version.bump_minor, - "major": semver.Version.bump_major, - "rc": semver.Version.bump_prerelease, -} - -if TYPE_CHECKING: - pass - - -class ConnectorVersionNotFoundError(Exception): - pass - - -class PoetryVersionBumpError(Exception): - pass - - -class SetConnectorVersion(StepModifyingFiles): - context: ConnectorContext - - @property - def title(self) -> str: - return f"Set connector version to {self.new_version}" - - def __init__( - self, - context: ConnectorContext, - connector_directory: dagger.Directory, - new_version: str, - ) -> None: - super().__init__(context, connector_directory) - self.new_version = new_version - - @staticmethod - async def _set_version_in_metadata(new_version: str, connector_directory: dagger.Directory) -> dagger.Directory: - raw_metadata = await dagger_read_file(connector_directory, METADATA_FILE_NAME) - current_metadata = yaml.safe_load(raw_metadata) - - try: - current_version = current_metadata["data"]["dockerImageTag"] - except KeyError: - raise ConnectorVersionNotFoundError("dockerImageTag not found in metadata file") - - # We use replace here instead of mutating the deserialized yaml to avoid messing up with the comments in the metadata file. - new_raw_metadata = raw_metadata.replace("dockerImageTag: " + current_version, "dockerImageTag: " + new_version) - updated_connector_dir = dagger_write_file(connector_directory, METADATA_FILE_NAME, new_raw_metadata) - - return updated_connector_dir - - @staticmethod - async def _set_version_in_poetry_package( - container_with_poetry: dagger.Container, connector_directory: dagger.Directory, new_version: str - ) -> dagger.Directory: - try: - connector_directory_with_updated_pyproject = await ( - container_with_poetry.with_directory("/connector", connector_directory) - .with_workdir("/connector") - .with_exec(["poetry", "version", new_version], use_entrypoint=True) - .directory("/connector") - ) - except dagger.ExecError as e: - raise PoetryVersionBumpError(f"Failed to bump version in pyproject.toml: {e}") - return connector_directory_with_updated_pyproject - - async def _run(self) -> StepResult: - original_connector_directory = self.modified_directory - try: - self.modified_directory = await self._set_version_in_metadata(self.new_version, original_connector_directory) - self.modified_files.append(METADATA_FILE_NAME) - except (FileNotFoundError, ConnectorVersionNotFoundError) as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr="Connector does not have a metadata file or the version is not set in the metadata file", - exc_info=e, - ) - - if self.context.connector.pyproject_file_path.is_file(): - try: - poetry_container = with_poetry(self.context) - self.modified_directory = await self._set_version_in_poetry_package( - poetry_container, self.modified_directory, self.new_version - ) - self.modified_files.append(PYPROJECT_FILE_NAME) - except PoetryVersionBumpError as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr="Failed to bump version in pyproject.toml", - exc_info=e, - ) - - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=f"Updated connector to {self.new_version}", - output=self.modified_directory, - ) - - -class BumpConnectorVersion(SetConnectorVersion): - def __init__(self, context: ConnectorContext, connector_directory: dagger.Directory, bump_type: str, rc: bool = False) -> None: - self.bump_type = bump_type - new_version = self.get_bumped_version(context.connector.version, bump_type, rc) - super().__init__( - context, - connector_directory, - new_version, - ) - - @property - def title(self) -> str: - return f"{self.bump_type.upper()} bump {self.context.connector.technical_name} version to {self.new_version}" - - @staticmethod - def get_bumped_version(version: str | None, bump_type: str, rc: bool) -> str: - if version is None: - raise ValueError("Version is not set") - current_version = semver.VersionInfo.parse(version) - if bump_type in BUMP_VERSION_METHOD_MAPPING: - new_version = BUMP_VERSION_METHOD_MAPPING[bump_type](current_version) - if rc: - new_version = new_version.bump_prerelease() - elif bump_type.startswith("version:"): - version_str = bump_type.split("version:", 1)[1] - if semver.VersionInfo.is_valid(version_str): - return version_str - else: - raise ValueError(f"Invalid version: {version_str}") - else: - raise ValueError(f"Unknown bump type: {bump_type}") - return str(new_version) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/changelog.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/changelog.py deleted file mode 100644 index 3170d082b333..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/changelog.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from __future__ import annotations - -import datetime - -import semver -from dagger import Directory - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.helpers.changelog import Changelog -from pipelines.helpers.connectors.dagger_fs import dagger_read_file, dagger_write_file -from pipelines.models.steps import StepModifyingFiles, StepResult, StepStatus - - -class AddChangelogEntry(StepModifyingFiles): - context: ConnectorContext - - title = "Add changelog entry" - - def __init__( - self, - context: ConnectorContext, - documentation_directory: Directory, - new_version: str, - comment: str, - pull_request_number: str | int | None, - ) -> None: - super().__init__(context, documentation_directory) - self.new_version = semver.VersionInfo.parse(new_version) - self.comment = comment - self.pull_request_number = pull_request_number or "*PR_NUMBER_PLACEHOLDER*" - - async def _run(self, pull_request_number: int | str | None = None) -> StepResult: - if pull_request_number is None: - # this allows passing it dynamically from a result of another action (like creating a pull request) - pull_request_number = self.pull_request_number - - try: - original_markdown = await dagger_read_file(self.modified_directory, self.context.connector.documentation_file_name) - except FileNotFoundError: - return StepResult( - step=self, - status=StepStatus.SKIPPED, - stderr="Connector does not have a documentation file.", - ) - - try: - changelog = Changelog(original_markdown) - changelog.add_entry(self.new_version, datetime.date.today(), pull_request_number, self.comment) - updated_doc = changelog.to_markdown() - except Exception as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=f"Could not add changelog entry: {e}", - output=self.modified_directory, - exc_info=e, - ) - - self.modified_directory = dagger_write_file(self.modified_directory, self.context.connector.documentation_file_name, updated_doc) - self.modified_files.append(self.context.connector.documentation_file_name) - return StepResult( - step=self, - status=StepStatus.SUCCESS, - stdout=f"Added changelog entry to {self.context.connector.documentation_file_name}", - output=self.modified_directory, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/docker.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/docker.py deleted file mode 100644 index 1e974fc46deb..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/docker.py +++ /dev/null @@ -1,112 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from pathlib import Path -from typing import Dict, List, Optional - -import dagger - -from pipelines.dagger.actions.python.pipx import with_installed_pipx_package -from pipelines.dagger.containers.python import with_python_base -from pipelines.models.contexts.pipeline_context import PipelineContext -from pipelines.models.secrets import Secret -from pipelines.models.steps import MountPath, Step, StepResult - - -class SimpleDockerStep(Step): - def __init__( - self, - title: str, - context: PipelineContext, - paths_to_mount: Optional[List[MountPath]] = None, - internal_tools: Optional[List[MountPath]] = None, - secret_env_variables: Optional[Dict[str, Secret]] = None, - env_variables: dict[str, str] = {}, - working_directory: str = "/", - command: Optional[List[str]] = None, - ) -> None: - """A simple step that runs a given command in a container. - - Args: - title (str): name of the step - context (PipelineContext): context of the step - paths_to_mount (List[MountPath], optional): directory paths to mount. Defaults to []. - internal_tools (List[MountPath], optional): internal tools to install. Defaults to []. - secret_env_variables (List[Tuple[str, Secret]], optional): secrets to add to container as environment variables, a tuple of env var name > Secret object . Defaults to []. - env_variables (dict[str, str], optional): env variables to set in container. Defaults to {}. - working_directory (str, optional): working directory to run the command in. Defaults to "/". - command (Optional[List[str]], optional): The default command to run. Defaults to None. - """ - self._title = title - super().__init__(context) - - self.paths_to_mount = paths_to_mount if paths_to_mount else [] - self.working_directory = working_directory - self.internal_tools = internal_tools if internal_tools else [] - self.secret_env_variables = secret_env_variables if secret_env_variables else {} - self.env_variables = env_variables - self.command = command - - @property - def title(self) -> str: - return self._title - - def _mount_paths(self, container: dagger.Container) -> dagger.Container: - for path_to_mount in self.paths_to_mount: - if path_to_mount.optional and not path_to_mount.get_path().exists(): - continue - - if path_to_mount.get_path().is_symlink(): - container = self._mount_path(container, path_to_mount.get_path().readlink()) - - container = self._mount_path(container, path_to_mount.get_path()) - return container - - def _mount_path(self, container: dagger.Container, path: Path) -> dagger.Container: - path_string = str(path) - destination_path = f"/{path_string}" - if path.is_file(): - file_to_load = self.context.get_repo_file(path_string) - container = container.with_mounted_file(destination_path, file_to_load) - else: - dir_to_load = self.context.get_repo_dir(path_string) - container = container.with_mounted_directory(destination_path, dir_to_load) - return container - - async def _install_internal_tools(self, container: dagger.Container) -> dagger.Container: - for internal_tool in self.internal_tools: - container = await with_installed_pipx_package(self.context, container, str(internal_tool)) - return container - - def _set_workdir(self, container: dagger.Container) -> dagger.Container: - return container.with_workdir(self.working_directory) - - def _set_env_variables(self, container: dagger.Container) -> dagger.Container: - for key, value in self.env_variables.items(): - container = container.with_env_variable(key, value) - return container - - def _set_secret_env_variables(self, container: dagger.Container) -> dagger.Container: - for env_var_name, secret in self.secret_env_variables.items(): - container = container.with_secret_variable(env_var_name, secret.as_dagger_secret(self.context.dagger_client)) - return container - - async def init_container(self) -> dagger.Container: - # TODO (ben): Replace with python base container when available - container = with_python_base(self.context) - - container = self._mount_paths(container) - container = self._set_env_variables(container) - container = self._set_secret_env_variables(container) - container = await self._install_internal_tools(container) - container = self._set_workdir(container) - - return container - - async def _run(self, command: Optional[List[str]] = None) -> StepResult: - command_to_run = command or self.command - if not command_to_run: - raise ValueError(f"No command given to the {self.title} step") - - container_to_run = await self.init_container() - return await self.get_step_result(container_to_run.with_exec(command_to_run)) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/gradle.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/gradle.py deleted file mode 100644 index 32aa13463907..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/gradle.py +++ /dev/null @@ -1,323 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -import os -import select -import shutil -import signal -import subprocess -from abc import ABC -from contextlib import contextmanager -from datetime import datetime -from pathlib import Path -from typing import Any, ClassVar, Dict, Generator, List, Optional, Tuple, cast - -from dagger import Container, ExecError - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.helpers.utils import dagger_directory_as_zip_file -from pipelines.models.artifacts import Artifact -from pipelines.models.secrets import Secret -from pipelines.models.steps import Step, StepResult, StepStatus - - -class InvalidGradleEnvironment(Exception): - pass - - -class GradleTimeoutError(Exception): - """Raised when a Gradle operation times out.""" - - pass - - -class GradleExecutionError(Exception): - """Raised when a Gradle execution fails due to an unexpected error.""" - - pass - - -class GradleTask(Step, ABC): - """ - A step to run a Gradle task. - - Attributes: - title (str): The step title. - gradle_task_name (str): The Gradle task name to run. - bind_to_docker_host (bool): Whether to install the docker client and bind it to the host. - mount_connector_secrets (bool): Whether to mount connector secrets. - """ - - context: ConnectorContext - - STATIC_GRADLE_OPTIONS = ("--build-cache", "--scan", "--no-watch-fs") - gradle_task_name: ClassVar[str] - - with_test_artifacts: ClassVar[bool] = False - accept_extra_params = True - - GRADLE_TIMEOUT = 3600 * 2 # 2 hour timeout by default - - @property - def gradle_task_options(self) -> Tuple[str, ...]: - if self.context.s3_build_cache_access_key_id and self.context.s3_build_cache_secret_key: - return self.STATIC_GRADLE_OPTIONS + (f"-Ds3BuildCachePrefix={self.context.connector.technical_name}",) - return self.STATIC_GRADLE_OPTIONS - - def _get_gradle_command(self, task: str, *args: Any, task_options: Optional[List[str]] = None) -> str: - task_options = task_options or [] - return f"./gradlew {' '.join(self.gradle_task_options + args)} {task} {' '.join(task_options)}" - - def check_system_requirements(self) -> None: - """ - Check if the system has all the required commands in the path. - This could be improved to check for more specific versions of the commands. - """ - required_commands_in_path = ["docker", "gradle", "jq", "xargs", "java"] - for command in required_commands_in_path: - if not shutil.which(command): - raise ValueError(f"Command {command} is not in the path") - - @property - def gradle_command(self) -> str: - connector_gradle_task = f":airbyte-integrations:connectors:{self.context.connector.technical_name}:{self.gradle_task_name}" - return self._get_gradle_command(connector_gradle_task, task_options=self.params_as_cli_options) - - def timeout_handler(self, signum: int, frame: Any) -> None: - raise GradleTimeoutError(f"Gradle operation timed out after {self.GRADLE_TIMEOUT} seconds") - - @contextmanager - def gradle_environment(self) -> Generator[None, None, None]: - """ - Context manager to set the gradle environment with timeout: - - Check if the system has all the required commands in the path. - - Set the S3 build cache environment variables if available. - - Enforces a timeout for gradle operations - - ... Add whatever setup/teardown logic needed to run a gradle task. - - Raises: - InvalidGradleEnvironment: If the gradle environment is not properly set up - GradleTimeoutError: If the gradle operation exceeds the timeout - """ - - def set_env_vars() -> Dict[str, str]: - # Set the RUN_IN_AIRBYTE_CI environment variable to True to tell gradle to use the docker image that was previously built in the airbyte-ci pipeline - env_vars = {"RUN_IN_AIRBYTE_CI": "True"} - - # Set the S3 build cache environment variables if available. - if self.context.s3_build_cache_access_key_id and self.context.s3_build_cache_secret_key: - env_vars["S3_BUILD_CACHE_ACCESS_KEY_ID"] = self.context.s3_build_cache_access_key_id.value - env_vars["S3_BUILD_CACHE_SECRET_KEY"] = self.context.s3_build_cache_secret_key.value - - for key, value in env_vars.items(): - os.environ[key] = value - return env_vars - - def unset_env_vars(env_vars: Dict[str, str]) -> None: - for key in env_vars.keys(): - del os.environ[key] - - def write_secrets(secrets: List[Secret]) -> List[Path]: - secrets_paths = [] - secrets_dir = f"{self.context.connector.code_directory}/secrets" - for secret in secrets: - secret_path = Path(f"{secrets_dir}/{secret.file_name}") - secret_path.parent.mkdir(parents=True, exist_ok=True) - secret_path.write_text(secret.value) - secrets_paths.append(secret_path) - return secrets_paths - - def remove_secrets(secrets_paths: List[Path]) -> None: - for secret_path in secrets_paths: - secret_path.unlink() - - # Set the timeout handler for gradle operations - original_timeout_handler = signal.signal(signal.SIGALRM, self.timeout_handler) - - try: - # Check if the system has all the required commands in the path. - self.check_system_requirements() - # Set env vars and write secrets - will be undone in the finally block - env_vars = set_env_vars() - secret_paths = write_secrets(self.secrets) - - # Set the timeout for gradle operations via SIGALRM - self.logger.info(f"Setting gradle timeout to {self.GRADLE_TIMEOUT} seconds") - signal.alarm(self.GRADLE_TIMEOUT) - - yield None - except GradleTimeoutError: - raise - except InvalidGradleEnvironment: - raise - except Exception as e: - # Wrap any other unexpected exceptions in GradleExecutionError - raise GradleExecutionError(f"Unexpected error during gradle execution: {str(e)}") from e - finally: - signal.alarm(0) # Disable the alarm - signal.signal(signal.SIGALRM, original_timeout_handler) # Restore original handler - unset_env_vars(env_vars) - # Remove secrets from the secrets folders only in CI - if self.context.is_ci: - remove_secrets(secret_paths) - - def _run_gradle_in_subprocess(self) -> Tuple[str, str, int]: - """ - Run a gradle command in a subprocess and stream output in real-time using non-blocking reads. - """ - try: - self.context.logger.info(f"Running gradle command: {self.gradle_command}") - process = subprocess.Popen( - self.gradle_command, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - universal_newlines=True, - ) - - # Store complete output for return value - full_stdout = [] - full_stderr = [] - - # Get file objects for stdout and stderr - stdout = process.stdout - stderr = process.stderr - - # While the process is running and we have pipes to read from - while process.poll() is None or (stdout or stderr): - # Wait for data on either pipe (timeout of 0.1 seconds) - readable, _, _ = select.select( - [stdout, stderr] if stdout and stderr else [stdout] if stdout else [stderr] if stderr else [], [], [], 0.1 - ) - - for stream in readable: - data = stream.readline() - if data: - if stream == stdout: - self.logger.info(data.rstrip()) - full_stdout.append(data) - else: - self.logger.error(data.rstrip()) - full_stderr.append(data) - else: - if stream == stdout: - stdout = None - else: - stderr = None - - returncode = process.wait() - final_stdout = "".join(full_stdout) - final_stderr = "".join(full_stderr) - - if returncode != 0: - final_stderr = f"Error while running gradle command: {self.gradle_command}\n{final_stderr}" - - return final_stdout, final_stderr, returncode - - finally: - # Ensure process is terminated if something goes wrong - if "process" in locals(): - self.logger.info("Terminating gradle process") - process.terminate() - process.wait(timeout=20) # Give it 20 seconds to terminate gracefully - try: - self.logger.info("Force killing gradle process") - process.kill() - except ProcessLookupError: - pass - - async def _run(self, *args: Any, **kwargs: Any) -> StepResult: - try: - with self.gradle_environment(): - stdout, stderr, returncode = self._run_gradle_in_subprocess() - artifacts = [] - if self.with_test_artifacts: - if test_logs := await self._collect_test_logs(): - artifacts.append(test_logs) - if test_results := await self._collect_test_results(): - artifacts.append(test_results) - step_result = StepResult( - step=self, - status=StepStatus.SUCCESS if returncode == 0 else StepStatus.FAILURE, - stdout=stdout, - stderr=stderr, - output=self.context.dagger_client.host().directory(str(self.context.connector.code_directory)), - artifacts=artifacts, - ) - return step_result - except (GradleTimeoutError, InvalidGradleEnvironment) as e: - return StepResult( - step=self, - status=StepStatus.FAILURE, - stderr=str(e), - ) - - async def _collect_test_logs(self) -> Optional[Artifact]: - """ - Exports the java docs to the host filesystem as a zip file. - The docs are expected to be in build/test-logs, and will end up test-artifact directory by default - One can change the destination directory by setting the outputs - """ - test_logs_dir_name_in_container = "test-logs" - test_logs_dir_name_in_zip = f"test-logs-{datetime.fromtimestamp(cast(float, self.context.pipeline_start_timestamp)).isoformat()}-{self.context.git_branch}-{self.gradle_task_name}".replace( - "/", "_" - ) - if ( - test_logs_dir_name_in_container - not in await self.dagger_client.host().directory(f"{self.context.connector.code_directory}/build").entries() - ): - self.context.logger.warn(f"No {test_logs_dir_name_in_container} found directory in the build folder") - return None - try: - zip_file = await dagger_directory_as_zip_file( - self.dagger_client, - await self.dagger_client.host().directory( - f"{self.context.connector.code_directory}/build/{test_logs_dir_name_in_container}" - ), - test_logs_dir_name_in_zip, - ) - return Artifact( - name=f"{test_logs_dir_name_in_zip}.zip", - content=zip_file, - content_type="application/zip", - to_upload=True, - ) - except ExecError as e: - self.context.logger.error(str(e)) - return None - - async def _collect_test_results(self) -> Optional[Artifact]: - """ - Exports the junit test results into the host filesystem as a zip file. - The docs in the container are expected to be in build/test-results, and will end up test-artifact directory by default - Only the XML files generated by junit are downloaded into the host filesystem - One can change the destination directory by setting the outputs - """ - test_results_dir_name_in_container = "test-results" - test_results_dir_name_in_zip = f"test-results-{datetime.fromtimestamp(cast(float, self.context.pipeline_start_timestamp)).isoformat()}-{self.context.git_branch}-{self.gradle_task_name}".replace( - "/", "_" - ) - if ( - test_results_dir_name_in_container - not in await self.dagger_client.host().directory(f"{self.context.connector.code_directory}/build").entries() - ): - self.context.logger.warn(f"No {test_results_dir_name_in_container} found directory in the build folder") - return None - try: - zip_file = await dagger_directory_as_zip_file( - self.dagger_client, - await self.dagger_client.host().directory( - f"{self.context.connector.code_directory}/build/{test_results_dir_name_in_container}" - ), - test_results_dir_name_in_zip, - ) - return Artifact( - name=f"{test_results_dir_name_in_zip}.zip", - content=zip_file, - content_type="application/zip", - to_upload=True, - ) - except ExecError as e: - self.context.logger.error(str(e)) - return None diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/no_op.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/no_op.py deleted file mode 100644 index 86b9712713a3..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/no_op.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import Any - -from pipelines.models.contexts.pipeline_context import PipelineContext -from pipelines.models.steps import Step, StepResult, StepStatus - - -class NoOpStep(Step): - """A step that does nothing.""" - - title = "No Op" - should_log = False - - def __init__(self, context: PipelineContext, step_status: StepStatus) -> None: - super().__init__(context) - self.step_status = step_status - - async def _run(self, *args: Any, **kwargs: Any) -> StepResult: - return StepResult(step=self, status=self.step_status) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/poetry.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/poetry.py deleted file mode 100644 index ebca7a7d612a..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/poetry.py +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import List - -from pipelines.dagger.actions.python.poetry import with_poetry_module -from pipelines.models.contexts.pipeline_context import PipelineContext -from pipelines.models.steps import Step, StepResult - - -class PoetryRunStep(Step): - def __init__(self, context: PipelineContext, title: str, parent_dir_path: str, module_path: str, poetry_run_args: List[str]) -> None: - """A simple step that runs a given command inside a poetry project. - - Args: - context (PipelineContext): context of the step - title (str): name of the step - parent_dir_path (str): The path to the parent directory of the poetry project - module_path (str): The path to the poetry project - poetry_run_args (List[str]): The arguments to pass to the poetry run command - """ - self._title = title - super().__init__(context) - - parent_dir = self.context.get_repo_dir(parent_dir_path) - module_path = module_path - self.poetry_run_args = poetry_run_args - self.poetry_run_container = with_poetry_module(self.context, parent_dir, module_path).with_entrypoint(["poetry", "run"]) - - @property - def title(self) -> str: - return self._title - - async def _run(self) -> StepResult: - poetry_run_exec = self.poetry_run_container.with_exec(self.poetry_run_args, use_entrypoint=True) - return await self.get_step_result(poetry_run_exec) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/pull_request.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/pull_request.py deleted file mode 100644 index 962d4018ed73..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/pull_request.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.helpers import github -from pipelines.models.steps import Step, StepResult, StepStatus - -if TYPE_CHECKING: - from pathlib import Path - from typing import Iterable, Optional - - -class CreateOrUpdatePullRequest(Step): - context: ConnectorContext - pull: bool - - title = "Create or update pull request on Airbyte repository" - - def __init__( - self, - context: PipelineContext, - skip_ci: bool, - labels: Optional[Iterable[str]] = None, - github_auto_merge: bool = False, - ) -> None: - super().__init__(context) - self.skip_ci = skip_ci - self.labels = labels or [] - self.github_auto_merge = github_auto_merge - - async def _run( - self, - modified_repo_files: Iterable[Path], - branch_id: str, - commit_message: str, - pr_title: str, - pr_body: str, - ) -> StepResult: - if self.context.ci_github_access_token is None: - return StepResult(step=self, status=StepStatus.FAILURE, stderr="No github access token provided") - - try: - pr = github.create_or_update_github_pull_request( - modified_repo_files, - self.context.ci_github_access_token.value, - branch_id, - commit_message, - pr_title, - pr_body, - logger=self.logger, - skip_ci=self.skip_ci, - labels=self.labels, - github_auto_merge=self.github_auto_merge, - ) - except Exception as e: - return StepResult(step=self, status=StepStatus.FAILURE, stderr=str(e), exc_info=e) - - return StepResult(step=self, status=StepStatus.SUCCESS, output=pr) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/python_registry.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/python_registry.py deleted file mode 100644 index de1829584c1e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/steps/python_registry.py +++ /dev/null @@ -1,171 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import configparser -import io -import uuid -from enum import Enum, auto -from typing import Dict, Optional - -import tomli_w -import tomllib -from dagger import Container, Directory - -from pipelines.consts import PYPROJECT_TOML_FILE_PATH, SETUP_PY_FILE_PATH -from pipelines.dagger.actions.python.poetry import with_poetry -from pipelines.helpers.utils import sh_dash_c -from pipelines.models.contexts.python_registry_publish import PythonPackageMetadata, PythonRegistryPublishContext -from pipelines.models.steps import Step, StepResult - - -class PackageType(Enum): - POETRY = auto() - PIP = auto() - - -class PublishToPythonRegistry(Step): - context: PythonRegistryPublishContext - title = "Publish package to python registry" - max_retries = 3 - - def _get_base_container(self) -> Container: - return with_poetry(self.context) - - async def _get_package_metadata_from_pyproject_toml(self, package_dir_to_publish: Directory) -> Optional[PythonPackageMetadata]: - pyproject_toml = package_dir_to_publish.file(PYPROJECT_TOML_FILE_PATH) - pyproject_toml_content = await pyproject_toml.contents() - contents = tomllib.loads(pyproject_toml_content) - try: - return PythonPackageMetadata(contents["tool"]["poetry"]["name"], contents["tool"]["poetry"]["version"]) - except KeyError: - return None - - async def _get_package_type(self, package_dir_to_publish: Directory) -> Optional[PackageType]: - files = await package_dir_to_publish.entries() - has_pyproject_toml = PYPROJECT_TOML_FILE_PATH in files - has_setup_py = SETUP_PY_FILE_PATH in files - if has_pyproject_toml: - return PackageType.POETRY - elif has_setup_py: - return PackageType.PIP - else: - return None - - async def _run(self) -> StepResult: - package_dir_to_publish = await self.context.get_repo_dir(self.context.package_path) - package_type = await self._get_package_type(package_dir_to_publish) - - if not package_type: - return self.skip("Connector does not have a pyproject.toml file or setup.py file, skipping.") - - result = await self._ensure_package_name_and_version(package_dir_to_publish, package_type) - if result: - return result - - self.logger.info( - f"Uploading package {self.context.package_metadata.name} version {self.context.package_metadata.version} to {self.context.registry}..." - ) - - return await self._publish(package_dir_to_publish, package_type) - - async def _ensure_package_name_and_version(self, package_dir_to_publish: Directory, package_type: PackageType) -> Optional[StepResult]: - """ - Try to infer package name and version from the pyproject.toml file. If it is not present, we need to have the package name and version set. - Setup.py packages need to set package name and version as parameter. - - Returns None if package name and version are set, otherwise a StepResult with a skip message. - """ - if self.context.package_metadata.name and self.context.package_metadata.version: - return None - - if package_type is not PackageType.POETRY: - return self.skip("Connector does not have a pyproject.toml file and version and package name is not set otherwise, skipping.") - - inferred_package_metadata = await self._get_package_metadata_from_pyproject_toml(package_dir_to_publish) - - if not inferred_package_metadata: - return self.skip( - "Connector does not have a pyproject.toml file which specifies package name and version and they are not set otherwise, skipping." - ) - - if not self.context.package_metadata.name: - self.context.package_metadata.name = inferred_package_metadata.name - if not self.context.package_metadata.version: - self.context.package_metadata.version = inferred_package_metadata.version - - return None - - async def _publish(self, package_dir_to_publish: Directory, package_type: PackageType) -> StepResult: - if package_type is PackageType.PIP: - return await self._pip_publish(package_dir_to_publish) - else: - return await self._poetry_publish(package_dir_to_publish) - - async def _poetry_publish(self, package_dir_to_publish: Directory) -> StepResult: - pyproject_toml = package_dir_to_publish.file(PYPROJECT_TOML_FILE_PATH) - pyproject_toml_content = await pyproject_toml.contents() - contents = tomllib.loads(pyproject_toml_content) - # make sure package name and version are set to the configured one - contents["tool"]["poetry"]["name"] = self.context.package_metadata.name - contents["tool"]["poetry"]["version"] = self.context.package_metadata.version - # enforce consistent author - contents["tool"]["poetry"]["authors"] = ["Airbyte "] - poetry_publish = ( - self._get_base_container() - .with_secret_variable("PYTHON_REGISTRY_TOKEN", self.context.python_registry_token.as_dagger_secret(self.dagger_client)) - .with_directory("package", package_dir_to_publish) - .with_workdir("package") - .with_new_file(PYPROJECT_TOML_FILE_PATH, contents=tomli_w.dumps(contents)) - # Make sure these steps are always executed and not cached as they are triggering a side-effect (calling the registry) - # Env var setting needs to be in this block as well to make sure a change of the env var will be propagated correctly - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - .with_exec(["poetry", "config", "repositories.mypypi", self.context.registry], use_entrypoint=True) - .with_exec(sh_dash_c(["poetry config pypi-token.mypypi $PYTHON_REGISTRY_TOKEN"])) - # Default timeout is set to 15 seconds - # We sometime face 443 HTTP read timeout responses from PyPi - # Setting it to 60 seconds to avoid transient publish failures - .with_env_variable("POETRY_REQUESTS_TIMEOUT", "60") - .with_exec(sh_dash_c(["poetry publish --build --repository mypypi -vvv --no-interaction"])) - ) - - return await self.get_step_result(poetry_publish) - - async def _pip_publish(self, package_dir_to_publish: Directory) -> StepResult: - files = await package_dir_to_publish.entries() - metadata: Dict[str, str] = { - "name": str(self.context.package_metadata.name), - "version": str(self.context.package_metadata.version), - # Enforce consistent author - "author": "Airbyte", - "author_email": "contact@airbyte.io", - } - if "README.md" in files: - metadata["long_description"] = await package_dir_to_publish.file("README.md").contents() - metadata["long_description_content_type"] = "text/markdown" - - config = configparser.ConfigParser() - config["metadata"] = metadata - - setup_cfg_io = io.StringIO() - config.write(setup_cfg_io) - setup_cfg = setup_cfg_io.getvalue() - - twine_upload = ( - self._get_base_container() - .with_exec(sh_dash_c(["apt-get update", "apt-get install -y twine"])) - .with_directory("package", package_dir_to_publish) - .with_workdir("package") - .with_exec(["sed", "-i", "/name=/d; /author=/d; /author_email=/d; /version=/d", SETUP_PY_FILE_PATH], use_entrypoint=True) - .with_new_file("setup.cfg", contents=setup_cfg) - .with_exec(["pip", "install", "--upgrade", "setuptools", "wheel"], use_entrypoint=True) - .with_exec(["python", SETUP_PY_FILE_PATH, "sdist", "bdist_wheel"], use_entrypoint=True) - # Make sure these steps are always executed and not cached as they are triggering a side-effect (calling the registry) - # Env var setting needs to be in this block as well to make sure a change of the env var will be propagated correctly - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - .with_secret_variable("TWINE_USERNAME", self.context.dagger_client.set_secret("pypi_username", "__token__")) - .with_secret_variable("TWINE_PASSWORD", self.context.python_registry_token.as_dagger_secret(self.dagger_client)) - .with_exec(["twine", "upload", "--verbose", "--repository-url", self.context.registry, "dist/*"], use_entrypoint=True) - ) - - return await self.get_step_result(twine_upload) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/__init__.py deleted file mode 100644 index 2418143507dc..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from pathlib import Path - -INTERNAL_POETRY_PACKAGES = [ - "airbyte-ci/connectors/auto_merge", - "airbyte-ci/connectors/pipelines", - "airbyte-ci/connectors/connectors_insights", - "airbyte-ci/connectors/connector_ops", - "airbyte-ci/connectors/ci_credentials", - "airbyte-ci/connectors/erd", - "airbyte-ci/connectors/live-tests", - "airbyte-ci/connectors/metadata_service/lib", - "airbyte-integrations/bases/connector-acceptance-test", -] - -INTERNAL_POETRY_PACKAGES_PATH = [Path(package) for package in INTERNAL_POETRY_PACKAGES] diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py deleted file mode 100644 index 15ec6831423b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/commands.py +++ /dev/null @@ -1,131 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -from typing import TYPE_CHECKING - -import asyncclick as click -import asyncer - -from pipelines.airbyte_ci.test import INTERNAL_POETRY_PACKAGES, INTERNAL_POETRY_PACKAGES_PATH, pipeline -from pipelines.cli.click_decorators import click_ci_requirements_option, click_ignore_unused_kwargs, click_merge_args_into_context_obj -from pipelines.helpers.git import get_modified_files -from pipelines.helpers.utils import transform_strs_to_paths -from pipelines.models.contexts.click_pipeline_context import ClickPipelineContext, pass_pipeline_context -from pipelines.models.steps import StepStatus - -if TYPE_CHECKING: - from pathlib import Path - from typing import List, Set, Tuple - - -async def find_modified_internal_packages(pipeline_context: ClickPipelineContext) -> Set[Path]: - """Finds the modified internal packages according to the modified files on the branch/commit. - - Args: - pipeline_context (ClickPipelineContext): The context object. - - Returns: - Set[Path]: The set of modified internal packages. - """ - modified_files = transform_strs_to_paths( - await get_modified_files( - pipeline_context.params["git_branch"], - pipeline_context.params["git_revision"], - pipeline_context.params["diffed_branch"], - pipeline_context.params["is_local"], - pipeline_context.params["ci_context"], - git_repo_url=pipeline_context.params["git_repo_url"], - ) - ) - modified_packages = set() - for modified_file in modified_files: - for internal_package in INTERNAL_POETRY_PACKAGES_PATH: - if modified_file.is_relative_to(internal_package): - modified_packages.add(internal_package) - return modified_packages - - -async def get_packages_to_run(pipeline_context: ClickPipelineContext) -> Set[Path]: - """Gets the packages to run the poe tasks on. - - Args: - pipeline_context (ClickPipelineContext): The context object. - - Raises: - click.ClickException: If no packages are specified to run the poe tasks on. - - Returns: - Set[Path]: The set of packages to run the poe tasks on. - """ - if not pipeline_context.params["poetry_package_paths"] and not pipeline_context.params["modified"]: - raise click.ClickException("You must specify at least one package to test.") - - poetry_package_paths = set() - if pipeline_context.params["modified"]: - poetry_package_paths = await find_modified_internal_packages(pipeline_context) - - return poetry_package_paths.union(set(pipeline_context.params["poetry_package_paths"])) - - -def crash_on_any_failure(poetry_package_poe_tasks_results: List[Tuple[Path, asyncer.SoonValue]]) -> None: - """Fail the command if any of the poe tasks failed. - - Args: - poetry_package_poe_tasks_results (List[Tuple[Path, asyncer.SoonValue]]): The results of the poe tasks. - - Raises: - click.ClickException: If any of the poe tasks failed. - """ - failed_packages = set() - for poetry_package_paths, package_result in poetry_package_poe_tasks_results: - poe_command_results = package_result.value - if any([result.status is StepStatus.FAILURE for result in poe_command_results]): - failed_packages.add(poetry_package_paths) - if failed_packages: - raise click.ClickException( - f"The following packages failed to run poe tasks: {', '.join([str(package_path) for package_path in failed_packages])}" - ) - return None - - -@click.command() -@click.option("--modified", default=False, is_flag=True, help="Run on modified internal packages.") -@click.option( - "--poetry-package-path", - "-p", - "poetry_package_paths", - help="The path to the poetry package to test.", - type=click.Choice(INTERNAL_POETRY_PACKAGES), - multiple=True, -) -@click_ci_requirements_option() -@click_merge_args_into_context_obj -@pass_pipeline_context -@click_ignore_unused_kwargs -# TODO this command should be renamed ci and go under the poetry command group -# e.g. airbyte-ci poetry ci --poetry-package-path airbyte-ci/connectors/pipelines -async def test(pipeline_context: ClickPipelineContext) -> None: - """Runs the tests for the given airbyte-ci package - - Args: - pipeline_context (ClickPipelineContext): The context object. - """ - poetry_package_paths = await get_packages_to_run(pipeline_context) - click.echo(f"Running poe tasks of the following packages: {', '.join([str(package_path) for package_path in poetry_package_paths])}") - dagger_client = await pipeline_context.get_dagger_client() - - poetry_package_poe_tasks_results: List[Tuple[Path, asyncer.SoonValue]] = [] - async with asyncer.create_task_group() as poetry_packages_task_group: - for poetry_package_path in poetry_package_paths: - poetry_package_poe_tasks_results.append( - ( - poetry_package_path, - poetry_packages_task_group.soonify(pipeline.run_poe_tasks_for_package)( - dagger_client, poetry_package_path, pipeline_context.params - ), - ) - ) - - crash_on_any_failure(poetry_package_poe_tasks_results) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/models.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/models.py deleted file mode 100644 index e5b2aae93c6a..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/models.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -import os -from typing import Dict, List, Set - -from pydantic import BaseModel, Field, validator - - -class AirbyteCiPackageConfiguration(BaseModel): - poe_tasks: Set[str] = Field(..., description="List of unique poe tasks to run") - required_environment_variables: Set[str] = Field( - set(), description="List of unique required environment variables to pass to the container running the poe task" - ) - poetry_extras: Set[str] = Field(set(), description="List of unique poetry extras to install") - optional_poetry_groups: Set[str] = Field(set(), description="List of unique poetry groups to install") - side_car_docker_engine: bool = Field( - False, description="Flag indicating the use of a sidecar Docker engine during the poe task executions" - ) - mount_docker_socket: bool = Field( - False, - description="Flag indicating the mount of the host docker socket to the container running the poe task, useful when the package under test is using dagger", - ) - python_versions: List[str] = Field(description="List of unique python versions to run the poe tasks on") - - @validator("required_environment_variables") - def check_required_environment_variables_are_set(cls, value: Set) -> Set: - for required_env_var in value: - if required_env_var not in os.environ: - raise ValueError(f"Environment variable {required_env_var} is not set.") - return value - - -def deserialize_airbyte_ci_config(pyproject_toml: Dict) -> AirbyteCiPackageConfiguration: - try: - airbyte_ci_config = pyproject_toml["tool"]["airbyte_ci"] - except KeyError: - raise ValueError("Missing tool.airbyte_ci configuration in pyproject.toml") - return AirbyteCiPackageConfiguration.parse_obj(airbyte_ci_config) diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/pipeline.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/pipeline.py deleted file mode 100644 index ef0a14199396..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/test/pipeline.py +++ /dev/null @@ -1,355 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import logging -import os -from typing import TYPE_CHECKING - -import asyncer -import dagger -import toml - -from pipelines.airbyte_ci.test.models import deserialize_airbyte_ci_config -from pipelines.consts import DOCKER_HOST_NAME, DOCKER_HOST_PORT, DOCKER_VERSION, POETRY_CACHE_VOLUME_NAME, PYPROJECT_TOML_FILE_PATH -from pipelines.dagger.actions.system import docker -from pipelines.helpers.github import update_commit_status_check -from pipelines.helpers.utils import sh_dash_c -from pipelines.models.steps import PoeTaskResult, StepStatus - -if TYPE_CHECKING: - from logging import Logger - from pathlib import Path - from typing import Dict, List - - from pipelines.airbyte_ci.test.models import AirbyteCiPackageConfiguration - -# The following directories are always mounted because a lot of tests rely on them -DIRECTORIES_TO_ALWAYS_MOUNT = [ - ".git", # This is needed as some package tests rely on being in a git repo - ".github", - "docs", - "airbyte-integrations", - "airbyte-ci", - "airbyte-cdk", - PYPROJECT_TOML_FILE_PATH, - "LICENSE_SHORT", - "poetry.lock", - "spotless-maven-pom.xml", - "tools/gradle/codestyle/java-google-style.xml", -] - -DEFAULT_EXCLUDE = ["**/__pycache__", "**/.pytest_cache", "**/.venv", "**.log", "**/.gradle"] - -DEFAULT_CONTAINER_IMAGE = "python:{version}" - -VERSION_CONTAINER_IMAGES = { - "3.10": DEFAULT_CONTAINER_IMAGE.format(version="3.10.12"), - "3.11": DEFAULT_CONTAINER_IMAGE.format(version="3.11.9"), -} - - -async def get_filtered_airbyte_repo_dir(dagger_client: dagger.Client, poetry_package_path: Path) -> dagger.Directory: - """Get a filtered airbyte repo directory with the directories to always mount and the poetry package path. - - Args: - dagger_client (dagger.Client): Dagger client. - poetry_package_path (Path): Path to the poetry package in the airbyte repo. - - Returns: - dagger.Directory: The filtered airbyte repo directory. - """ - directories_to_mount = list(set([str(poetry_package_path), *DIRECTORIES_TO_ALWAYS_MOUNT])) - return dagger_client.host().directory( - ".", - exclude=DEFAULT_EXCLUDE, - include=directories_to_mount, - ) - - -async def get_poetry_package_dir(airbyte_repo_dir: dagger.Directory, poetry_package_path: Path) -> dagger.Directory: - """Get the poetry package directory from the airbyte repo directory. - - Args: - airbyte_repo_dir (dagger.Directory): The airbyte repo directory. - poetry_package_path (Path): Path to the poetry package in the airbyte repo. - - Raises: - FileNotFoundError: If the pyproject.toml file is not found in the poetry package directory. - FileNotFoundError: If the poetry package directory is not found in the airbyte repo directory. - - Returns: - dagger.Directory: The poetry package directory. - """ - try: - package_directory = await airbyte_repo_dir.directory(str(poetry_package_path)) - if PYPROJECT_TOML_FILE_PATH not in await package_directory.entries(): - raise FileNotFoundError(f"Could not find pyproject.toml in {poetry_package_path}, are you sure this is a poetry package?") - except dagger.DaggerError: - raise FileNotFoundError(f"Could not find {poetry_package_path} in the repository, are you sure this path is correct?") - return package_directory - - -async def get_airbyte_ci_package_config(poetry_package_dir: dagger.Directory) -> AirbyteCiPackageConfiguration: - """Get the airbyte ci package configuration from the pyproject.toml file in the poetry package directory. - - Args: - poetry_package_dir (dagger.Directory): The poetry package directory. - - Returns: - AirbyteCiPackageConfiguration: The airbyte ci package configuration. - """ - raw_pyproject_toml = await poetry_package_dir.file(PYPROJECT_TOML_FILE_PATH).contents() - pyproject_toml = toml.loads(raw_pyproject_toml) - return deserialize_airbyte_ci_config(pyproject_toml) - - -def get_poetry_base_container(dagger_client: dagger.Client, python_version: str) -> dagger.Container: - """Get a base container with system dependencies to run poe tasks of poetry package: - - git: required for packages using GitPython - - poetry - - poethepoet - - docker: required for packages using docker in their tests - - Args: - dagger_client (dagger.Client): The dagger client. - - Returns: - dagger.Container: The base container. - """ - poetry_cache_volume: dagger.CacheVolume = dagger_client.cache_volume(POETRY_CACHE_VOLUME_NAME) - poetry_cache_path = "/root/.cache/poetry" - container_image = VERSION_CONTAINER_IMAGES.get(python_version, DEFAULT_CONTAINER_IMAGE.format(version=python_version)) - return ( - dagger_client.container() - .from_(container_image) - .with_env_variable("PIPX_BIN_DIR", "/usr/local/bin") - .with_env_variable("POETRY_CACHE_DIR", poetry_cache_path) - .with_mounted_cache(poetry_cache_path, poetry_cache_volume) - .with_exec( - sh_dash_c( - [ - "apt-get update", - "apt-get install -y bash git curl", - "pip install pipx", - "pipx ensurepath", - "pipx install poetry", - "pipx install poethepoet", - ] - ) - ) - .with_env_variable("VERSION", DOCKER_VERSION) - .with_exec(sh_dash_c(["curl -fsSL https://get.docker.com | sh"])) - ) - - -def prepare_container_for_poe_tasks( - dagger_client: dagger.Client, - airbyte_repo_dir: dagger.Directory, - airbyte_ci_package_config: AirbyteCiPackageConfiguration, - poetry_package_path: Path, - pipeline_context_params: Dict, - python_version: str, -) -> dagger.Container: - """Prepare a container to run poe tasks for a poetry package. - - Args: - dagger_client (dagger.Client): The dagger client. - airbyte_repo_dir (dagger.Directory): The airbyte repo directory. - airbyte_ci_package_config (AirbyteCiPackageConfiguration): The airbyte ci package configuration. - poetry_package_path (Path): The path to the poetry package in the airbyte repo. - pipeline_context_params (Dict): The pipeline context parameters. - - Returns: - dagger.Container: The container to run poe tasks for the poetry package. - """ - - # BE CAREFUL ABOUT THE ORDER OF THESE INSTRUCTIONS - # PLEASE REMIND THAT DAGGER OPERATION ARE CACHED LIKE IN DOCKERFILE: - # ANY CHANGE IN THE INPUTS OF AN OPERATION WILL INVALIDATE THE DOWNSTREAM OPERATIONS CACHE - - # Start from the base container - container = get_poetry_base_container(dagger_client, python_version) - - # Set the CI environment variable - is_ci = pipeline_context_params["is_ci"] - if is_ci: - container = container.with_env_variable("CI", "true") - - # Bind to dockerd service if needed - if airbyte_ci_package_config.side_car_docker_engine: - dockerd_service = docker.with_global_dockerd_service(dagger_client) - container = ( - container.with_env_variable("DOCKER_HOST", f"tcp://{DOCKER_HOST_NAME}:{DOCKER_HOST_PORT}") - .with_env_variable("DOCKER_HOST_NAME", DOCKER_HOST_NAME) - .with_service_binding(DOCKER_HOST_NAME, dockerd_service) - ) - - # Mount the docker socket if needed - if airbyte_ci_package_config.mount_docker_socket: - container = container.with_unix_socket("/var/run/docker.sock", dagger_client.host().unix_socket("/var/run/docker.sock")) - - # Set the required environment variables according to the package configuration - for required_env_var in airbyte_ci_package_config.required_environment_variables: - # We consider any environment variable as a secret for safety reasons - secret_env_var = dagger_client.set_secret(required_env_var, os.environ[required_env_var]) - container = container.with_secret_variable(required_env_var, secret_env_var) - - # Mount the airbyte repo directory - container = container.with_mounted_directory("/airbyte", airbyte_repo_dir) - - # Set working directory to the poetry package directory - container = container.with_workdir(f"/airbyte/{poetry_package_path}") - - # If a package from `airbyte-platform-internal` is required, modify the entry in pyproject.toml to use https instead of ssh, - # when run in Github Actions - # This is currently required for getting the connection-retriever package, for regression tests. - if is_ci: - container = ( - container.with_exec( - [ - "sed", - "-i", - "-E", - r"s,git@github\.com:airbytehq/airbyte-platform-internal,https://github.com/airbytehq/airbyte-platform-internal.git,", - "pyproject.toml", - ], - use_entrypoint=True, - ) - .with_exec( - [ - "poetry", - "source", - "add", - "--priority=supplemental", - "airbyte-platform-internal-source", - "https://github.com/airbytehq/airbyte-platform-internal.git", - ], - use_entrypoint=True, - ) - .with_secret_variable( - "CI_GITHUB_ACCESS_TOKEN", - dagger_client.set_secret("CI_GITHUB_ACCESS_TOKEN", pipeline_context_params["ci_github_access_token"].value), - ) - .with_exec( - [ - "/bin/sh", - "-c", - "poetry config http-basic.airbyte-platform-internal-source octavia-squidington-iii $CI_GITHUB_ACCESS_TOKEN", - ], - use_entrypoint=True, - ) - .with_exec(["poetry", "lock"], use_entrypoint=True) - ) - - # Install the poetry package - container = container.with_exec( - ["poetry", "install"] - + [f"--with={group}" for group in airbyte_ci_package_config.optional_poetry_groups] - + [f"--extras={extra}" for extra in airbyte_ci_package_config.poetry_extras] - ) - return container - - -async def run_poe_task(container: dagger.Container, poe_task: str) -> PoeTaskResult: - """Run the poe task in the container and return a PoeTaskResult. - - Args: - container (dagger.Container): The container to run the poe task in. - poe_task (str): The poe task to run. - - Returns: - PoeTaskResult: The result of the command execution. - """ - try: - executed_container = await container.with_exec(["poe", poe_task], use_entrypoint=True) - return PoeTaskResult( - task_name=poe_task, - status=StepStatus.SUCCESS, - stdout=await executed_container.stdout(), - stderr=await executed_container.stderr(), - ) - except dagger.ExecError as e: - return PoeTaskResult(task_name=poe_task, status=StepStatus.FAILURE, exc_info=e) - - -async def run_and_log_poe_task_results( - pipeline_context_params: Dict, package_name: str, container: dagger.Container, poe_task: str, logger: Logger -) -> PoeTaskResult: - """Run the poe task in the container and log the result. - - Args: - pipeline_context_params (Dict): The pipeline context parameters. - package_name (str): The name of the package to run the poe task for. - container (dagger.Container): The container to run the poe task in. - poe_task (str): The poe task to run. - logger (Logger): The logger to log the result. - - Returns: - PoeTaskResult: The result of the command execution. - """ - - commit_status_check_params = { - "sha": pipeline_context_params["git_revision"], - "description": f"{poe_task} execution for {package_name}", - "context": f"{package_name} - {poe_task}", - "target_url": f"{pipeline_context_params['gha_workflow_run_url']}", - "should_send": pipeline_context_params["is_ci"], - "logger": logger, - } - - logger.info(f"Running poe task: {poe_task}") - # Send pending status check - update_commit_status_check(**{**commit_status_check_params, "state": "pending"}) - result = await run_poe_task(container, poe_task) - result.log(logger) - # Send the final status check - update_commit_status_check(**{**commit_status_check_params, "state": result.status.get_github_state()}) - - return result - - -async def run_poe_tasks_for_package( - dagger_client: dagger.Client, poetry_package_path: Path, pipeline_context_params: Dict -) -> List[PoeTaskResult]: - """Concurrently Run the poe tasks declared in pyproject.toml for a poetry package. - - Args: - dagger_client (dagger.Client): The dagger client. - poetry_package_path (Path): The path to the poetry package in the airbyte repo. - pipeline_context_params (Dict): The pipeline context parameters. - Returns: - List[PoeTaskResult]: The results of the poe tasks. - """ - dagger_client = dagger_client - airbyte_repo_dir = await get_filtered_airbyte_repo_dir(dagger_client, poetry_package_path) - package_dir = await get_poetry_package_dir(airbyte_repo_dir, poetry_package_path) - package_config = await get_airbyte_ci_package_config(package_dir) - - logger = logging.getLogger(str(poetry_package_path)) - - if not package_config.poe_tasks: - logger.warning("No poe tasks to run.") - return [] - - logger.info(f"Python versions: {package_config.python_versions}") - - poe_task_results: List[asyncer.SoonValue] = [] - return_results = [] - - for python_version in package_config.python_versions: - container = prepare_container_for_poe_tasks( - dagger_client, airbyte_repo_dir, package_config, poetry_package_path, pipeline_context_params, python_version - ) - - async with asyncer.create_task_group() as poe_tasks_task_group: - for task in package_config.poe_tasks: - poe_task_results.append( - poe_tasks_task_group.soonify(run_and_log_poe_task_results)( - pipeline_context_params, str(poetry_package_path), container, task, logger.getChild(f"@{python_version}") - ) - ) - - return_results.extend([result.value for result in poe_task_results]) - - return return_results diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/commands.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/commands.py deleted file mode 100644 index 690c5ae56f94..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/update/commands.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import logging - -import asyncclick as click - -from pipelines.cli.auto_update import is_dev_command -from pipelines.external_scripts.airbyte_ci_dev_install import main as install_airbyte_ci_dev_pipx -from pipelines.external_scripts.airbyte_ci_install import main as install_airbyte_ci_binary - - -@click.command() -@click.option("--version", default="latest", type=str, help="The version to update to.") -async def update(version: str) -> None: - """Updates airbyte-ci to the latest version.""" - is_dev = is_dev_command() - if is_dev: - logging.info("Updating to the latest development version of airbyte-ci...") - install_airbyte_ci_dev_pipx() - else: - logging.info("Updating to the latest version of airbyte-ci...") - install_airbyte_ci_binary(version) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/airbyte_ci.py b/airbyte-ci/connectors/pipelines/pipelines/cli/airbyte_ci.py deleted file mode 100644 index ca06ffeab448..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/airbyte_ci.py +++ /dev/null @@ -1,218 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module is the CLI entrypoint to the airbyte-ci commands.""" - -from __future__ import annotations - -# HACK! IMPORTANT! This import and function call must be the first import in this file -# This is needed to ensure that the working directory is the root of the airbyte repo -# ruff: noqa: E402 -from pipelines.cli.ensure_repo_root import set_working_directory_to_root - -set_working_directory_to_root() - -import logging -import multiprocessing -import os -import sys -from typing import Optional - -import asyncclick as click -import docker # type: ignore -from github import PullRequest - -from pipelines import main_logger -from pipelines.cli.auto_update import __installed_version__, check_for_upgrade, pre_confirm_auto_update_flag -from pipelines.cli.click_decorators import ( - CI_REQUIREMENTS_OPTION_NAME, - click_append_to_context_object, - click_ci_requirements_option, - click_ignore_unused_kwargs, - click_merge_args_into_context_obj, -) -from pipelines.cli.confirm_prompt import pre_confirm_all_flag -from pipelines.cli.lazy_group import LazyGroup -from pipelines.cli.secrets import wrap_gcp_credentials_in_secret, wrap_in_secret -from pipelines.cli.telemetry import click_track_command -from pipelines.consts import DAGGER_WRAP_ENV_VAR_NAME, LOCAL_BUILD_PLATFORM, CIContext -from pipelines.dagger.actions.connector.hooks import get_dagger_sdk_version -from pipelines.helpers import github -from pipelines.helpers.git import get_current_git_branch, get_current_git_revision -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL, AIRBYTE_GITHUB_REPO_URL_PREFIX -from pipelines.helpers.utils import get_current_epoch_time -from pipelines.models.secrets import InMemorySecretStore - - -def log_context_info(ctx: click.Context) -> None: - main_logger.info(f"Running airbyte-ci version {__installed_version__}") - main_logger.info(f"Running dagger version {get_dagger_sdk_version()}") - main_logger.info("Running airbyte-ci in CI mode.") - main_logger.info(f"CI Context: {ctx.obj['ci_context']}") - main_logger.info(f"CI Report Bucket Name: {ctx.obj['ci_report_bucket_name']}") - main_logger.info(f"Git Repo URL: {ctx.obj['git_repo_url']}") - main_logger.info(f"Git Branch: {ctx.obj['git_branch']}") - main_logger.info(f"Git Revision: {ctx.obj['git_revision']}") - main_logger.info(f"GitHub Workflow Run ID: {ctx.obj['gha_workflow_run_id']}") - main_logger.info(f"GitHub Workflow Run URL: {ctx.obj['gha_workflow_run_url']}") - main_logger.info(f"Pull Request Number: {ctx.obj['pull_request_number']}") - main_logger.info(f"Pipeline Start Timestamp: {ctx.obj['pipeline_start_timestamp']}") - main_logger.info(f"Local build platform: {LOCAL_BUILD_PLATFORM}") - - -def _get_gha_workflow_run_url(ctx: click.Context) -> Optional[str]: - gha_workflow_run_id = ctx.obj["gha_workflow_run_id"] - if not gha_workflow_run_id: - return None - - return f"{AIRBYTE_GITHUB_REPO_URL_PREFIX}/actions/runs/{gha_workflow_run_id}" - - -def _get_pull_request(ctx: click.Context) -> Optional[PullRequest.PullRequest]: - pull_request_number = ctx.obj["pull_request_number"] - ci_github_access_token = ctx.obj["ci_github_access_token"] - - can_get_pull_request = pull_request_number and ci_github_access_token - if not can_get_pull_request: - return None - return github.get_pull_request(pull_request_number, ci_github_access_token) - - -def check_local_docker_configuration() -> None: - try: - docker_client = docker.from_env() - except Exception as e: - raise click.UsageError(f"Could not connect to docker daemon: {e}") - daemon_info = docker_client.info() - docker_cpus_count = daemon_info["NCPU"] - local_cpus_count = multiprocessing.cpu_count() - if docker_cpus_count < local_cpus_count: - logging.warning( - f"Your docker daemon is configured with less CPUs than your local machine ({docker_cpus_count} vs. {local_cpus_count}). This may slow down the airbyte-ci execution. Please consider increasing the number of CPUs allocated to your docker daemon in the Resource Allocation settings of Docker." - ) - - -def is_dagger_run_enabled_by_default() -> bool: - if CI_REQUIREMENTS_OPTION_NAME in sys.argv: - return False - - dagger_run_by_default = [ - ["connectors", "test"], - ["connectors", "build"], - ["test"], - ["metadata_service"], - ] - - for command_tokens in dagger_run_by_default: - if all(token in sys.argv for token in command_tokens): - return True - - return False - - -def check_dagger_wrap() -> bool: - """ - Check if the command is already wrapped by dagger run. - This is useful to avoid infinite recursion when calling dagger run from dagger run. - """ - return os.getenv(DAGGER_WRAP_ENV_VAR_NAME) == "true" - - -def is_current_process_wrapped_by_dagger_run() -> bool: - """ - Check if the current process is wrapped by dagger run. - """ - called_with_dagger_run = check_dagger_wrap() - main_logger.info(f"Called with dagger run: {called_with_dagger_run}") - return called_with_dagger_run - - -# COMMANDS - - -@click.group( - cls=LazyGroup, - help="Airbyte CI top-level command group.", - lazy_subcommands={ - "connectors": "pipelines.airbyte_ci.connectors.commands.connectors", - "poetry": "pipelines.airbyte_ci.poetry.commands.poetry", - "metadata": "pipelines.airbyte_ci.metadata.commands.metadata", - "test": "pipelines.airbyte_ci.test.commands.test", - "update": "pipelines.airbyte_ci.update.commands.update", - }, -) -@click.version_option(__installed_version__) -@pre_confirm_all_flag -@pre_confirm_auto_update_flag -@click.option("--enable-dagger-run/--disable-dagger-run", default=is_dagger_run_enabled_by_default) -@click.option("--enable-update-check/--disable-update-check", default=True) -@click.option("--enable-auto-update/--disable-auto-update", default=True) -@click.option("--is-local/--is-ci", default=True) -@click.option("--git-repo-url", default=AIRBYTE_GITHUB_REPO_URL, envvar="CI_GIT_REPO_URL") -@click.option("--git-branch", default=get_current_git_branch, envvar="CI_GIT_BRANCH") -@click.option("--git-revision", default=get_current_git_revision, envvar="CI_GIT_REVISION") -@click.option( - "--diffed-branch", - help="Branch to which the git diff will happen to detect new or modified connectors", - default="master", - type=str, -) -@click.option("--gha-workflow-run-id", help="[CI Only] The run id of the GitHub action workflow", default=None, type=str) -@click.option("--ci-context", default=CIContext.MANUAL, envvar="CI_CONTEXT", type=click.Choice([c for c in CIContext])) -@click.option("--pipeline-start-timestamp", default=get_current_epoch_time, envvar="CI_PIPELINE_START_TIMESTAMP", type=int) -@click.option("--pull-request-number", envvar="PULL_REQUEST_NUMBER", type=int) -@click.option("--ci-git-user", default="octavia-squidington-iii", envvar="CI_GIT_USER", type=str) -@click.option("--ci-github-access-token", envvar="CI_GITHUB_ACCESS_TOKEN", type=str, callback=wrap_in_secret) -@click.option("--ci-report-bucket-name", envvar="CI_REPORT_BUCKET_NAME", type=str) -@click.option("--ci-artifact-bucket-name", envvar="CI_ARTIFACT_BUCKET_NAME", type=str) -@click.option( - "--ci-gcp-credentials", - help="The service account to use during CI.", - type=click.STRING, - required=False, # Not required for pre-release or local pipelines - envvar="GCP_GSM_CREDENTIALS", - callback=wrap_gcp_credentials_in_secret, -) -@click.option("--ci-job-key", envvar="CI_JOB_KEY", type=str) -@click.option("--s3-build-cache-access-key-id", envvar="S3_BUILD_CACHE_ACCESS_KEY_ID", type=str, callback=wrap_in_secret) -@click.option("--s3-build-cache-secret-key", envvar="S3_BUILD_CACHE_SECRET_KEY", type=str, callback=wrap_in_secret) -@click.option("--show-dagger-logs/--hide-dagger-logs", default=False, type=bool) -@click_ci_requirements_option() -@click_track_command -@click_merge_args_into_context_obj -@click_append_to_context_object("is_ci", lambda ctx: not ctx.obj["is_local"]) -@click_append_to_context_object("gha_workflow_run_url", _get_gha_workflow_run_url) -@click_append_to_context_object("pull_request", _get_pull_request) -@click.pass_context -@click_ignore_unused_kwargs -async def airbyte_ci(ctx: click.Context) -> None: # noqa D103 - # Check that the command being run is not upgrade - is_update_command = ctx.invoked_subcommand == "update" - if ctx.obj["enable_update_check"] and ctx.obj["is_local"] and not is_update_command: - check_for_upgrade( - require_update=ctx.obj["is_local"], - enable_auto_update=ctx.obj["is_local"] and ctx.obj["enable_auto_update"], - ) - - if ctx.obj["enable_dagger_run"] and not is_current_process_wrapped_by_dagger_run(): - main_logger.debug("Re-Running airbyte-ci with dagger run.") - from pipelines.cli.dagger_run import call_current_command_with_dagger_run - - call_current_command_with_dagger_run() - return - - if ctx.obj["is_local"]: - # This check is meaningful only when running locally - # In our CI the docker host used by the Dagger Engine is different from the one used by the runner. - check_local_docker_configuration() - - if not ctx.obj["is_local"]: - log_context_info(ctx) - - if not ctx.obj.get("secret_stores", {}).get("in_memory"): - ctx.obj["secret_stores"] = {"in_memory": InMemorySecretStore()} - - -if __name__ == "__main__": - airbyte_ci() diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/auto_update.py b/airbyte-ci/connectors/pipelines/pipelines/cli/auto_update.py deleted file mode 100644 index 6f7c16bae0ca..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/auto_update.py +++ /dev/null @@ -1,145 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -# HELPERS -from __future__ import annotations - -import importlib -import logging -import os -import sys -from typing import TYPE_CHECKING - -import asyncclick as click -import requests - -from pipelines import main_logger -from pipelines.cli.confirm_prompt import confirm -from pipelines.consts import LOCAL_PIPELINE_PACKAGE_PATH -from pipelines.external_scripts.airbyte_ci_install import RELEASE_URL, get_airbyte_os_name - -if TYPE_CHECKING: - from typing import Callable - -__installed_version__ = importlib.metadata.version("pipelines") - -PROD_COMMAND = "airbyte-ci" -DEV_COMMAND = "airbyte-ci-dev" -AUTO_UPDATE_AGREE_KEY = "yes_auto_update" - - -def pre_confirm_auto_update_flag(f: Callable) -> Callable: - """Decorator to add a --yes-auto-update flag to a command.""" - return click.option( - "--yes-auto-update/--no-auto-update", - AUTO_UPDATE_AGREE_KEY, - is_flag=True, - default=True, - help="Skip prompts and automatically upgrade pipelines", - )(f) - - -def _is_version_available(version: str, is_dev: bool) -> bool: - """ - Check if an given version is available. - """ - - # Given that they can install from source, we don't need to check for upgrades - if is_dev: - return True - - os_name = get_airbyte_os_name() - url = f"{RELEASE_URL}/{os_name}/{version}/airbyte-ci" - - # Just check if the URL exists, but dont download it - return requests.head(url).ok - - -def _get_latest_version() -> str: - """ - Get the version of the latest release, which is just in the pyproject.toml file of the pipelines package - as this is an internal tool, we don't need to check for the latest version on PyPI - """ - path_to_pyproject_toml = LOCAL_PIPELINE_PACKAGE_PATH + "pyproject.toml" - with open(path_to_pyproject_toml, "r") as f: - for line in f.readlines(): - if "version" in line: - return line.split("=")[1].strip().replace('"', "") - raise Exception("Could not find version in pyproject.toml. Please ensure you are running from the root of the airbyte repo.") - - -def is_dev_command() -> bool: - """ - Check if the current command is the dev version of the command - """ - current_command = " ".join(sys.argv) - return DEV_COMMAND in current_command - - -def check_for_upgrade( - require_update: bool = True, - enable_auto_update: bool = True, -) -> None: - """Check if the installed version of pipelines is up to date.""" - current_command = " ".join(sys.argv) - latest_version = _get_latest_version() - is_out_of_date = latest_version > __installed_version__ - if not is_out_of_date: - main_logger.info(f"airbyte-ci is up to date. Installed version: {__installed_version__}. Latest version: {latest_version}") - return - - is_dev_version = is_dev_command() - upgrade_available = _is_version_available(latest_version, is_dev_version) - if not upgrade_available: - main_logger.warning( - f"airbyte-ci is out of date, but no upgrade is available yet. This likely means that a release is still being built. Installed version: {__installed_version__}. Latest version: {latest_version}" - ) - return - - parent_command = DEV_COMMAND if is_dev_version else PROD_COMMAND - upgrade_command = f"{parent_command} update" - - # Tack on the specific version if it is not the latest version and it is not the dev version - # This is because the dev version always corresponds to the version in the local repository - if not is_dev_version: - upgrade_command = f"{upgrade_command} --version {latest_version}" - - upgrade_error_message = f""" - 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 - - This version of `airbyte-ci` does not match that of your local airbyte repository. - - Installed Version: {__installed_version__}. - Local Repository Version: {latest_version} - - Please upgrade your local airbyte repository to the latest version using the following command: - $ {upgrade_command} - - Alternatively you can skip this with the `--disable-update-check` flag. - - 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 - """ - logging.warning(upgrade_error_message) - - # Ask the user if they want to upgrade - if enable_auto_update and confirm( - "Do you want to automatically upgrade?", default=True, additional_pre_confirm_key=AUTO_UPDATE_AGREE_KEY - ): - # if the current command contains `airbyte-ci-dev` is the dev version of the command - logging.info(f"[{'DEV' if is_dev_version else 'BINARY'}] Upgrading pipelines...") - - upgrade_exit_code = os.system(upgrade_command) - if upgrade_exit_code != 0: - raise Exception(f"Failed to upgrade pipelines. Exit code: {upgrade_exit_code}") - - logging.info(f"Re-running command: {current_command}") - - # Re-run the command - command_exit_code = os.system(current_command) - sys.exit(command_exit_code) - - if require_update: - raise Exception(upgrade_error_message) - - return diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/click_decorators.py b/airbyte-ci/connectors/pipelines/pipelines/cli/click_decorators.py deleted file mode 100644 index 7ede57342b7b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/click_decorators.py +++ /dev/null @@ -1,153 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import functools -import inspect -from functools import wraps -from typing import Any, Callable, Type, TypeVar - -import asyncclick as click - -from pipelines.models.ci_requirements import CIRequirements - -_AnyCallable = Callable[..., Any] -FC = TypeVar("FC", bound="_AnyCallable | click.core.Command") -CI_REQUIREMENTS_OPTION_NAME = "--ci-requirements" - - -def _contains_var_kwarg(f: Callable) -> bool: - return any(param.kind is inspect.Parameter.VAR_KEYWORD for param in inspect.signature(f).parameters.values()) - - -def _is_kwarg_of(key: str, f: Callable) -> bool: - param = inspect.signature(f).parameters.get(key) - if not param: - return False - - return bool(param) and (param.kind is inspect.Parameter.KEYWORD_ONLY or param.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD) - - -def click_ignore_unused_kwargs(f: Callable) -> Callable: - """Make function ignore unmatched kwargs. - - If the function already has the catch all **kwargs, do nothing. - - Useful in the case that the argument is meant to be passed to a child command - and is not used by the parent command - """ - if _contains_var_kwarg(f): - return f - - @functools.wraps(f) - def inner(*args: Any, **kwargs: Any) -> Callable: - filtered_kwargs = {key: value for key, value in kwargs.items() if _is_kwarg_of(key, f)} - return f(*args, **filtered_kwargs) - - return inner - - -def click_merge_args_into_context_obj(f: Callable) -> Callable: - """ - Decorator to pass click context and args to children commands. - """ - - def wrapper(*args: Any, **kwargs: Any) -> Callable: - ctx = click.get_current_context() - ctx.ensure_object(dict) - click_obj = ctx.obj - click_params = ctx.params - command_name = ctx.command.name - - # Error if click_obj and click_params have the same key - intersection = set(click_obj.keys()) & set(click_params.keys()) - if intersection: - raise ValueError(f"Your command '{command_name}' has defined options/arguments with the same key as its parent: {intersection}") - - ctx.obj = {**click_obj, **click_params} - return f(*args, **kwargs) - - return wrapper - - -def click_append_to_context_object(key: str, value: Callable) -> Callable: - """ - Decorator to append a value to the click context object. - """ - - def decorator(f: Callable) -> Callable: - async def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 - ctx = click.get_current_context() - ctx.ensure_object(dict) - - # if async, get the value, cannot use await - if inspect.iscoroutinefunction(value): - ctx.obj[key] = await value(ctx) - elif callable(value): - ctx.obj[key] = value(ctx) - else: - ctx.obj[key] = value - return await f(*args, **kwargs) - - return wrapper - - return decorator - - -class LazyPassDecorator: - """ - Used to create a decorator that will pass an instance of the given class to the decorated function. - """ - - def __init__(self, cls: Type[Any], *args: Any, **kwargs: Any) -> None: - """ - Initialize the decorator with the given source class - """ - self.cls = cls - self.args = args - self.kwargs = kwargs - - def __call__(self, f: Callable[..., Any]) -> Callable[..., Any]: - """ - Create a decorator that will pass an instance of the given class to the decorated function. - """ - - @wraps(f) - def decorated_function(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 - # Check if the kwargs already contain the arguments being passed by the decorator - decorator_kwargs = {k: v for k, v in self.kwargs.items() if k not in kwargs} - # Create an instance of the class - instance = self.cls(*self.args, **decorator_kwargs) - # If function has **kwargs, we can put the instance there - if "kwargs" in kwargs: - kwargs["kwargs"] = instance - # Otherwise, add it to positional arguments - else: - args = (*args, instance) - return f(*args, **kwargs) - - return decorated_function - - -def click_ci_requirements_option() -> Callable[[FC], FC]: - """Add a --ci-requirements option to the command. - - Returns: - Callable[[FC], FC]: The decorated command. - """ - - def callback(ctx: click.Context, param: click.Parameter, value: bool) -> None: - if value: - ci_requirements = CIRequirements() - click.echo(ci_requirements.to_json()) - ctx.exit() - - return click.decorators.option( - CI_REQUIREMENTS_OPTION_NAME, - is_flag=True, - expose_value=False, - is_eager=True, - flag_value=True, - help="Show the CI requirements and exit. It used to make airbyte-ci client define the CI runners it will run on.", - callback=callback, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/confirm_prompt.py b/airbyte-ci/connectors/pipelines/pipelines/cli/confirm_prompt.py deleted file mode 100644 index b7504cb452f1..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/confirm_prompt.py +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import asyncclick as click - -if TYPE_CHECKING: - from typing import Any, Callable - -PRE_CONFIRM_ALL_KEY = "yes" - - -def pre_confirm_all_flag(f: Callable) -> Callable: - """Decorator to add a --yes flag to a command.""" - return click.option("-y", "--yes", PRE_CONFIRM_ALL_KEY, is_flag=True, default=False, help="Skip prompts and use default values")(f) - - -def confirm(*args: Any, **kwargs: Any) -> bool: - """Confirm a prompt with the user, with support for a --yes flag.""" - additional_pre_confirm_key = kwargs.pop("additional_pre_confirm_key", None) - ctx = click.get_current_context() - if ctx.obj.get(PRE_CONFIRM_ALL_KEY, False): - return True - - if additional_pre_confirm_key: - if ctx.obj.get(additional_pre_confirm_key, False): - return True - - return click.confirm(*args, **kwargs) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_pipeline_command.py b/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_pipeline_command.py deleted file mode 100644 index 9120bf59921e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_pipeline_command.py +++ /dev/null @@ -1,108 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups util function used in pipelines.""" - -from __future__ import annotations - -import sys -from pathlib import Path - -import asyncclick as click -from dagger import DaggerError - -from pipelines import consts, main_logger -from pipelines.consts import GCS_PUBLIC_DOMAIN, STATIC_REPORT_PREFIX -from pipelines.helpers import sentry_utils -from pipelines.helpers.gcs import upload_to_gcs -from pipelines.helpers.utils import slugify - - -class DaggerPipelineCommand(click.Command): - @sentry_utils.with_command_context - async def invoke(self, ctx: click.Context) -> None: - """Wrap parent invoke in a try catch suited to handle pipeline failures. - Args: - ctx (click.Context): The invocation context. - Raises: - e: Raise whatever exception that was caught. - """ - command_name = self.name - main_logger.info(f"Running Dagger Command {command_name}...") - main_logger.info( - "If you're running this command for the first time the Dagger engine image will be pulled, it can take a short minute..." - ) - ctx.obj["report_output_prefix"] = self.render_report_output_prefix(ctx) - dagger_logs_gcs_key = f"{ctx.obj['report_output_prefix']}/dagger-logs.txt" - try: - if not ctx.obj["show_dagger_logs"]: - dagger_log_dir = Path(f"{consts.LOCAL_REPORTS_PATH_ROOT}/{ctx.obj['report_output_prefix']}") - dagger_log_path = Path(f"{dagger_log_dir}/dagger.log").resolve() - ctx.obj["dagger_logs_path"] = dagger_log_path - main_logger.info(f"Saving dagger logs to: {dagger_log_path}") - if ctx.obj["is_ci"]: - ctx.obj["dagger_logs_url"] = f"{GCS_PUBLIC_DOMAIN}/{ctx.obj['ci_report_bucket_name']}/{dagger_logs_gcs_key}" - else: - ctx.obj["dagger_logs_url"] = None - else: - ctx.obj["dagger_logs_path"] = None - pipeline_success = await super().invoke(ctx) - if not pipeline_success: - raise DaggerError(f"Dagger Command {command_name} failed.") - except DaggerError as e: - main_logger.error(f"Dagger Command {command_name} failed", exc_info=e) - sys.exit(1) - finally: - if ctx.obj.get("dagger_logs_path"): - main_logger.info(f"Dagger logs saved to {ctx.obj['dagger_logs_path']}") - if ctx.obj["is_ci"] and ctx.obj["ci_gcp_credentials"] and ctx.obj["ci_report_bucket_name"]: - gcs_uri, public_url = upload_to_gcs( - ctx.obj["dagger_logs_path"], - ctx.obj["ci_report_bucket_name"], - dagger_logs_gcs_key, - ctx.obj["ci_gcp_credentials"].value, - ) - main_logger.info(f"Dagger logs saved to {gcs_uri}. Public URL: {public_url}") - - @staticmethod - def render_report_output_prefix(ctx: click.Context) -> str: - """Render the report output prefix for any command in the Connector CLI. - - The goal is to standardize the output of all logs and reports generated by the CLI - related to a specific command, and to a specific CI context. - - Note: We cannot hoist this higher in the command hierarchy because only one level of - subcommands are available at the time the context is created. - """ - - git_branch = ctx.obj["git_branch"] - git_revision = ctx.obj["git_revision"] - pipeline_start_timestamp = ctx.obj["pipeline_start_timestamp"] - ci_context = ctx.obj["ci_context"] - ci_job_key = ctx.obj["ci_job_key"] if ctx.obj.get("ci_job_key") else ci_context - - sanitized_branch = slugify(git_branch.replace("/", "_")) - - # get the command name for the current context, if a group then prepend the parent command name - if ctx.command_path: - cmd_components = ctx.command_path.split(" ") - cmd_components[0] = STATIC_REPORT_PREFIX - cmd = "/".join(cmd_components) - else: - cmd = None - - path_values = [ - cmd, - ci_job_key, - sanitized_branch, - pipeline_start_timestamp, - git_revision, - ] - - # check all values are defined - if None in path_values: - raise ValueError(f"Missing value required to render the report output prefix: {path_values}") - - # join all values with a slash, and convert all values to string - return "/".join(map(str, path_values)) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_run.py b/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_run.py deleted file mode 100644 index d06921be5743..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/dagger_run.py +++ /dev/null @@ -1,127 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module execute the airbyte-ci-internal CLI wrapped in a dagger run command to use the Dagger Terminal UI.""" - -import logging -import os -import re -import subprocess -import sys -from pathlib import Path -from typing import Optional - -import pkg_resources # type: ignore -import requests # type: ignore - -from pipelines.consts import DAGGER_WRAP_ENV_VAR_NAME - -LOGGER = logging.getLogger(__name__) -BIN_DIR = Path.home() / "bin" -BIN_DIR.mkdir(exist_ok=True) -DAGGER_TELEMETRY_TOKEN_ENV_VAR_NAME_VALUE = ( - # The _EXPERIMENTAL_DAGGER_CLOUD_TOKEN is used for telemetry only at the moment. - # It will eventually be renamed to a more specific name in future Dagger versions. - "_EXPERIMENTAL_DAGGER_CLOUD_TOKEN", - "p.eyJ1IjogIjFiZjEwMmRjLWYyZmQtNDVhNi1iNzM1LTgxNzI1NGFkZDU2ZiIsICJpZCI6ICJlNjk3YzZiYy0yMDhiLTRlMTktODBjZC0yNjIyNGI3ZDBjMDEifQ.hT6eMOYt3KZgNoVGNYI3_v4CC-s19z8uQsBkGrBhU3k", -) - -ARGS_DISABLING_TUI = ["--no-tui", "--version", "publish", "upgrade-base-image", "--help", "format", "bump-version"] - - -def get_dagger_path() -> Optional[str]: - try: - return ( - subprocess.run(["which", "dagger"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip() - ) - except subprocess.CalledProcessError: - if Path(BIN_DIR / "dagger").exists(): - return str(Path(BIN_DIR / "dagger")) - return None - - -def get_current_dagger_sdk_version() -> str: - version = pkg_resources.get_distribution("dagger-io").version - return version - - -def install_dagger_cli(dagger_version: str) -> None: - install_script_path = "/tmp/install_dagger.sh" - with open(install_script_path, "w") as f: - response = requests.get("https://dl.dagger.io/dagger/install.sh") - response.raise_for_status() - f.write(response.text) - subprocess.run(["chmod", "+x", install_script_path], check=True) - os.environ["BIN_DIR"] = str(BIN_DIR) - os.environ["DAGGER_VERSION"] = dagger_version - subprocess.run([install_script_path], check=True) - - -def get_dagger_cli_version(dagger_path: Optional[str]) -> Optional[str]: - if not dagger_path: - return None - version_output = ( - subprocess.run([dagger_path, "version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip() - ) - version_pattern = r"v(\d+\.\d+\.\d+)" - - match = re.search(version_pattern, version_output) - - if match: - version = match.group(1) - return version - else: - raise Exception("Could not find dagger version in output: " + version_output) - - -def check_dagger_cli_install() -> str: - """ - If the dagger CLI is not installed, install it. - """ - - expected_dagger_cli_version = get_current_dagger_sdk_version() - dagger_path = get_dagger_path() - if dagger_path is None: - LOGGER.info(f"The Dagger CLI is not installed. Installing {expected_dagger_cli_version}...") - install_dagger_cli(expected_dagger_cli_version) - dagger_path = get_dagger_path() - assert dagger_path is not None, "Dagger CLI installation failed, dagger not found in path" - - cli_version = get_dagger_cli_version(dagger_path) - if cli_version != expected_dagger_cli_version: - LOGGER.warning( - f"The Dagger CLI version '{cli_version}' does not match the expected version '{expected_dagger_cli_version}'. Installing Dagger CLI '{expected_dagger_cli_version}'..." - ) - install_dagger_cli(expected_dagger_cli_version) - return check_dagger_cli_install() - return dagger_path - - -def mark_dagger_wrap() -> None: - """ - Mark that the dagger wrap has been applied. - """ - os.environ[DAGGER_WRAP_ENV_VAR_NAME] = "true" - - -def call_current_command_with_dagger_run() -> None: - mark_dagger_wrap() - # We're enabling telemetry only for local runs. - # CI runs already have telemetry as DAGGER_CLOUD_TOKEN env var is set on the CI. - if (os.environ.get("AIRBYTE_ROLE") == "airbyter") and not os.environ.get("CI"): - os.environ[DAGGER_TELEMETRY_TOKEN_ENV_VAR_NAME_VALUE[0]] = DAGGER_TELEMETRY_TOKEN_ENV_VAR_NAME_VALUE[1] - - exit_code = 0 - dagger_path = check_dagger_cli_install() - command = [dagger_path, "--silent", "run"] + sys.argv - try: - try: - LOGGER.info(f"Running command: {command}") - subprocess.run(command, check=True) - except KeyboardInterrupt: - LOGGER.info("Keyboard interrupt detected. Exiting...") - exit_code = 1 - except subprocess.CalledProcessError as e: - exit_code = e.returncode - sys.exit(exit_code) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/ensure_repo_root.py b/airbyte-ci/connectors/pipelines/pipelines/cli/ensure_repo_root.py deleted file mode 100644 index 5970979d9d71..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/ensure_repo_root.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -import logging -import os -from pathlib import Path - -import git - - -def _validate_airbyte_repo(repo: git.Repo) -> bool: - """Check if any of the remotes are the airbyte repo.""" - expected_repo_name = "airbytehq/airbyte" - for remote in repo.remotes: - if expected_repo_name in remote.url: - return True - - warning_message = f""" - ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ - - It looks like you are not running this command from the airbyte repo ({expected_repo_name}). - - If this command is run from outside the airbyte repo, it will not work properly. - - Please run this command your local airbyte project. - - ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ - """ - - logging.warning(warning_message) - - return False - - -def get_airbyte_repo() -> git.Repo: - """Get the airbyte repo.""" - repo = git.Repo(search_parent_directories=True) - _validate_airbyte_repo(repo) - return repo - - -def get_airbyte_repo_path_with_fallback() -> Path: - """Get the path to the airbyte repo.""" - try: - repo_path = get_airbyte_repo().working_tree_dir - if repo_path is not None: - return Path(str(get_airbyte_repo().working_tree_dir)) - except git.exc.InvalidGitRepositoryError: - pass - logging.warning("Could not find the airbyte repo, falling back to the current working directory.") - path = Path.cwd() - logging.warning(f"Using {path} as the airbyte repo path.") - return path - - -def set_working_directory_to_root() -> None: - """Set the working directory to the root of the airbyte repo.""" - working_dir = get_airbyte_repo_path_with_fallback() - logging.info(f"Setting working directory to {working_dir}") - os.chdir(working_dir) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/lazy_group.py b/airbyte-ci/connectors/pipelines/pipelines/cli/lazy_group.py deleted file mode 100644 index def24edb1b6d..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/lazy_group.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -# Source: https://click.palletsprojects.com/en/8.1.x/complex/ - -import importlib -from typing import Any, Dict, List, Optional - -import asyncclick as click - - -class LazyGroup(click.Group): - """ - A click Group that can lazily load subcommands. - """ - - def __init__(self, *args: Any, lazy_subcommands: Optional[Dict[str, str]] = None, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - # lazy_subcommands is a map of the form: - # - # {command-name} -> {module-name}.{command-object-name} - # - self.lazy_subcommands = lazy_subcommands or {} - - def list_commands(self, ctx: click.Context) -> List[str]: - base = super().list_commands(ctx) - lazy = sorted(self.lazy_subcommands.keys()) - return base + lazy - - def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]: - if cmd_name in self.lazy_subcommands: - return self._lazy_load(cmd_name) - return super().get_command(ctx, cmd_name) - - def _lazy_load(self, cmd_name: str) -> click.Command: - # lazily loading a command, first get the module name and attribute name - import_path = self.lazy_subcommands[cmd_name] - modname, cmd_object_name = import_path.rsplit(".", 1) - # do the import - mod = importlib.import_module(modname) - # get the Command object from that module - cmd_object = getattr(mod, cmd_object_name) - # check the result to make debugging easier - if not isinstance(cmd_object, click.Command): - print(f"{cmd_object} is of instance {type(cmd_object)}") - raise ValueError(f"Lazy loading of {import_path} failed by returning " "a non-command object") - return cmd_object diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/secrets.py b/airbyte-ci/connectors/pipelines/pipelines/cli/secrets.py deleted file mode 100644 index a477e89d2a71..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/secrets.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from typing import Any, Optional - -import asyncclick as click - -from pipelines.helpers.gcs import sanitize_gcp_credentials -from pipelines.models.secrets import InMemorySecretStore, Secret - - -def wrap_in_secret(ctx: click.Context, param: click.Option, value: Any) -> Optional[Secret]: # noqa - # Validate callback usage - if value is None: - return None - assert param.name is not None - if not isinstance(value, str): - raise click.BadParameter(f"{param.name} value is not a string, only strings can be wrapped in a secret.") - - # Make sure the context object is set or set it with an empty dict - ctx.ensure_object(dict) - - # Instantiate a global in memory secret store in the context object if it's not yet set - if "secret_stores" not in ctx.obj: - ctx.obj["secret_stores"] = {} - if "in_memory" not in ctx.obj["secret_stores"]: - ctx.obj["secret_stores"]["in_memory"] = InMemorySecretStore() - - # Add the CLI option value to the in memory secret store and wrap it in a Secret - ctx.obj["secret_stores"]["in_memory"].add_secret(param.name, value) - return Secret(param.name, ctx.obj["secret_stores"]["in_memory"]) - - -def wrap_gcp_credentials_in_secret(ctx: click.Context, param: click.Option, value: Any) -> Optional[Secret]: # noqa - # Validate callback usage - if value is None: - return None - if not isinstance(value, str): - raise click.BadParameter(f"{param.name} value is not a string, only strings can be wrapped in a secret.") - - value = sanitize_gcp_credentials(value) - return wrap_in_secret(ctx, param, value) diff --git a/airbyte-ci/connectors/pipelines/pipelines/cli/telemetry.py b/airbyte-ci/connectors/pipelines/pipelines/cli/telemetry.py deleted file mode 100644 index 8ecb606973a1..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/cli/telemetry.py +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import getpass -import hashlib -import os -import platform -import sys -from typing import TYPE_CHECKING - -import segment.analytics as analytics # type: ignore -from asyncclick import get_current_context - -DISABLE_TELEMETRY = os.environ.get("AIRBYTE_CI_DISABLE_TELEMETRY", "false").lower() == "true" - -if TYPE_CHECKING: - from typing import Any, Callable, Dict, Tuple - - from asyncclick import Command - -analytics.write_key = "G6G7whgro81g9xM00kN2buclGKvcOjFd" -analytics.send = not DISABLE_TELEMETRY -analytics.debug = False - - -def _is_airbyte_user() -> bool: - """Returns True if the user is airbyter, False otherwise.""" - return os.getenv("AIRBYTE_ROLE") == "airbyter" - - -def _get_anonymous_system_id() -> str: - """Returns a unique anonymous hashid of the current system info.""" - # Collect machine-specific information - machine_info = platform.node() - username = getpass.getuser() - - unique_system_info = f"{machine_info}-{username}" - - # Generate a unique hash - unique_id = hashlib.sha256(unique_system_info.encode()).hexdigest() - - return unique_id - - -def click_track_command(f: Callable) -> Callable: - """ - Decorator to track CLI commands with segment.io - """ - - def wrapper(*args: Tuple, **kwargs: Dict[str, Any]) -> Command: - ctx = get_current_context() - - top_level_command = ctx.command_path - full_cmd = " ".join(sys.argv) - - # remove anything prior to the command name f.__name__ - # to avoid logging inline secrets - sanitized_cmd = full_cmd[full_cmd.find(top_level_command) :] - sys_id = _get_anonymous_system_id() - sys_user_name = f"anonymous:{sys_id}" - airbyter = _is_airbyte_user() - - is_local = kwargs.get("is_local", False) - user_id = "local-user" if is_local else "ci-user" - event = f"airbyte-ci:{f.__name__}" - - # IMPORTANT! do not log kwargs as they may contain secrets - analytics.track(user_id, event, {"username": sys_user_name, "command": sanitized_cmd, "airbyter": airbyter}) - - return f(*args, **kwargs) - - return wrapper diff --git a/airbyte-ci/connectors/pipelines/pipelines/consts.py b/airbyte-ci/connectors/pipelines/pipelines/consts.py deleted file mode 100644 index 8846d2d1f927..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/consts.py +++ /dev/null @@ -1,101 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import os -import platform -from enum import Enum - -from dagger import Platform - -PYPROJECT_TOML_FILE_PATH = "pyproject.toml" -MANIFEST_FILE_PATH = "manifest.yaml" -COMPONENTS_FILE_PATH = "components.py" -LICENSE_SHORT_FILE_PATH = "LICENSE_SHORT" -CONNECTOR_TESTING_REQUIREMENTS = [ - "pip==21.3.1", - "mccabe==0.6.1", - "pytest==6.2.5", - "coverage[toml]==6.3.1", - "pytest-custom_exit_code", -] - -BUILD_PLATFORMS = (Platform("linux/amd64"), Platform("linux/arm64")) - -PLATFORM_MACHINE_TO_DAGGER_PLATFORM = { - "x86_64": Platform("linux/amd64"), - "arm64": Platform("linux/arm64"), - "aarch64": Platform("linux/amd64"), - "amd64": Platform("linux/amd64"), -} -LOCAL_MACHINE_TYPE = platform.machine() -LOCAL_BUILD_PLATFORM = PLATFORM_MACHINE_TO_DAGGER_PLATFORM[LOCAL_MACHINE_TYPE] -AMAZONCORRETTO_IMAGE = "amazoncorretto:21-al2023" -NODE_IMAGE = "node:18.18.0-slim" -MAVEN_IMAGE = "maven:3.9.6-amazoncorretto-21-al2023" -DOCKER_VERSION = "24" -DOCKER_DIND_IMAGE = f"docker:{DOCKER_VERSION}-dind" -DOCKER_CLI_IMAGE = f"docker:{DOCKER_VERSION}-cli" -DOCKER_REGISTRY_MIRROR_URL = os.getenv("DOCKER_REGISTRY_MIRROR_URL") -DOCKER_REGISTRY_ADDRESS = "docker.io" -DOCKER_VAR_LIB_VOLUME_NAME = "docker-cache-2" -GIT_IMAGE = "alpine/git:latest" -GIT_DIRECTORY_ROOT_PATH = ".git" -GRADLE_CACHE_PATH = "/root/.gradle/caches" -GRADLE_BUILD_CACHE_PATH = f"{GRADLE_CACHE_PATH}/build-cache-1" -GRADLE_READ_ONLY_DEPENDENCY_CACHE_PATH = "/root/gradle_dependency_cache" -LOCAL_REPORTS_PATH_ROOT = "airbyte-ci/connectors/pipelines/pipeline_reports/" -LOCAL_PIPELINE_PACKAGE_PATH = "airbyte-ci/connectors/pipelines/" -DOCS_DIRECTORY_ROOT_PATH = "docs/" -GCS_PUBLIC_DOMAIN = "https://storage.cloud.google.com" -DOCKER_HOST_NAME = "global-docker-host" -DOCKER_HOST_PORT = 2375 -DOCKER_TMP_VOLUME_NAME = "shared-tmp" -STATIC_REPORT_PREFIX = "airbyte-ci" -PIP_CACHE_VOLUME_NAME = "pip_cache" -PIP_CACHE_PATH = "/root/.cache/pip" -POETRY_CACHE_VOLUME_NAME = "poetry_cache" -POETRY_CACHE_PATH = "/root/.cache/pypoetry" -STORAGE_DRIVER = "fuse-overlayfs" -SETUP_PY_FILE_PATH = "setup.py" -DEFAULT_PYTHON_PACKAGE_REGISTRY_URL = "https://upload.pypi.org/legacy/" -DEFAULT_PYTHON_PACKAGE_REGISTRY_CHECK_URL = "https://pypi.org/pypi" -MAIN_CONNECTOR_TESTING_SECRET_STORE_ALIAS = "airbyte-connector-testing-secret-store" -AIRBYTE_SUBMODULE_DIR_NAME = "airbyte-submodule" -MANUAL_PIPELINE_STATUS_CHECK_OVERRIDE_PREFIXES = ["Regression Tests"] - -PUBLISH_UPDATES_SLACK_CHANNEL = "#connector-publish-updates" -PUBLISH_FAILURE_SLACK_CHANNEL = "#connector-publish-failures" -# TODO this should be passed via an env var or a CLI input -PATH_TO_LOCAL_CDK = "../airbyte-python-cdk" - - -class CIContext(str, Enum): - """An enum for Ci context values which can be ["manual", "pull_request", "nightly_builds"].""" - - MANUAL = "manual" - PULL_REQUEST = "pull_request" - MASTER = "master" - - def __str__(self) -> str: - return self.value - - -class ContextState(Enum): - """Enum to characterize the current context state, values are used for external representation on GitHub commit checks.""" - - INITIALIZED = {"github_state": "pending", "description": "Pipelines are being initialized..."} - RUNNING = {"github_state": "pending", "description": "Pipelines are running..."} - ERROR = {"github_state": "error", "description": "Something went wrong while running the Pipelines."} - SUCCESSFUL = {"github_state": "success", "description": "All Pipelines ran successfully."} - FAILURE = {"github_state": "failure", "description": "Pipeline failed."} - - -class INTERNAL_TOOL_PATHS(str, Enum): - CI_CREDENTIALS = "airbyte-ci/connectors/ci_credentials" - CONNECTOR_OPS = "airbyte-ci/connectors/connector_ops" - # CONNECTORS_QA has been removed - now using airbyte-internal-ops from PyPI - METADATA_SERVICE = "airbyte-ci/connectors/metadata_service/lib" - - -DAGGER_WRAP_ENV_VAR_NAME = "_DAGGER_WRAP_APPLIED" diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/hooks.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/hooks.py deleted file mode 100644 index c9057533fa50..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/hooks.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import importlib.util -from importlib import metadata -from importlib.abc import Loader - -from dagger import Container - -from pipelines.airbyte_ci.connectors.context import ConnectorContext - - -def get_dagger_sdk_version() -> str: - try: - return metadata.version("dagger-io") - except metadata.PackageNotFoundError: - return "n/a" - - -async def finalize_build(context: ConnectorContext, connector_container: Container) -> Container: - """Finalize build by adding dagger engine version label and running finalize_build.sh or finalize_build.py if present in the connector directory.""" - original_user = (await connector_container.with_exec(["whoami"]).stdout()).strip() - # Switch to root to finalize the build with superuser permissions - connector_container = connector_container.with_user("root") - connector_container = connector_container.with_label("io.dagger.engine_version", get_dagger_sdk_version()) - connector_dir_with_finalize_script = await context.get_connector_dir(include=["finalize_build.sh", "finalize_build.py"]) - finalize_scripts = await connector_dir_with_finalize_script.entries() - if not finalize_scripts: - return connector_container - - # We don't want finalize scripts to override the entrypoint so we keep it in memory to reset it after finalization - original_entrypoint = await connector_container.entrypoint() - if not original_entrypoint: - original_entrypoint = [] - - has_finalize_bash_script = "finalize_build.sh" in finalize_scripts - has_finalize_python_script = "finalize_build.py" in finalize_scripts - if has_finalize_python_script and has_finalize_bash_script: - raise Exception("Connector has both finalize_build.sh and finalize_build.py, please remove one of them") - - if has_finalize_python_script: - context.logger.info(f"{context.connector.technical_name} has a finalize_build.py script, running it to finalize build...") - module_path = context.connector.code_directory / "finalize_build.py" - connector_finalize_module_spec = importlib.util.spec_from_file_location( - f"{context.connector.code_directory.name}_finalize", module_path - ) - if connector_finalize_module_spec is None: - raise Exception("Connector has a finalize_build.py script but it can't be loaded.") - connector_finalize_module = importlib.util.module_from_spec(connector_finalize_module_spec) - if not isinstance(connector_finalize_module_spec.loader, Loader): - raise Exception("Connector has a finalize_build.py script but it can't be loaded.") - connector_finalize_module_spec.loader.exec_module(connector_finalize_module) - try: - connector_container = await connector_finalize_module.finalize_build(context, connector_container) - except AttributeError: - raise Exception("Connector has a finalize_build.py script but it doesn't have a finalize_build function.") - - if has_finalize_bash_script: - context.logger.info(f"{context.connector.technical_name} has finalize_build.sh script, running it to finalize build...") - connector_container = ( - connector_container.with_file("/tmp/finalize_build.sh", connector_dir_with_finalize_script.file("finalize_build.sh")) - .with_entrypoint(["sh"]) - .with_exec(["/tmp/finalize_build.sh"], use_entrypoint=True) - ) - # Switch back to the original user - connector_container = connector_container.with_exec(["chown", "-R", f"{original_user}:{original_user}", "/tmp"]) - connector_container = connector_container.with_user(original_user) - return connector_container.with_entrypoint(original_entrypoint) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/normalization.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/normalization.py deleted file mode 100644 index 406293ab9437..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/connector/normalization.py +++ /dev/null @@ -1,78 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import Any, Dict - -from dagger import Container, Platform - -from pipelines.airbyte_ci.connectors.context import ConnectorContext - -BASE_DESTINATION_NORMALIZATION_BUILD_CONFIGURATION = { - "destination-clickhouse": { - "dockerfile": "clickhouse.Dockerfile", - "dbt_adapter": "dbt-clickhouse>=1.4.0", - "integration_name": "clickhouse", - "normalization_image": "airbyte/normalization-clickhouse:0.4.3", - "supports_in_connector_normalization": False, - "yum_packages": [], - }, - "destination-mssql": { - "dockerfile": "mssql.Dockerfile", - "dbt_adapter": "dbt-sqlserver==1.0.0", - "integration_name": "mssql", - "normalization_image": "airbyte/normalization-mssql:0.4.3", - "supports_in_connector_normalization": True, - "yum_packages": [], - }, - "destination-mysql": { - "dockerfile": "mysql.Dockerfile", - "dbt_adapter": "dbt-mysql==1.0.0", - "integration_name": "mysql", - "normalization_image": "airbyte/normalization-mysql:0.4.3", - "supports_in_connector_normalization": False, - "yum_packages": [], - }, - "destination-oracle": { - "dockerfile": "oracle.Dockerfile", - "dbt_adapter": "dbt-oracle==0.4.3", - "integration_name": "oracle", - "normalization_image": "airbyte/normalization-oracle:0.4.3", - "supports_in_connector_normalization": False, - "yum_packages": [], - }, - "destination-postgres": { - "dockerfile": "Dockerfile", - "dbt_adapter": "dbt-postgres==1.0.0", - "integration_name": "postgres", - "normalization_image": "airbyte/normalization:0.4.3", - "supports_in_connector_normalization": True, - "yum_packages": [], - }, - "destination-redshift": { - "dockerfile": "redshift.Dockerfile", - "dbt_adapter": "dbt-redshift==1.0.0", - "integration_name": "redshift", - "normalization_image": "airbyte/normalization-redshift:0.4.3", - "supports_in_connector_normalization": True, - "yum_packages": [], - }, - "destination-tidb": { - "dockerfile": "tidb.Dockerfile", - "dbt_adapter": "dbt-tidb==1.0.1", - "integration_name": "tidb", - "normalization_image": "airbyte/normalization-tidb:0.4.3", - "supports_in_connector_normalization": True, - "yum_packages": [], - }, -} -DESTINATION_NORMALIZATION_BUILD_CONFIGURATION: Dict[str, Dict[str, Any]] = { - **BASE_DESTINATION_NORMALIZATION_BUILD_CONFIGURATION, - **{f"{k}-strict-encrypt": v for k, v in BASE_DESTINATION_NORMALIZATION_BUILD_CONFIGURATION.items()}, -} - - -def with_normalization(context: ConnectorContext, build_platform: Platform) -> Container: - normalization_image_name = DESTINATION_NORMALIZATION_BUILD_CONFIGURATION[context.connector.technical_name]["normalization_image"] - assert isinstance(normalization_image_name, str) - return context.dagger_client.container(platform=build_platform).from_(normalization_image_name) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/common.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/common.py deleted file mode 100644 index 48b00214c774..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/common.py +++ /dev/null @@ -1,350 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import re -from pathlib import Path -from typing import List, Optional, Sequence - -from click import UsageError -from dagger import Container, Directory - -from pipelines import hacks -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.consts import PATH_TO_LOCAL_CDK -from pipelines.dagger.containers.python import with_pip_cache, with_poetry_cache, with_python_base -from pipelines.helpers.utils import check_path_in_workdir, get_file_contents, raise_if_not_user - - -def with_python_package( - context: PipelineContext, - python_environment: Container, - package_source_code_path: str, - exclude: Optional[List] = None, - include: Optional[List] = None, - owner: str | None = None, -) -> Container: - """Load a python package source code to a python environment container. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the python sources will be pulled. - python_environment (Container): An existing python environment in which the package will be installed. - package_source_code_path (str): The local path to the package source code. - additional_dependency_groups (Optional[List]): extra_requires dependency of setup.py to install. Defaults to None. - exclude (Optional[List]): A list of file or directory to exclude from the python package source code. - include (Optional[List]): A list of file or directory to include from the python package source code. - owner (str, optional): The owner of the mounted directory. Defaults to None. - Returns: - Container: A python environment container with the python package source code. - """ - package_source_code_directory: Directory = context.get_repo_dir(package_source_code_path, exclude=exclude, include=include) - work_dir_path = f"/{package_source_code_path}" - container = python_environment.with_mounted_directory(work_dir_path, package_source_code_directory, owner=owner).with_workdir( - work_dir_path - ) - return container - - -async def find_local_dependencies_in_setup_py(python_package: Container) -> List[str]: - """Find local dependencies of a python package in its setup.py file. - - Args: - python_package (Container): A python package container. - - Returns: - List[str]: Paths to the local dependencies relative to the airbyte repo. - """ - setup_file_content = await get_file_contents(python_package, "setup.py") - if not setup_file_content: - return [] - - local_setup_dependency_paths = [] - with_egg_info = python_package.with_exec(["python", "setup.py", "egg_info"], use_entrypoint=True) - egg_info_output = await with_egg_info.stdout() - dependency_in_requires_txt = [] - for line in egg_info_output.split("\n"): - if line.startswith("writing requirements to"): - # Find the path to the requirements.txt file that was generated by calling egg_info - requires_txt_path = line.replace("writing requirements to", "").strip() - requirements_txt_content = await with_egg_info.file(requires_txt_path).contents() - dependency_in_requires_txt = requirements_txt_content.split("\n") - - for dependency_line in dependency_in_requires_txt: - if "file://" in dependency_line: - match = re.search(r"file:///(.+)", dependency_line) - if match: - local_setup_dependency_paths.append([match.group(1)][0]) - return local_setup_dependency_paths - - -async def find_local_dependencies_in_requirements_txt(python_package: Container, package_source_code_path: str) -> List[str]: - """Find local dependencies of a python package in a requirements.txt file. - - Args: - python_package (Container): A python environment container with the python package source code. - package_source_code_path (str): The local path to the python package source code. - - Returns: - List[str]: Paths to the local dependencies relative to the airbyte repo. - """ - requirements_txt_content = await get_file_contents(python_package, "requirements.txt") - if not requirements_txt_content: - return [] - - local_requirements_dependency_paths = [] - for line in requirements_txt_content.split("\n"): - # Some package declare themselves as a requirement in requirements.txt, - # #Without line != "-e ." the package will be considered a dependency of itself which can cause an infinite loop - if line.startswith("-e .") and line != "-e .": - local_dependency_path = str((Path(package_source_code_path) / Path(line[3:])).resolve().relative_to(Path.cwd())) - local_requirements_dependency_paths.append(local_dependency_path) - return local_requirements_dependency_paths - - -async def find_local_python_dependencies( - context: PipelineContext, - package_source_code_path: str, - search_dependencies_in_setup_py: bool = True, - search_dependencies_in_requirements_txt: bool = True, -) -> List[str]: - """Find local python dependencies of a python package. The dependencies are found in the setup.py and requirements.txt files. - - Args: - context (PipelineContext): The current pipeline context, providing a dagger client and a repository directory. - package_source_code_path (str): The local path to the python package source code. - search_dependencies_in_setup_py (bool, optional): Whether to search for local dependencies in the setup.py file. Defaults to True. - search_dependencies_in_requirements_txt (bool, optional): Whether to search for local dependencies in the requirements.txt file. Defaults to True. - Returns: - List[str]: Paths to the local dependencies relative to the airbyte repo. - """ - python_environment = with_python_base(context) - container = with_python_package(context, python_environment, package_source_code_path) - - local_dependency_paths = [] - if search_dependencies_in_setup_py: - local_dependency_paths += await find_local_dependencies_in_setup_py(container) - if search_dependencies_in_requirements_txt: - local_dependency_paths += await find_local_dependencies_in_requirements_txt(container, package_source_code_path) - - transitive_dependency_paths = [] - for local_dependency_path in local_dependency_paths: - # Transitive local dependencies installation is achieved by calling their setup.py file, not their requirements.txt file. - transitive_dependency_paths += await find_local_python_dependencies(context, local_dependency_path, True, False) - - all_dependency_paths = local_dependency_paths + transitive_dependency_paths - if all_dependency_paths: - context.logger.debug(f"Found local dependencies for {package_source_code_path}: {all_dependency_paths}") - return all_dependency_paths - - -def _install_python_dependencies_from_setup_py( - container: Container, - additional_dependency_groups: Optional[Sequence[str]] = None, -) -> Container: - install_connector_package_cmd = ["pip", "install", "."] - container = container.with_exec(install_connector_package_cmd) - - if additional_dependency_groups: - # e.g. .[dev,tests] - group_string = f".[{','.join(additional_dependency_groups)}]" - group_install_cmd = ["pip", "install", group_string] - - container = container.with_exec(group_install_cmd) - - return container - - -def _install_python_dependencies_from_requirements_txt(container: Container) -> Container: - install_requirements_cmd = ["pip", "install", "-r", "requirements.txt"] - return container.with_exec(install_requirements_cmd) - - -def _install_python_dependencies_from_poetry( - container: Container, - additional_dependency_groups: Optional[Sequence[str]] = None, - install_root_package: bool = True, -) -> Container: - pip_install_poetry_cmd = ["pip", "install", "poetry"] - poetry_disable_virtual_env_cmd = ["poetry", "config", "virtualenvs.create", "false"] - poetry_install_cmd = ["poetry", "install"] - poetry_check_cmd = ["poetry", "check"] - if not install_root_package: - poetry_install_cmd += ["--no-root"] - if additional_dependency_groups: - for group in additional_dependency_groups: - poetry_install_cmd += ["--with", group] - else: - poetry_install_cmd += ["--only", "main"] - return ( - container.with_exec(pip_install_poetry_cmd) - .with_exec(poetry_disable_virtual_env_cmd) - .with_exec(poetry_check_cmd) - .with_exec(poetry_install_cmd) - ) - - -async def with_installed_python_package( - context: PipelineContext, - python_environment: Container, - package_source_code_path: str, - user: str, - additional_dependency_groups: Optional[Sequence[str]] = None, - exclude: Optional[List] = None, - include: Optional[List] = None, - install_root_package: bool = True, -) -> Container: - """Install a python package in a python environment container. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the python sources will be pulled. - python_environment (Container): An existing python environment in which the package will be installed. - package_source_code_path (str): The local path to the package source code. - user (str): The user to use in the container. - additional_dependency_groups (Optional[Sequence[str]]): extra_requires dependency of setup.py to install. Defaults to None. - exclude (Optional[List]): A list of file or directory to exclude from the python package source code. - include (Optional[List]): A list of file or directory to include from the python package source code. - install_root_package (bool): Whether to install the root package. Defaults to True. - - Returns: - Container: A python environment container with the python package installed. - """ - - container = with_python_package(context, python_environment, package_source_code_path, exclude=exclude, include=include, owner=user) - local_dependencies = await find_local_python_dependencies(context, package_source_code_path) - - for dependency_directory in local_dependencies: - container = container.with_mounted_directory("/" + dependency_directory, context.get_repo_dir(dependency_directory), owner=user) - - has_setup_py = await check_path_in_workdir(container, "setup.py") - has_requirements_txt = await check_path_in_workdir(container, "requirements.txt") - has_pyproject_toml = await check_path_in_workdir(container, "pyproject.toml") - - container = container.with_user("root") - # All of these will require root access to install dependencies - # Dependencies are installed at the system level, if the user is not root it is not allowed to install system level dependencies - if has_pyproject_toml: - container = with_poetry_cache(container, context.dagger_client, owner=user) - container = _install_python_dependencies_from_poetry(container, additional_dependency_groups, install_root_package) - elif has_setup_py: - container = with_pip_cache(container, context.dagger_client) - container = _install_python_dependencies_from_setup_py(container, additional_dependency_groups) - elif has_requirements_txt: - container = with_pip_cache(container, context.dagger_client) - container = _install_python_dependencies_from_requirements_txt(container) - - container = container.with_user(user) - - return container - - -def apply_python_development_overrides(context: ConnectorContext, connector_container: Container, current_user: str) -> Container: - # Run the connector using the local cdk if flag is set - if context.use_local_cdk: - # Assume CDK is cloned in a sibling dir to `airbyte`: - path_to_cdk = str(Path(PATH_TO_LOCAL_CDK).resolve()) - if not Path(path_to_cdk).exists(): - raise UsageError( - f"Local CDK not found at '{path_to_cdk}'. Please clone the CDK repository in a sibling directory to the airbyte repository. Or use --use-cdk-ref to specify a CDK ref." - ) - context.logger.info(f"Using local CDK found at: '{path_to_cdk}'") - directory_to_mount = context.dagger_client.host().directory(path_to_cdk) - cdk_mount_dir = "/airbyte-cdk/python" - - context.logger.info(f"Mounting CDK from '{path_to_cdk}' to '{cdk_mount_dir}'") - # Install the airbyte-cdk package from the local directory - connector_container = ( - connector_container.with_env_variable( - "POETRY_DYNAMIC_VERSIONING_BYPASS", - "0.0.0-dev.0", # Replace dynamic versioning with dev version - ) - .with_directory( - cdk_mount_dir, - directory_to_mount, - owner=current_user, - ) - # We switch to root as otherwise we get permission errors when installing the package - # Permissions errors are caused by the fact that the airbyte user does not have a home directory - # Pip tries to write to /nonexistent which does not exist and on which the airbyte user does not have permissions - # We could create a proper home directory for the airbyte user, but that should be done at the base image level. - # Installing as root should not cause any issues as the container is ephemeral and the image is not pushed to a registry. - # Moreover this install is a system-wide install so the airbyte user will be able to use the package. - .with_user("root") - .with_exec( - ["pip", "install", "--no-cache-dir", "--force-reinstall", f"{cdk_mount_dir}"], - # TODO: Consider moving to Poetry-native installation: - # ["poetry", "add", cdk_mount_dir] - ) - # Switch back to the original user - .with_user(current_user) - ) - elif context.use_cdk_ref: - cdk_ref = context.use_cdk_ref - if " " in cdk_ref: - raise ValueError("CDK ref should not contain spaces") - - context.logger.info("Using CDK ref: '{cdk_ref}'") - # Install the airbyte-cdk package from provided ref - connector_container = ( - connector_container.with_user("root") - .with_exec( - [ - "apt-get", - "install", - "-y", - "git", - ] - ) - .with_exec( - [ - "pip", - "install", - f"git+https://github.com/airbytehq/airbyte-python-cdk.git#{cdk_ref}", - ], - ) - .with_user(current_user) - ) - return connector_container - - -async def with_python_connector_installed( - context: ConnectorContext, - python_container: Container, - connector_source_path: str, - user: str, - additional_dependency_groups: Optional[Sequence[str]] = None, - exclude: Optional[List[str]] = None, - include: Optional[List[str]] = None, - install_root_package: bool = True, -) -> Container: - """Install an airbyte python connectors dependencies.""" - - # Download the latest CDK version to update the pip cache. - # This is a hack to ensure we always get the latest CDK version installed in connectors not pinning the CDK version. - await hacks.cache_latest_cdk(context) - container = await with_installed_python_package( - context, - python_container, - connector_source_path, - user, - additional_dependency_groups=additional_dependency_groups, - exclude=exclude, - include=include, - install_root_package=install_root_package, - ) - - container = await apply_python_development_overrides(context, container, user) - await raise_if_not_user(container, user) - return container - - -def with_pip_packages(base_container: Container, packages_to_install: List[str]) -> Container: - """Installs packages using pip - Args: - context (Container): A container with python installed - - Returns: - Container: A container with the pip packages installed. - - """ - package_install_command = ["pip", "install"] - return base_container.with_exec(package_install_command + packages_to_install) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/pipx.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/pipx.py deleted file mode 100644 index aa8b7cd90654..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/pipx.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import List, Optional - -from dagger import Container - -from pipelines.airbyte_ci.connectors.context import PipelineContext -from pipelines.dagger.actions.python.common import with_pip_packages, with_python_package -from pipelines.dagger.actions.python.poetry import find_local_dependencies_in_pyproject_toml - - -def with_pipx(base_python_container: Container) -> Container: - """Installs pipx in a python container. - - Args: - base_python_container (Container): The container to install pipx on. - - Returns: - Container: A python environment with pipx installed. - """ - python_with_pipx = with_pip_packages(base_python_container, ["pipx"]).with_env_variable("PIPX_BIN_DIR", "/usr/local/bin") - - return python_with_pipx - - -async def with_installed_pipx_package( - context: PipelineContext, - python_environment: Container, - package_source_code_path: str, - exclude: Optional[List] = None, -) -> Container: - """Install a python package in a python environment container using pipx. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the python sources will be pulled. - python_environment (Container): An existing python environment in which the package will be installed. - package_source_code_path (str): The local path to the package source code. - exclude (Optional[List]): A list of file or directory to exclude from the python package source code. - - Returns: - Container: A python environment container with the python package installed. - """ - pipx_python_environment = with_pipx(python_environment) - container = with_python_package(context, pipx_python_environment, package_source_code_path, exclude=exclude) - - local_dependencies = await find_local_dependencies_in_pyproject_toml(context, container, package_source_code_path, exclude=exclude) - for dependency_directory in local_dependencies: - container = container.with_mounted_directory("/" + dependency_directory, context.get_repo_dir(dependency_directory)) - - container = container.with_exec(["pipx", "install", f"/{package_source_code_path}"], use_entrypoint=True) - - return container diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/poetry.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/poetry.py deleted file mode 100644 index cc87e2506734..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/python/poetry.py +++ /dev/null @@ -1,93 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import uuid -from pathlib import Path -from typing import List, Optional - -import toml -from dagger import Container, Directory - -from pipelines.airbyte_ci.connectors.context import PipelineContext -from pipelines.consts import AIRBYTE_SUBMODULE_DIR_NAME -from pipelines.dagger.actions.python.common import with_pip_packages, with_python_package -from pipelines.dagger.actions.system.common import with_debian_packages -from pipelines.dagger.containers.python import with_python_base -from pipelines.helpers.utils import get_file_contents - - -async def find_local_dependencies_in_pyproject_toml( - context: PipelineContext, - base_container: Container, - pyproject_file_path: str, - exclude: Optional[List] = None, -) -> list: - """Find local dependencies of a python package in a pyproject.toml file. - - Args: - python_package (Container): A python environment container with the python package source code. - pyproject_file_path (str): The path to the pyproject.toml file. - - Returns: - list: Paths to the local dependencies relative to the current directory. - """ - python_package = with_python_package(context, base_container, pyproject_file_path) - pyproject_content_raw = await get_file_contents(python_package, "pyproject.toml") - if not pyproject_content_raw: - return [] - - pyproject_content = toml.loads(pyproject_content_raw) - local_dependency_paths = [] - for value in pyproject_content["tool"]["poetry"]["dependencies"].values(): - if isinstance(value, dict) and "path" in value: - local_dependency_path = str((Path(pyproject_file_path) / Path(value["path"])).resolve().relative_to(Path.cwd())) - # Support the edge case where the airbyte repo is used as a git submodule. - local_dependency_path = local_dependency_path.removeprefix(f"{AIRBYTE_SUBMODULE_DIR_NAME}/") - local_dependency_paths.append(local_dependency_path) - - # Ensure we parse the child dependencies - # TODO handle more than pyproject.toml - child_local_dependencies = await find_local_dependencies_in_pyproject_toml( - context, base_container, local_dependency_path, exclude=exclude - ) - local_dependency_paths += child_local_dependencies - - return local_dependency_paths - - -def with_poetry(context: PipelineContext) -> Container: - """Install poetry in a python environment. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the ci_credentials sources will be pulled. - Returns: - Container: A python environment with poetry installed. - """ - python_base_environment: Container = with_python_base(context) - python_with_git = with_debian_packages(python_base_environment, ["git"]) - python_with_poetry = with_pip_packages(python_with_git, ["poetry"]) - - # poetry_cache: CacheVolume = context.dagger_client.cache_volume("poetry_cache") - # poetry_with_cache = python_with_poetry.with_mounted_cache("/root/.cache/pypoetry", poetry_cache, sharing=CacheSharingMode.SHARED) - - return python_with_poetry - - -def with_poetry_module(context: PipelineContext, parent_dir: Directory, module_path: str) -> Container: - """Sets up a Poetry module. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the ci_credentials sources will be pulled. - Returns: - Container: A python environment with dependencies installed using poetry. - """ - poetry_install_dependencies_cmd = ["poetry", "install"] - - python_with_poetry = with_poetry(context) - return ( - python_with_poetry.with_mounted_directory("/src", parent_dir) - .with_workdir(f"/src/{module_path}") - .with_exec(poetry_install_dependencies_cmd) - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/remote_storage.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/remote_storage.py deleted file mode 100644 index f8f8f28accf7..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/remote_storage.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups functions to interact with remote storage services like S3 or GCS.""" - -import uuid -from pathlib import Path -from typing import List, Optional, Tuple - -from dagger import Client, File - -from pipelines.helpers.utils import get_exec_result, secret_host_variable, with_exit_code -from pipelines.models.secrets import Secret - -GOOGLE_CLOUD_SDK_TAG = "425.0.0-slim" - - -async def upload_to_s3(dagger_client: Client, file_to_upload_path: Path, key: str, bucket: str) -> int: - """Upload a local file to S3 using the AWS CLI docker image and running aws s3 cp command. - - Args: - dagger_client (Client): The dagger client. - file_to_upload_path (Path): The local path to the file to upload. - key (str): The key that will be written on the S3 bucket. - bucket (str): The S3 bucket name. - - Returns: - int: Exit code of the upload process. - """ - s3_uri = f"s3://{bucket}/{key}" - file_to_upload: File = dagger_client.host().directory(".", include=[str(file_to_upload_path)]).file(str(file_to_upload_path)) - return await with_exit_code( - dagger_client.container() - .from_("amazon/aws-cli:latest") - .with_file(str(file_to_upload_path), file_to_upload) - .with_(secret_host_variable(dagger_client, "AWS_ACCESS_KEY_ID")) - .with_(secret_host_variable(dagger_client, "AWS_SECRET_ACCESS_KEY")) - .with_(secret_host_variable(dagger_client, "AWS_DEFAULT_REGION")) - .with_exec(["s3", "cp", str(file_to_upload_path), s3_uri], use_entrypoint=True) - ) - - -async def upload_to_gcs( - dagger_client: Client, - file_to_upload: File, - key: str, - bucket: str, - gcs_credentials: Secret, - flags: Optional[List] = None, - cache_upload: bool = False, -) -> Tuple[int, str, str]: - """Upload a local file to GCS using the AWS CLI docker image and running aws s3 cp command. - Args: - dagger_client (Client): The dagger client. - file_to_upload (File): The dagger File to upload. - key (str): The key that will be written on the S3 bucket. - bucket (str): The S3 bucket name. - gcs_credentials (Secret): The secret holding the credentials to get and upload the targeted GCS bucket. - flags (List[str]): Flags to be passed to the 'gcloud storage cp' command. - cache_upload (bool): If false, the gcloud commands will be executed on each call. - Returns: - Tuple[int, str, str]: Exit code, stdout, stderr - """ - flags = [] if flags is None else flags - gcs_uri = f"gs://{bucket}/{key}" - dagger_client = dagger_client - cp_command = ["gcloud", "storage", "cp"] + flags + ["to_upload", gcs_uri] - - gcloud_container = ( - dagger_client.container() - .from_(f"google/cloud-sdk:{GOOGLE_CLOUD_SDK_TAG}") - .with_workdir("/upload") - .with_mounted_secret("credentials.json", gcs_credentials.as_dagger_secret(dagger_client)) - .with_env_variable("GOOGLE_APPLICATION_CREDENTIALS", "/upload/credentials.json") - .with_file("to_upload", file_to_upload) - ) - if not cache_upload: - gcloud_container = gcloud_container.with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - else: - gcloud_container = gcloud_container.without_env_variable("CACHEBUSTER") - - gcloud_auth_container = gcloud_container.with_exec(["gcloud", "auth", "login", "--cred-file=credentials.json"], use_entrypoint=True) - if (await with_exit_code(gcloud_auth_container)) == 1: - gcloud_auth_container = gcloud_container.with_exec( - ["gcloud", "auth", "activate-service-account", "--key-file", "credentials.json"], use_entrypoint=True - ) - - gcloud_cp_container = gcloud_auth_container.with_exec(cp_command) - return await get_exec_result(gcloud_cp_container) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/secrets.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/secrets.py deleted file mode 100644 index 606adce68c8f..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/secrets.py +++ /dev/null @@ -1,83 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This modules groups functions made to download/upload secrets from/to a remote secret service and provide these secret in a dagger Directory.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -from pipelines.helpers.utils import get_secret_host_variable -from pipelines.models.secrets import Secret - -if TYPE_CHECKING: - from typing import Callable, List - - from dagger import Container - - from pipelines.airbyte_ci.connectors.context import ConnectorContext - - -# TODO deprecate to use Secret and SecretStores -# Not prioritized as few connectors have to export their secrets back to GSM -# This would require exposing a secret update interface on the SecretStore -# and a more complex structure / Logic to map container files to Secret objects -async def upload(context: ConnectorContext, gcp_gsm_env_variable_name: str = "GCP_GSM_CREDENTIALS") -> Container: - """Use the ci-credentials tool to upload the secrets stored in the context's updated_secrets-dir. - - Args: - context (ConnectorContext): The context providing a connector object and the update secrets dir. - gcp_gsm_env_variable_name (str, optional): The name of the environment variable holding credentials to connect to Google Secret Manager. Defaults to "GCP_GSM_CREDENTIALS". - - Returns: - container (Container): The executed ci-credentials update-secrets command. - - Raises: - ExecError: If the command returns a non-zero exit code. - """ - assert context.updated_secrets_dir is not None, "The context's updated_secrets_dir must be set to upload secrets." - # temp - fix circular import - from pipelines.dagger.containers.internal_tools import with_ci_credentials - - gsm_secret = get_secret_host_variable(context.dagger_client, gcp_gsm_env_variable_name) - secrets_path = f"/{context.connector.code_directory}/secrets" - - ci_credentials = await with_ci_credentials(context, gsm_secret) - - return await ci_credentials.with_directory(secrets_path, context.updated_secrets_dir).with_exec( - ["ci_credentials", context.connector.technical_name, "update-secrets"], use_entrypoint=True - ) - - -async def mounted_connector_secrets( - context: ConnectorContext, secret_directory_path: str, connector_secrets: List[Secret], owner: str | None = None -) -> Callable[[Container], Container]: - """Returns an argument for a dagger container's with_ method which mounts all connector secrets in it. - - Args: - context (ConnectorContext): The context providing a connector object and its secrets. - secret_directory_path (str): Container directory where the secrets will be mounted, as files. - connector_secrets (List[secrets]): List of secrets to mount to the connector container. - owner (str, optional): The owner of the mounted secrets. Defaults to None. - Returns: - fn (Callable[[Container], Container]): A function to pass as argument to the connector container's with_ method. - """ - java_log_scrub_pattern_secret = context.java_log_scrub_pattern_secret - - def with_secrets_mounted_as_dagger_secrets(container: Container) -> Container: - if java_log_scrub_pattern_secret: - # This LOG_SCRUB_PATTERN environment variable is used by our log4j test configuration - # to scrub secrets from the log messages. Although we already ensure that github scrubs them - # from its runner logs, this is required to prevent the secrets from leaking into gradle scans, - # test reports or any other build artifacts generated by a java connector test. - container = container.with_secret_variable("LOG_SCRUB_PATTERN", java_log_scrub_pattern_secret) - container = container.with_exec(["mkdir", "-p", secret_directory_path]) - for secret in connector_secrets: - if secret.file_name: - container = container.with_mounted_secret( - f"{secret_directory_path}/{secret.file_name}", secret.as_dagger_secret(context.dagger_client), owner=owner - ) - return container - - return with_secrets_mounted_as_dagger_secrets diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/common.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/common.py deleted file mode 100644 index 940d56a74260..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/common.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from typing import List - -from dagger import Container - - -def with_debian_packages(base_container: Container, packages_to_install: List[str]) -> Container: - """Installs packages using apt-get. - Args: - context (Container): A alpine based container. - - Returns: - Container: A container with the packages installed. - - """ - update_packages_command = ["apt-get", "update"] - package_install_command = ["apt-get", "install", "-y"] - return base_container.with_exec(update_packages_command).with_exec(package_install_command + packages_to_install) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/docker.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/docker.py deleted file mode 100644 index 2b61fe086274..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/actions/system/docker.py +++ /dev/null @@ -1,248 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json -import logging -import platform -import uuid -from typing import Any, Callable, Coroutine, Dict, List, Optional, Union - -from dagger import Client, Container, File, Service -from dagger import Secret as DaggerSecret - -from pipelines import consts -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.consts import ( - DOCKER_HOST_NAME, - DOCKER_HOST_PORT, - DOCKER_REGISTRY_ADDRESS, - DOCKER_REGISTRY_MIRROR_URL, - DOCKER_TMP_VOLUME_NAME, - DOCKER_VAR_LIB_VOLUME_NAME, - STORAGE_DRIVER, -) -from pipelines.helpers.utils import sh_dash_c -from pipelines.models.secrets import Secret - - -def get_base_dockerd_container(dagger_client: Client) -> Container: - """Provision a container to run a docker daemon. - It will be used as a docker host for docker-in-docker use cases. - - Args: - dagger_client (Client): The dagger client used to create the container. - Returns: - Container: The container to run dockerd as a service - """ - apk_packages_to_install = [ - STORAGE_DRIVER, - # Curl is only used for debugging purposes. - "curl", - ] - base_container = ( - dagger_client.container() - .from_(consts.DOCKER_DIND_IMAGE) - # We set this env var because we need to use a non-default zombie reaper setting. - # The reason for this is that by default it will want to set its parent process ID to 1 when reaping. - # This won't be possible because of container-ception: dind is running inside the dagger engine. - # See https://github.com/krallin/tini#subreaping for details. - .with_env_variable("TINI_SUBREAPER", "") - .with_exec( - sh_dash_c( - [ - "apk update", - f"apk add {' '.join(apk_packages_to_install)}", - "mkdir /etc/docker", - ] - ) - ) - # Expose the docker host port. - .with_exec(["adduser", "-u", "1000", "-S", "-H", "airbyte"]) - .with_exposed_port(DOCKER_HOST_PORT) - # We cache /tmp for file sharing between client and daemon. - .with_mounted_cache("/tmp", dagger_client.cache_volume(DOCKER_TMP_VOLUME_NAME), owner="airbyte") - .with_exec(["chmod", "777", "/tmp"]) - ) - - # We cache /var/lib/docker to avoid downloading images and layers multiple times. - base_container = base_container.with_mounted_cache( - "/var/lib/docker", dagger_client.cache_volume(DOCKER_VAR_LIB_VOLUME_NAME), owner="airbyte" - ) - return base_container - - -def get_daemon_config_json(registry_mirror_url: Optional[str] = None) -> str: - """Get the json representation of the docker daemon config. - - Args: - registry_mirror_url (Optional[str]): The registry mirror url to use. - - Returns: - str: The json representation of the docker daemon config. - """ - storage_driver = "vfs" if platform.system() == "Darwin" else STORAGE_DRIVER - logging.info(f"Using storage driver: {storage_driver}") - daemon_config: Dict[str, Union[List[str], str]] = { - "storage-driver": storage_driver, - } - if registry_mirror_url: - daemon_config["registry-mirrors"] = ["http://" + registry_mirror_url] - daemon_config["insecure-registries"] = [registry_mirror_url] - return json.dumps(daemon_config) - - -def docker_login( - dockerd_container: Container, - docker_registry_username: DaggerSecret, - docker_registry_password: DaggerSecret, -) -> Container: - """Login to a docker registry if the username and password secrets are provided. - - Args: - dockerd_container (Container): The dockerd_container container to login to the registry. - docker_registry_username_secret (Secret): The docker registry username secret. - docker_registry_password_secret (Secret): The docker registry password secret. - docker_registry_address (Optional[str]): The docker registry address to login to. Defaults to "docker.io" (DockerHub). - Returns: - Container: The container with the docker login command executed if the username and password secrets are provided. Noop otherwise. - """ - if docker_registry_username and docker_registry_username: - return ( - dockerd_container - # We use a cache buster here to guarantee the docker login is always executed. - .with_env_variable("CACHEBUSTER", str(uuid.uuid4())) - .with_secret_variable("DOCKER_REGISTRY_USERNAME", docker_registry_username) - .with_secret_variable("DOCKER_REGISTRY_PASSWORD", docker_registry_password) - .with_exec(sh_dash_c([f"docker login -u $DOCKER_REGISTRY_USERNAME -p $DOCKER_REGISTRY_PASSWORD {DOCKER_REGISTRY_ADDRESS}"])) - ) - else: - return dockerd_container - - -def with_global_dockerd_service( - dagger_client: Client, - docker_hub_username: Optional[Secret] = None, - docker_hub_password: Optional[Secret] = None, -) -> Service: - """Create a container with a docker daemon running. - We expose its 2375 port to use it as a docker host for docker-in-docker use cases. - It is optionally connected to a DockerHub mirror if the DOCKER_REGISTRY_MIRROR_URL env var is set. - Args: - dagger_client (Client): The dagger client used to create the container. - docker_hub_username (Optional[Secret]): The DockerHub username secret. - docker_hub_password (Optional[Secret]): The DockerHub password secret. - Returns: - Container: The container running dockerd as a service - """ - - dockerd_container = get_base_dockerd_container(dagger_client) - if DOCKER_REGISTRY_MIRROR_URL is not None: - # Ping the registry mirror host to make sure it's reachable through VPN - # We set a cache buster here to guarantee the curl command is always executed. - dockerd_container = dockerd_container.with_env_variable("CACHEBUSTER", str(uuid.uuid4())).with_exec( - ["curl", "-vvv", f"http://{DOCKER_REGISTRY_MIRROR_URL}/v2/"] - ) - daemon_config_json = get_daemon_config_json(DOCKER_REGISTRY_MIRROR_URL) - else: - daemon_config_json = get_daemon_config_json() - - dockerd_container = dockerd_container.with_new_file("/etc/docker/daemon.json", contents=daemon_config_json) - if docker_hub_username and docker_hub_password: - # Docker login happens late because there's a cache buster in the docker login command. - dockerd_container = docker_login( - dockerd_container, docker_hub_username.as_dagger_secret(dagger_client), docker_hub_password.as_dagger_secret(dagger_client) - ) - return dockerd_container.with_exec( - ["dockerd", "--log-level=error", f"--host=tcp://0.0.0.0:{DOCKER_HOST_PORT}", "--tls=false"], - insecure_root_capabilities=True, - use_entrypoint=True, - ).as_service() - - -async def with_bound_docker_host( - context: ConnectorContext, - container: Container, -) -> Container: - """Bind a container to a docker host. It will use the dockerd service as a docker host. - - Args: - context (ConnectorContext): The current connector context. - container (Container): The container to bind to the docker host. - Returns: - Container: The container bound to the docker host. - """ - assert context.dockerd_service is not None - current_user = (await container.with_exec(["whoami"]).stdout()).strip() - return ( - container.with_env_variable("DOCKER_HOST", f"tcp://{DOCKER_HOST_NAME}:{DOCKER_HOST_PORT}") - .with_service_binding(DOCKER_HOST_NAME, context.dockerd_service) - .with_mounted_cache("/tmp", context.dagger_client.cache_volume(DOCKER_TMP_VOLUME_NAME), owner=current_user) - ) - - -def bound_docker_host(context: ConnectorContext) -> Callable[[Container], Coroutine[Any, Any, Container]]: - async def bound_docker_host_inner(container: Container) -> Container: - return await with_bound_docker_host(context, container) - - return bound_docker_host_inner - - -async def with_docker_cli(context: ConnectorContext) -> Container: - """Create a container with the docker CLI installed and bound to a persistent docker host. - - Args: - context (ConnectorContext): The current connector context. - - Returns: - Container: A docker cli container bound to a docker host. - """ - docker_cli = context.dagger_client.container().from_(consts.DOCKER_CLI_IMAGE) - return await with_bound_docker_host(context, docker_cli) - - -async def load_image_to_docker_host(context: ConnectorContext, tar_file: File, image_tag: str) -> str: - """Load a docker image tar archive to the docker host. - - Args: - context (ConnectorContext): The current connector context. - tar_file (File): The file object holding the docker image tar archive. - image_tag (str): The tag to create on the image if it has no tag. - """ - # Hacky way to make sure the image is always loaded - tar_name = f"{str(uuid.uuid4())}.tar" - docker_cli = (await with_docker_cli(context)).with_mounted_file(tar_name, tar_file) - - image_load_output = await docker_cli.with_exec(["docker", "load", "--input", tar_name], use_entrypoint=True).stdout() - # Not tagged images only have a sha256 id the load output shares. - if "sha256:" in image_load_output: - image_id = image_load_output.replace("\n", "").replace("Loaded image ID: sha256:", "") - await docker_cli.with_exec(["docker", "tag", image_id, image_tag], use_entrypoint=True) - image_sha = json.loads(await docker_cli.with_exec(["docker", "inspect", image_tag], use_entrypoint=True).stdout())[0].get("Id") - return image_sha - - -def with_crane( - context: ConnectorContext, -) -> Container: - """Crane is a tool to analyze and manipulate container images. - We can use it to extract the image manifest and the list of layers or list the existing tags on an image repository. - https://github.com/google/go-containerregistry/tree/main/cmd/crane - """ - - # We use the debug image as it contains a shell which we need to properly use environment variables - # https://github.com/google/go-containerregistry/tree/main/cmd/crane#images - base_container = context.dagger_client.container().from_( - "gcr.io/go-containerregistry/crane/debug:c195f151efe3369874c72662cd69ad43ee485128@sha256:94f61956845714bea3b788445454ae4827f49a90dcd9dac28255c4cccb6220ad" - ) - - if context.docker_hub_username and context.docker_hub_password: - base_container = ( - base_container.with_secret_variable("DOCKER_HUB_USERNAME", context.docker_hub_username.as_dagger_secret(context.dagger_client)) - .with_secret_variable("DOCKER_HUB_PASSWORD", context.docker_hub_password.as_dagger_secret(context.dagger_client)) - # We use sh -c to be able to use environment variables in the command - # This is a workaround as the default crane entrypoint doesn't support environment variables - .with_exec(sh_dash_c(["crane auth login index.docker.io -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD"])) - ) - - return base_container diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/git.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/git.py deleted file mode 100644 index dda43fdb3116..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/git.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -import os -from typing import Optional - -from dagger import Client, Container - -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL -from pipelines.helpers.utils import sh_dash_c - - -def get_authenticated_repo_url(url: str, github_token: str) -> str: - return url.replace("https://github.com", f"https://{github_token}@github.com") - - -async def checked_out_git_container( - dagger_client: Client, - current_git_branch: str, - current_git_revision: str, - diffed_branch: Optional[str] = None, - repo_url: str = AIRBYTE_GITHUB_REPO_URL, -) -> Container: - """ - Create a container with git in it. - We add the airbyte repo as the origin remote and the target repo as the target remote. - We fetch the diffed branch from the origin remote and the current branch from the target remote. - We then checkout the current branch. - """ - origin_repo_url = AIRBYTE_GITHUB_REPO_URL - target_repo_url = repo_url - current_git_branch = current_git_branch.removeprefix("origin/") - diffed_branch = current_git_branch if diffed_branch is None else diffed_branch.removeprefix("origin/") - if github_token := os.environ.get("CI_GITHUB_ACCESS_TOKEN"): - origin_repo_url = get_authenticated_repo_url(origin_repo_url, github_token) - target_repo_url = get_authenticated_repo_url(target_repo_url, github_token) - origin_repo_url_secret = dagger_client.set_secret("ORIGIN_REPO_URL", origin_repo_url) - target_repo_url_secret = dagger_client.set_secret("TARGET_REPO_URL", target_repo_url) - - git_container = ( - dagger_client.container() - .from_("alpine/git:latest") - .with_workdir("/repo") - .with_exec(["init"], use_entrypoint=True) - .with_env_variable("CACHEBUSTER", current_git_revision) - .with_secret_variable("ORIGIN_REPO_URL", origin_repo_url_secret) - .with_secret_variable("TARGET_REPO_URL", target_repo_url_secret) - .with_exec(sh_dash_c(["git remote add origin ${ORIGIN_REPO_URL}"])) - .with_exec(sh_dash_c(["git remote add target ${TARGET_REPO_URL}"])) - .with_exec(["fetch", "origin", diffed_branch], use_entrypoint=True) - ) - if diffed_branch != current_git_branch: - git_container = git_container.with_exec(["fetch", "target", current_git_branch], use_entrypoint=True) - return await git_container.with_exec(["checkout", current_git_branch], use_entrypoint=True) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/internal_tools.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/internal_tools.py deleted file mode 100644 index 8438fcec56e1..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/internal_tools.py +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from dagger import Container, Secret - -from pipelines.airbyte_ci.connectors.context import PipelineContext -from pipelines.consts import INTERNAL_TOOL_PATHS -from pipelines.dagger.actions.python.pipx import with_installed_pipx_package -from pipelines.dagger.containers.python import with_python_base - - -async def with_ci_credentials(context: PipelineContext, gsm_secret: Secret) -> Container: - """Install the ci_credentials package in a python environment. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the ci_credentials sources will be pulled. - gsm_secret (Secret): The secret holding GCP_GSM_CREDENTIALS env variable value. - - Returns: - Container: A python environment with the ci_credentials package installed. - """ - python_base_environment: Container = with_python_base(context) - ci_credentials = await with_installed_pipx_package(context, python_base_environment, INTERNAL_TOOL_PATHS.CI_CREDENTIALS.value) - ci_credentials = ci_credentials.with_env_variable("VERSION", "dagger_ci") - return ci_credentials.with_secret_variable("GCP_GSM_CREDENTIALS", gsm_secret).with_workdir("/") - - -async def with_connector_ops(context: PipelineContext) -> Container: - """Installs the connector_ops package in a Container running Python > 3.10 with git.. - - Args: - context (PipelineContext): The current test context, providing the repository directory from which the ci_connector_sources sources will be pulled. - - Returns: - Container: A python environment container with connector_ops installed. - """ - python_base_environment: Container = with_python_base(context) - - return await with_installed_pipx_package(context, python_base_environment, INTERNAL_TOOL_PATHS.CONNECTOR_OPS.value) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/java.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/java.py deleted file mode 100644 index 61ee8c4337d7..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/java.py +++ /dev/null @@ -1,193 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import datetime - -from dagger import CacheVolume, Container, File, Platform - -from pipelines.airbyte_ci.connectors.context import ConnectorContext, PipelineContext -from pipelines.consts import AMAZONCORRETTO_IMAGE -from pipelines.dagger.actions.connector.hooks import finalize_build -from pipelines.dagger.actions.connector.normalization import DESTINATION_NORMALIZATION_BUILD_CONFIGURATION, with_normalization -from pipelines.helpers.utils import deprecated, sh_dash_c - - -@deprecated("This function is deprecated. Please declare an explicit base image to use in the java connector metadata.") -def with_integration_base(context: PipelineContext, build_platform: Platform) -> Container: - return ( - context.dagger_client.container(platform=build_platform) - .from_("amazonlinux:2022.0.20220831.1") - .with_workdir("/airbyte") - .with_file("base.sh", context.get_repo_dir("airbyte-integrations/bases/base", include=["base.sh"]).file("base.sh")) - .with_env_variable("AIRBYTE_ENTRYPOINT", "/airbyte/base.sh") - .with_label("io.airbyte.version", "0.1.0") - .with_label("io.airbyte.name", "airbyte/integration-base") - ) - - -@deprecated("This function is deprecated. Please declare an explicit base image to use in the java connector metadata.") -def with_integration_base_java(context: PipelineContext, build_platform: Platform) -> Container: - integration_base = with_integration_base(context, build_platform) - yum_packages_to_install = [ - "tar", # required to untar java connector binary distributions. - "openssl", # required because we need to ssh and scp sometimes. - "findutils", # required for xargs, which is shipped as part of findutils. - ] - return ( - context.dagger_client.container(platform=build_platform) - # Use a linux+jdk base image with long-term support, such as amazoncorretto. - .from_(AMAZONCORRETTO_IMAGE) - # Bust the cache on a daily basis to get fresh packages. - .with_env_variable("DAILY_CACHEBUSTER", datetime.datetime.now().strftime("%Y-%m-%d")) - # Install a bunch of packages as early as possible. - .with_exec( - sh_dash_c( - [ - # Update first, but in the same .with_exec step as the package installation. - # Otherwise, we risk caching stale package URLs. - "yum update -y --security", - # - f"yum install -y {' '.join(yum_packages_to_install)}", - # Remove any dangly bits. - "yum clean all", - ] - ) - ) - # Add what files we need to the /airbyte directory. - # Copy base.sh from the airbyte/integration-base image. - .with_directory("/airbyte", integration_base.directory("/airbyte")) - .with_workdir("/airbyte") - # Download a utility jar from the internet. - .with_file("dd-java-agent.jar", context.dagger_client.http("https://dtdg.co/latest-java-tracer")) - # Copy javabase.sh from the git repo. - .with_file("javabase.sh", context.get_repo_dir("airbyte-integrations/bases/base-java", include=["javabase.sh"]).file("javabase.sh")) - # Set a bunch of env variables used by base.sh. - .with_env_variable("AIRBYTE_SPEC_CMD", "/airbyte/javabase.sh --spec") - .with_env_variable("AIRBYTE_CHECK_CMD", "/airbyte/javabase.sh --check") - .with_env_variable("AIRBYTE_DISCOVER_CMD", "/airbyte/javabase.sh --discover") - .with_env_variable("AIRBYTE_READ_CMD", "/airbyte/javabase.sh --read") - .with_env_variable("AIRBYTE_WRITE_CMD", "/airbyte/javabase.sh --write") - .with_env_variable("AIRBYTE_ENTRYPOINT", "/airbyte/base.sh") - # Set image labels. - .with_label("io.airbyte.version", "0.1.2") - .with_label("io.airbyte.name", "airbyte/integration-base-java") - ) - - -@deprecated("This function is deprecated. Please declare an explicit base image to use in the java connector metadata.") -def with_integration_base_java_and_normalization(context: ConnectorContext, build_platform: Platform) -> Container: - yum_packages_to_install = [ - "python3", - "python3-devel", - "jq", - "sshpass", - "git", - ] - - additional_yum_packages = DESTINATION_NORMALIZATION_BUILD_CONFIGURATION[context.connector.technical_name]["yum_packages"] - yum_packages_to_install += additional_yum_packages - - dbt_adapter_package = DESTINATION_NORMALIZATION_BUILD_CONFIGURATION[context.connector.technical_name]["dbt_adapter"] - assert isinstance(dbt_adapter_package, str) - normalization_integration_name = DESTINATION_NORMALIZATION_BUILD_CONFIGURATION[context.connector.technical_name]["integration_name"] - assert isinstance(normalization_integration_name, str) - - pip_cache: CacheVolume = context.dagger_client.cache_volume("pip_cache") - - return ( - with_integration_base_java(context, build_platform) - # Bust the cache on a daily basis to get fresh packages. - .with_env_variable("DAILY_CACHEBUSTER", datetime.datetime.now().strftime("%Y-%m-%d")) - .with_exec( - sh_dash_c( - [ - "yum update -y --security", - f"yum install -y {' '.join(yum_packages_to_install)}", - "yum clean all", - "alternatives --install /usr/bin/python python /usr/bin/python3 60", - ] - ) - ) - .with_mounted_cache("/root/.cache/pip", pip_cache) - .with_exec( - sh_dash_c( - [ - "python -m ensurepip --upgrade", - # Workaround for https://github.com/yaml/pyyaml/issues/601 - "pip3 install 'Cython<3.0' 'pyyaml~=5.4' --no-build-isolation", - # Required for dbt https://github.com/dbt-labs/dbt-core/issues/7075 - "pip3 install 'pytz~=2023.3'", - f"pip3 install {dbt_adapter_package}", - # amazon linux 2 isn't compatible with urllib3 2.x, so force 1.x - "pip3 install 'urllib3<2'", - ] - ) - ) - .with_directory("airbyte_normalization", with_normalization(context, build_platform).directory("/airbyte")) - .with_workdir("airbyte_normalization") - .with_exec(sh_dash_c(["mv * .."])) - .with_workdir("/airbyte") - .with_exec(["rm", "-rf", "airbyte_normalization"], use_entrypoint=True) - .with_workdir("/airbyte/normalization_code") - .with_exec(["pip3", "install", "."], use_entrypoint=True) - .with_workdir("/airbyte/normalization_code/dbt-template/") - .with_exec(["dbt", "deps"], use_entrypoint=True) - .with_workdir("/airbyte") - .with_file( - "run_with_normalization.sh", - context.get_repo_dir("airbyte-integrations/bases/base-java", include=["run_with_normalization.sh"]).file( - "run_with_normalization.sh" - ), - ) - .with_env_variable("AIRBYTE_NORMALIZATION_INTEGRATION", normalization_integration_name) - .with_env_variable("AIRBYTE_ENTRYPOINT", "/airbyte/run_with_normalization.sh") - ) - - -async def with_airbyte_java_connector(context: ConnectorContext, connector_java_tar_file: File, build_platform: Platform) -> Container: - application = context.connector.technical_name - - build_stage = ( - with_integration_base_java(context, build_platform) - .with_workdir("/airbyte") - .with_env_variable("APPLICATION", context.connector.technical_name) - .with_file(f"{application}.tar", connector_java_tar_file) - .with_exec( - sh_dash_c( - [ - f"tar xf {application}.tar --strip-components=1", - f"rm -rf {application}.tar", - ] - ) - ) - ) - # TODO: remove the condition below once all connectors have a base image declared in their metadata. - if "connectorBuildOptions" in context.connector.metadata and "baseImage" in context.connector.metadata["connectorBuildOptions"]: - base_image_address = context.connector.metadata["connectorBuildOptions"]["baseImage"] - context.logger.info(f"Using base image {base_image_address} from connector metadata to build connector.") - base = context.dagger_client.container(platform=build_platform).from_(base_image_address) - elif ( - context.connector.supports_normalization - and DESTINATION_NORMALIZATION_BUILD_CONFIGURATION[context.connector.technical_name]["supports_in_connector_normalization"] - ): - context.logger.warn( - f"Connector {context.connector.technical_name} has in-connector normalization enabled. This is supposed to be deprecated. " - f"Please declare a base image address in the connector metadata.yaml file (connectorBuildOptions.baseImage)." - ) - base = with_integration_base_java_and_normalization(context, build_platform).with_entrypoint(["/airbyte/run_with_normalization.sh"]) - else: - context.logger.warn( - f"Connector {context.connector.technical_name} does not declare a base image in its connector metadata. " - f"Please declare a base image address in the connector metadata.yaml file (connectorBuildOptions.baseImage)." - ) - base = with_integration_base_java(context, build_platform).with_entrypoint(["/airbyte/base.sh"]) - - current_user = (await base.with_exec(["whoami"]).stdout()).strip() - connector_container = ( - base.with_workdir("/airbyte") - .with_env_variable("APPLICATION", application) - .with_mounted_directory("built_artifacts", build_stage.directory("/airbyte"), owner=current_user) - .with_exec(sh_dash_c(["mv built_artifacts/* ."])) - ) - return await finalize_build(context, connector_container) diff --git a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/python.py b/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/python.py deleted file mode 100644 index 7bd6f1e024bc..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/dagger/containers/python.py +++ /dev/null @@ -1,92 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -from dagger import CacheSharingMode, CacheVolume, Client, Container - -from pipelines.airbyte_ci.connectors.context import PipelineContext -from pipelines.consts import ( - CONNECTOR_TESTING_REQUIREMENTS, - PIP_CACHE_PATH, - PIP_CACHE_VOLUME_NAME, - POETRY_CACHE_PATH, - POETRY_CACHE_VOLUME_NAME, - PYPROJECT_TOML_FILE_PATH, -) -from pipelines.helpers.utils import sh_dash_c - - -def with_python_base(context: PipelineContext, python_version: str = "3.11") -> Container: - """Build a Python container with a cache volume for pip cache. - - Args: - context (PipelineContext): The current test context, providing a dagger client and a repository directory. - python_image_name (str, optional): The python image to use to build the python base environment. Defaults to "python:3.9-slim". - - Raises: - ValueError: Raised if the python_image_name is not a python image. - - Returns: - Container: The python base environment container. - """ - - pip_cache: CacheVolume = context.dagger_client.cache_volume("pip_cache") - - base_container = ( - context.dagger_client.container() - .from_(f"python:{python_version}-slim") - .with_mounted_cache("/root/.cache/pip", pip_cache) - .with_exec( - sh_dash_c( - [ - "apt-get update", - "apt-get install -y build-essential cmake g++ libffi-dev libstdc++6 git", - "pip install pip==23.1.2", - ] - ) - ) - ) - - return base_container - - -def with_testing_dependencies(context: PipelineContext) -> Container: - """Build a testing environment by installing testing dependencies on top of a python base environment. - - Args: - context (PipelineContext): The current test context, providing a dagger client and a repository directory. - - Returns: - Container: The testing environment container. - """ - python_environment: Container = with_python_base(context) - pyproject_toml_file = context.get_repo_dir(".", include=[PYPROJECT_TOML_FILE_PATH]).file(PYPROJECT_TOML_FILE_PATH) - - return python_environment.with_exec(["pip", "install"] + CONNECTOR_TESTING_REQUIREMENTS).with_file( - f"/{PYPROJECT_TOML_FILE_PATH}", pyproject_toml_file - ) - - -def with_pip_cache(container: Container, dagger_client: Client, owner: str | None = None) -> Container: - """Mounts the pip cache in the container. - Args: - container (Container): A container with python installed - owner (str, optional): The owner of the cache. Defaults to None. - Returns: - Container: A container with the pip cache mounted. - """ - pip_cache_volume = dagger_client.cache_volume(PIP_CACHE_VOLUME_NAME) - return container.with_mounted_cache(PIP_CACHE_PATH, pip_cache_volume, sharing=CacheSharingMode.SHARED, owner=owner) - - -def with_poetry_cache(container: Container, dagger_client: Client, owner: str | None = None) -> Container: - """Mounts the poetry cache in the container. - Args: - container (Container): A container with python installed - owner (str, optional): The owner of the cache. Defaults to None. - Returns: - Container: A container with the poetry cache mounted. - """ - poetry_cache_volume = dagger_client.cache_volume(POETRY_CACHE_VOLUME_NAME) - return container.with_mounted_cache(POETRY_CACHE_PATH, poetry_cache_volume, sharing=CacheSharingMode.SHARED, owner=owner) diff --git a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/external_scripts/__init__.py deleted file mode 100644 index f70ecfc3a89e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. diff --git a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_check.sh b/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_check.sh deleted file mode 100755 index b46d5a6c3313..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_check.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -o errexit -o nounset -o pipefail - -echo "Checking if airbyte-ci is correctly installed..." - -INSTALL_DIR="$HOME/.local/bin" -HUMAN_READABLE_INSTALL_DIR="\$HOME/.local/bin" - -# Check that the target directory is on the PATH -# If not print an error message and exit -if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then - echo "The target directory $INSTALL_DIR is not on the PATH" - echo "Check that $HUMAN_READABLE_INSTALL_DIR is part of the PATH" - echo "" - echo "If not, please add 'export PATH=\"$HUMAN_READABLE_INSTALL_DIR:\$PATH\"' to your shell profile" - exit 1 -fi - -# Check that airbyte-ci is on the PATH -# If not print an error message and exit -if ! which airbyte-ci >/dev/null 2>&1; then - echo "airbyte-ci is not installed" - echo "" - echo "Please run 'make tools.airbyte-ci.install' to install airbyte-ci" - exit 1 -fi - -EXPECTED_PATH="$INSTALL_DIR/airbyte-ci" -AIRBYTE_CI_PATH=$(which airbyte-ci 2>/dev/null) -if [ "$AIRBYTE_CI_PATH" != "$EXPECTED_PATH" ]; then - echo "airbyte-ci is not from the expected install location: $EXPECTED_PATH" - echo "airbyte-ci is installed at: $AIRBYTE_CI_PATH" - echo "Check that airbyte-ci exists at $HUMAN_READABLE_INSTALL_DIR and $HUMAN_READABLE_INSTALL_DIR is part of the PATH" - echo "" - echo "If it is, try running 'make tools.airbyte-ci.clean', then run 'make tools.airbyte-ci.install' again" - exit 1 -fi - -# Check if the AIRBYTE_CI_PATH is a symlink -if [ -L "$AIRBYTE_CI_PATH" ]; then - echo "" - echo "#########################################################################" - echo "# #" - echo "# Warning: airbyte-ci at $AIRBYTE_CI_PATH is a symlink. #" - echo "# You are possibly using a development version of airbyte-ci. #" - echo "# To update to a release version, run 'make tools.airbyte-ci.install' #" - echo "# #" - echo "# If this warning persists, try running 'make tools.airbyte-ci.clean' #" - echo "# Then run 'make tools.airbyte-ci.install' again. #" - echo "# #" - echo "#########################################################################" - echo "" -fi - -echo "airbyte-ci is correctly installed at $EXPECTED_PATH" diff --git a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_clean.sh b/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_clean.sh deleted file mode 100755 index 56d2c1fbf371..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_clean.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -set -o errexit -o nounset -o pipefail - -# Check if pipx is on the path and if so, uninstall pipelines -if which pipx >/dev/null 2>&1; then - # ignore errors if pipelines is not installed - pipx uninstall pipelines || true - echo "Uninstalled pipelines via pipx" -else - echo "pipx not found, skipping uninstall of pipelines" -fi - -# Remove airbyte-ci if it's on the path -while which airbyte-ci >/dev/null 2>&1; do - echo "Removing $(which airbyte-ci)" - rm "$(which airbyte-ci)" -done -echo "Removed airbyte-ci" - -# Remove airbyte-ci-internal if it's on the path -while which airbyte-ci-internal >/dev/null 2>&1; do - echo "Removing $(which airbyte-ci)" - rm "$(which airbyte-ci-internal)" -done -echo "Removed airbyte-ci-internal" - -# Remove airbyte-ci-dev if it's on the path -while which airbyte-ci-dev >/dev/null 2>&1; do - echo "Removing $(which airbyte-ci)" - rm "$(which airbyte-ci-dev)" -done - echo "Removed airbyte-ci-dev" - -# Check if airbyte-ci is stashed away in pyenv -# If so, remove it -# This prevents `pyenv init -` from adding it back to the path -while pyenv whence --path airbyte-ci >/dev/null 2>&1; do - rm "$(pyenv whence --path airbyte-ci)" - echo "Uninstalled pipelines via pyenv" -done - echo "All airbyte-ci references removed from pyenv versions." - -echo "Cleanup of airbyte-ci install completed." diff --git a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_dev_install.py b/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_dev_install.py deleted file mode 100755 index 32a7aa43a9fc..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_dev_install.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -# !IMPORTANT! This script is used to install the airbyte-ci tool on a Linux or macOS system. -# Meaning, no external dependencies are allowed as we don't want users to have to run anything -# other than this script to install the tool. - -import subprocess -import sys - - -def check_command_exists(command: str, not_found_message: str) -> None: - """ - Check if a command exists in the system path. - """ - try: - subprocess.check_call(["which", command], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except subprocess.CalledProcessError: - print(not_found_message) - sys.exit(1) - - -def main() -> None: - # Check if Python 3.10 is on the path - check_command_exists( - "python3.11", - """python3.10 not found on the path. -Please install Python 3.11 using pyenv: -1. Install pyenv if not already installed: - brew install pyenv -2. Install Python 3.10 using pyenv: - pyenv install 3.10.12""", - ) - print("Python 3.10 is already installed.") - - # Check if pipx is installed - check_command_exists( - "uv", - """uv not found. Please install uv: `brew install uv` - -After installation, restart your terminal or source your shell -configuration file to ensure the uv command is available.""", - ) - print("uv is already installed.") - - # Install airbyte-ci development version - subprocess.run(["uv", "tool", "install", "--editable", "--force", "--python=python3.11", "airbyte-ci/connectors/pipelines/"]) - print("Development version of airbyte-ci installed successfully.") - - -if __name__ == "__main__": - main() diff --git a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_install.py b/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_install.py deleted file mode 100755 index 01ed0ce20f90..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/external_scripts/airbyte_ci_install.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -# !IMPORTANT! This script is used to install the airbyte-ci tool on a Linux or macOS system. -# Meaning, no external dependencies are allowed as we don't want users to have to run anything -# other than this script to install the tool. - -from __future__ import annotations - -import os -import shutil -import ssl -import sys -import urllib.request -from typing import TYPE_CHECKING - -# !IMPORTANT! This constant is inline here instead of being imported from pipelines/consts.py -# because we don't want to introduce any dependencies on other files in the repository. -RELEASE_URL = os.getenv("RELEASE_URL", "https://connectors.airbyte.com/files/airbyte-ci/releases") - -if TYPE_CHECKING: - from typing import Optional - - -def _get_custom_certificate_path() -> Optional[str]: - """ - Returns the path to the custom certificate file if certifi is installed, otherwise None. - - HACK: This is a workaround for the fact that the pyinstaller binary does not know how or where to - find the ssl certificates file. This happens because the binary is built on a different system - than the one it is being run on. This function will return the path to the certifi certificate file - if it is installed, otherwise it will return None. This function is used in get_ssl_context() below. - - WHY: this works when certifi is not found: - If you run this file directly, it will use the system python interpreter and will be able to find - the ssl certificates file. e.g. when running in dev mode or via the makefile. - - WHY: this works when certifi is found: - When this file is run by the pyinstaller binary, it is through the pipelines project, which has - certifi installed. This means that when this file is run by the pyinstaller binary, it will be able - to find the ssl certificates file in the certifi package. - - """ - # if certifi is not installed, do nothing - try: - import certifi - - return certifi.where() - except ImportError: - return None - - -def get_ssl_context() -> ssl.SSLContext: - """ - Returns an ssl.SSLContext object with the custom certificate file if certifi is installed, otherwise - returns the default ssl.SSLContext object. - """ - certifi_path = _get_custom_certificate_path() - if certifi_path is None: - return ssl.create_default_context() - - return ssl.create_default_context(cafile=certifi_path) - - -def get_airbyte_os_name() -> Optional[str]: - """ - Returns 'ubuntu' if the system is Linux or 'macos' if the system is macOS. - """ - OS = os.uname().sysname - if OS == "Linux": - print("Linux based system detected.") - return "ubuntu" - elif OS == "Darwin": - print("macOS based system detected.") - return "macos" - else: - return None - - -def main(version: str = "latest") -> None: - # Determine the operating system - os_name = get_airbyte_os_name() - if os_name is None: - print("Unsupported operating system") - return - - url = f"{RELEASE_URL}/{os_name}/{version}/airbyte-ci" - - # Create the directory if it does not exist - destination_dir = os.path.expanduser("~/.local/bin") - os.makedirs(destination_dir, exist_ok=True) - - # Set the path of the versioned binary - versioned_path = os.path.join(destination_dir, f"airbyte-ci-{version}") - - # If the version is not explicit, delete any existing versioned binary - if version == "latest" and os.path.exists(versioned_path): - os.remove(versioned_path) - - # Download the versioned binary if it doesn't exist - if not os.path.exists(versioned_path): - # Download the file using urllib.request - print(f"Downloading from {url}") - ssl_context = get_ssl_context() - with urllib.request.urlopen(url, context=ssl_context) as response, open(versioned_path, "wb") as out_file: - shutil.copyfileobj(response, out_file) - - # Make the versioned binary executable - os.chmod(versioned_path, 0o755) - - # Ensure that the destination path does not exist. - destination_path = os.path.join(destination_dir, "airbyte-ci") - if os.path.exists(destination_path): - os.remove(destination_path) - - # Symlink the versioned binary to the destination path - os.symlink(versioned_path, destination_path) - - # ASCII Art and Completion Message - install_complete_message = f""" - ╔───────────────────────────────────────────────────────────────────────────────╗ - │ │ - │ AAA IIIII RRRRRR BBBBB YY YY TTTTTTT EEEEEEE CCCCC IIIII │ - │ AAAAA III RR RR BB B YY YY TTT EE CC III │ - │ AA AA III RRRRRR BBBBBB YYYYY TTT EEEEE _____ CC III │ - │ AAAAAAA III RR RR BB BB YYY TTT EE CC III │ - │ AA AA IIIII RR RR BBBBBB YYY TTT EEEEEEE CCCCC IIIII │ - │ │ - │ === Installation complete. v({version})=== │ - │ {destination_path} │ - ╚───────────────────────────────────────────────────────────────────────────────╝ - """ - - print(install_complete_message) - - -if __name__ == "__main__": - version_arg = sys.argv[1] if len(sys.argv) > 1 else "latest" - main(version_arg) diff --git a/airbyte-ci/connectors/pipelines/pipelines/hacks.py b/airbyte-ci/connectors/pipelines/pipelines/hacks.py deleted file mode 100644 index 28b7b298c4ae..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/hacks.py +++ /dev/null @@ -1,145 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module contains hacks used in connectors pipelines. They're gathered here for tech debt visibility.""" - -from __future__ import annotations - -from logging import Logger -from typing import TYPE_CHECKING, Callable, List - -import asyncclick as click -from connector_ops.utils import ConnectorLanguage # type: ignore - -from pipelines import consts -from pipelines.airbyte_ci.steps.base_image import UpdateBaseImageMetadata -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL, is_automerge_pull_request, update_commit_status_check - -if TYPE_CHECKING: - from dagger import Container - - from pipelines.airbyte_ci.connectors.context import ConnectorContext - from pipelines.models.steps import StepResult - - -async def cache_latest_cdk(context: ConnectorContext) -> None: - """ - Download the latest CDK version to update the pip cache. - - Underlying issue: - Most Python connectors, or normalization, are not pinning the CDK version they use. - It means that the will get whatever version is in the pip cache. - But the original goal of not pinning the CDK version is to always get the latest version. - - Hack: - Call this function before building connector test environment to update the cache with the latest CDK version. - - Github Issue: - Revisiting and aligning how we build Python connectors and using the same container for test, build and publish will provide better control over the CDK version. - https://github.com/airbytehq/airbyte/issues/25523 - Args: - dagger_client (Client): Dagger client. - """ - # We want the CDK to be re-downloaded on every run per connector to ensure we always get the latest version. - # But we don't want to invalidate the pip cache on every run because it could lead to a different CDK version installed on different architecture build. - cachebuster_value = f"{context.connector.technical_name}_{context.pipeline_start_timestamp}" - - await ( - context.dagger_client.container() - .from_("python:3.9-slim") - .with_mounted_cache(consts.PIP_CACHE_PATH, context.dagger_client.cache_volume(consts.PIP_CACHE_VOLUME_NAME)) - .with_env_variable("CACHEBUSTER", cachebuster_value) - .with_exec(["pip", "install", "--force-reinstall", "airbyte-cdk", "-vvv"], use_entrypoint=True) - .sync() - ) - - -def never_fail_exec(command: List[str]) -> Callable[[Container], Container]: - """ - Wrap a command execution with some bash sugar to always exit with a 0 exit code but write the actual exit code to a file. - - Underlying issue: - When a classic dagger with_exec is returning a >0 exit code an ExecError is raised. - It's OK for the majority of our container interaction. - But some execution, like running CAT, are expected to often fail. - In CAT we don't want ExecError to be raised on container interaction because CAT might write updated secrets that we need to pull from the container after the test run. - The bash trick below is a hack to always return a 0 exit code but write the actual exit code to a file. - The file is then read by the pipeline to determine the exit code of the container. - - Args: - command (List[str]): The command to run in the container. - - Returns: - Callable: _description_ - """ - - def never_fail_exec_inner(container: Container) -> Container: - return container.with_exec(["sh", "-c", f"{' '.join(command)}; echo $? > /exit_code"]) - - return never_fail_exec_inner - - -def do_regression_test_status_check(ctx: click.Context, status_check_name: str, logger: Logger) -> None: - """ - Emit a failing status check that requires a manual override, via a /-command. - - Only required for certified connectors. - """ - commit = ctx.obj["git_revision"] - run_url = ctx.obj["gha_workflow_run_url"] - should_send = ctx.obj.get("ci_context") == consts.CIContext.PULL_REQUEST - - if ( - (not is_automerge_pull_request(ctx.obj.get("pull_request"))) - and (ctx.obj["git_repo_url"] == AIRBYTE_GITHUB_REPO_URL) - and any( - [ - (connector.language == ConnectorLanguage.PYTHON and connector.support_level == "certified") - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - ) - ): - logger.info(f'is_automerge_pull_request={is_automerge_pull_request(ctx.obj.get("pull_request"))}') - logger.info(f'git_repo_url={ctx.obj["git_repo_url"]}') - for connector in ctx.obj["selected_connectors_with_modified_files"]: - logger.info(f"connector = {connector.name}") - logger.info(f"connector.language={connector.language}") - logger.info(f"connector.support_level = {connector.support_level}") - update_commit_status_check( - commit, - "failure", - run_url, - description="Check if regression tests have been manually approved", - context=status_check_name, - is_optional=False, - should_send=should_send, - logger=logger, - ) - else: - update_commit_status_check( - commit, - "success", - run_url, - description="[Skipped]", - context=status_check_name, - is_optional=True, - should_send=should_send, - logger=logger, - ) - - -def determine_changelog_entry_comment(upgrade_base_image_in_metadata_result: StepResult, default_comment: str) -> str: - assert isinstance( - upgrade_base_image_in_metadata_result.step, UpdateBaseImageMetadata - ), "StepResult's step must be instance of UpdateBaseImageMetadata" - if upgrade_base_image_in_metadata_result.output is not None and upgrade_base_image_in_metadata_result.output.get( - "updated_base_image_address" - ): - updated_base_image_address = upgrade_base_image_in_metadata_result.output.get("updated_base_image_address") - if ( - "airbyte/python-connector-base:3.0.0" in updated_base_image_address - or "airbyte/source-declarative-manifest:6.9.2" in updated_base_image_address - ): - return "Starting with this version, the Docker image is now rootless. Please note that this and future versions will not be compatible with Airbyte versions earlier than 0.64" - return default_comment diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/cache_keys.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/cache_keys.py deleted file mode 100644 index c5fd9b75130a..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/cache_keys.py +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from pipelines.helpers.utils import slugify - - -def get_black_cache_key(black_version: str) -> str: - return slugify(f"black-{black_version}") - - -def get_prettier_cache_key(prettier_version: str) -> str: - return slugify(f"prettier-{prettier_version}") diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/changelog.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/changelog.py deleted file mode 100644 index a062b185f767..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/changelog.py +++ /dev/null @@ -1,126 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import datetime -import re -from dataclasses import dataclass -from operator import attrgetter -from typing import Set, Tuple - -import semver - -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO - - -class ChangelogParsingException(Exception): - pass - - -@dataclass(frozen=True) -class ChangelogEntry: - date: datetime.date - version: semver.Version - pr_number: int | str - comment: str - - def to_markdown(self, github_repo: str = AIRBYTE_GITHUB_REPO) -> str: - return f'| {self.version} | {self.date.strftime("%Y-%m-%d")} | [{self.pr_number}](https://github.com/{github_repo}/pull/{self.pr_number}) | {self.comment} |' - - def __str__(self) -> str: - return f'version={self.version}, data={self.date.strftime("%Y-%m-%d")}, pr_number={self.pr_number}, comment={self.comment}' - - def __repr__(self) -> str: - return "ChangelogEntry: " + self.__str__() - - def __eq__(self, other: object) -> bool: - if not isinstance(other, ChangelogEntry): - return False - entry_matches = ( - self.date == other.date - and self.version == other.version - and str(self.pr_number) == str(other.pr_number) - and self.comment == other.comment - ) - return entry_matches - - def __ne__(self, other: object) -> bool: - return not (self.__eq__(other)) - - def __hash__(self) -> int: - return self.__str__().__hash__() - - -def parse_markdown(markdown_lines: list[str], github_repo: str) -> Tuple[int, Set[ChangelogEntry]]: - """This parses the markdown to find the changelog table, and then populates entries with the existing entries""" - changelog_entry_re = ( - "^\\| *(?P[0-9]+\\.[0-9+]+\\.[0-9]+?) *\\| *" - + "(?P[0-9]{4}-[0-9]{2}-[0-9]{2}) *\\| *" - + "\\[?(?P[0-9]+)\\]? ?\\(https://github.com/" - + github_repo - + "/pull/(?P[0-9]+)\\) *\\| *" - + "(?P[^ ].*[^ ]) *\\| *$" - ) - changelog_header_line_index = -1 - changelog_line_enumerator = enumerate(markdown_lines) - for line_index, line in changelog_line_enumerator: - if re.search(r"\| *Version *\| *Date *\| *Pull Request *\| *Subject *\|", line): - changelog_header_line_index = line_index - break - if changelog_header_line_index == -1: - raise ChangelogParsingException("Could not find the changelog section table in the documentation file.") - if markdown_lines[changelog_header_line_index - 1] != "": - raise ChangelogParsingException( - "Found changelog section table in the documentation file at line but there is not blank line before it." - ) - if not re.search(r"(\|[- :]*){4}\|", next(changelog_line_enumerator)[1]): - raise ChangelogParsingException("The changelog table in the documentation file is missing the header delimiter.") - changelog_entries_start_line_index = changelog_header_line_index + 2 - - # parse next line to see if it needs to be cut - entries = set() - for line_index, line in changelog_line_enumerator: - changelog_entry_regexp = re.search(changelog_entry_re, line) - if not changelog_entry_regexp or changelog_entry_regexp.group("pr_number1") != changelog_entry_regexp.group("pr_number2"): - break - entry_version = semver.VersionInfo.parse(changelog_entry_regexp.group("version")) - entry_date = datetime.datetime.strptime(changelog_entry_regexp.group("day"), "%Y-%m-%d").date() - entry_pr_number = int(changelog_entry_regexp.group("pr_number1")) - entry_comment = changelog_entry_regexp.group("comment") - changelog_entry = ChangelogEntry(entry_date, entry_version, entry_pr_number, entry_comment) - entries.add(changelog_entry) - - return changelog_entries_start_line_index, entries - - -class Changelog: - def __init__(self, markdown: str, github_repo: str = AIRBYTE_GITHUB_REPO) -> None: - self.original_markdown_lines = markdown.splitlines() - self.changelog_entries_start_line_index, self.original_entries = parse_markdown(self.original_markdown_lines, github_repo) - self.new_entries: Set[ChangelogEntry] = set() - self.github_repo = github_repo - - def add_entry(self, version: semver.Version, date: datetime.date, pull_request_number: int | str, comment: str) -> None: - self.new_entries.add(ChangelogEntry(date, version, pull_request_number, comment)) - - def to_markdown(self) -> str: - """ - Generates the complete markdown content for the changelog, - including both original and new entries, sorted by version, date, pull request number, and comment. - """ - all_entries = set(self.original_entries.union(self.new_entries)) - sorted_entries = sorted( - sorted( - all_entries, - key=attrgetter("date"), - reverse=True, - ), - key=attrgetter("version"), - reverse=True, - ) - new_lines = ( - self.original_markdown_lines[: self.changelog_entries_start_line_index] - + [line.to_markdown(self.github_repo) for line in sorted_entries] - + self.original_markdown_lines[(self.changelog_entries_start_line_index + len(self.original_entries)) :] - ) - return "\n".join(new_lines) + "\n" diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/cli.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/cli.py deleted file mode 100644 index 4440fd4e466c..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/cli.py +++ /dev/null @@ -1,101 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - - -from dataclasses import dataclass -from logging import Logger -from typing import Any, List, Optional - -import asyncclick as click -import asyncer -from jinja2 import Template - -from pipelines.models.steps import CommandResult - -ALL_RESULTS_KEY = "_run_all_results" - -SUMMARY_TEMPLATE_STR = """ -{% if command_results %} -Summary of commands results -======================== -{% for command_result in command_results %} -{{ '✅' if command_result.success else '❌' }} {{ command_result.command.name }} -{% endfor %} -{% endif %} -""" - -DETAILS_TEMPLATE_STR = """ -{% for command_result in command_results %} -{% if command_result.stdout or command_result.stderr %} -================================= - -Details for {{ command_result.command.name }} command -{% if command_result.stdout %} -STDOUT: -{{ command_result.stdout }} -{% endif %} -{% if command_result.stderr %} -STDERR: -{{ command_result.stderr }} -{% endif %} -{% endif %} -{% endfor %} - -""" - - -@dataclass -class LogOptions: - quiet: bool = True - help_message: Optional[str] = None - - -def log_command_results( - ctx: click.Context, command_results: List[CommandResult], logger: Logger, options: LogOptions = LogOptions() -) -> None: - """ - Log the output of the subcommands run by `run_all_subcommands`. - """ - - if not options.quiet: - details_template = Template(DETAILS_TEMPLATE_STR) - details_message = details_template.render(command_results=command_results) - logger.info(details_message) - - summary_template = Template(SUMMARY_TEMPLATE_STR) - summary_message = summary_template.render(command_results=command_results) - logger.info(summary_message) - - if options.help_message: - logger.info(options.help_message) - - -async def invoke_commands_concurrently(ctx: click.Context, commands: List[click.Command]) -> List[Any]: - """ - Run click commands concurrently and return a list of their return values. - """ - - soon_command_executions_results = [] - async with asyncer.create_task_group() as command_task_group: - for command in commands: - soon_command_execution_result = command_task_group.soonify(command.invoke)(ctx) - soon_command_executions_results.append(soon_command_execution_result) - return [r.value for r in soon_command_executions_results] - - -async def invoke_commands_sequentially(ctx: click.Context, commands: List[click.Command]) -> List[Any]: - """ - Run click commands sequentially and return a list of their return values. - """ - command_executions_results = [] - for command in commands: - command_executions_results.append(await command.invoke(ctx)) - return command_executions_results - - -def get_all_sibling_commands(ctx: click.Context) -> List[click.Command]: - """ - Get all sibling commands of the current command. - """ - return [c for c in ctx.parent.command.commands.values() if c.name != ctx.command.name] # type: ignore diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/cdk_helpers.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/cdk_helpers.py deleted file mode 100644 index 30f546aca752..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/cdk_helpers.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -import re - -import requests -from dagger import Directory - - -def get_latest_python_cdk_version() -> str: - """ - Get the latest version of airbyte-cdk from pypi - """ - cdk_pypi_url = "https://pypi.org/pypi/airbyte-cdk/json" - response = requests.get(cdk_pypi_url) - response.raise_for_status() - package_info = response.json() - return package_info["info"]["version"] - - -async def get_latest_java_cdk_version(repo_dir: Directory) -> str: - version_file_content = await repo_dir.file("airbyte-cdk/java/airbyte-cdk/core/src/main/resources/version.properties").contents() - match = re.search(r"version *= *(?P[0-9]*\.[0-9]*\.[0-9]*)", version_file_content) - if match: - return match.group("version") - raise ValueError("Could not find version in version.properties") diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/command.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/command.py deleted file mode 100644 index 2f822c0e8f2b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/command.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from typing import TYPE_CHECKING, Any, Callable, List - -import asyncclick as click - -from pipelines.airbyte_ci.connectors.context import ConnectorContext -from pipelines.airbyte_ci.connectors.pipeline import run_connectors_pipelines -from pipelines.airbyte_ci.connectors.reports import ConnectorReport, Report -from pipelines.helpers.execution.run_steps import STEP_TREE, run_steps -from pipelines.models.steps import Step, StepStatus - -if TYPE_CHECKING: - from anyio import Semaphore - - -def get_connector_contexts(ctx: click.Context, pipeline_description: str, enable_report_auto_open: bool) -> List[ConnectorContext]: - connectors_contexts = [ - ConnectorContext( - pipeline_name=f"{pipeline_description}: {connector.technical_name}", - connector=connector, - is_local=ctx.obj["is_local"], - git_branch=ctx.obj["git_branch"], - git_revision=ctx.obj["git_revision"], - diffed_branch=ctx.obj["diffed_branch"], - git_repo_url=ctx.obj["git_repo_url"], - ci_report_bucket=ctx.obj["ci_report_bucket_name"], - report_output_prefix=ctx.obj["report_output_prefix"], - gha_workflow_run_url=ctx.obj.get("gha_workflow_run_url"), - dagger_logs_url=ctx.obj.get("dagger_logs_url"), - pipeline_start_timestamp=ctx.obj.get("pipeline_start_timestamp"), - ci_context=ctx.obj.get("ci_context"), - ci_gcp_credentials=ctx.obj["ci_gcp_credentials"], - ci_git_user=ctx.obj["ci_git_user"], - ci_github_access_token=ctx.obj["ci_github_access_token"], - enable_report_auto_open=enable_report_auto_open, - docker_hub_username=ctx.obj.get("docker_hub_username"), - docker_hub_password=ctx.obj.get("docker_hub_password"), - s3_build_cache_access_key_id=ctx.obj.get("s3_build_cache_access_key_id"), - s3_build_cache_secret_key=ctx.obj.get("s3_build_cache_secret_key"), - genai_api_key=ctx.obj.get("genai_api_key"), - dbdocs_token=ctx.obj.get("dbdocs_token"), - ) - for connector in ctx.obj["selected_connectors_with_modified_files"] - ] - return connectors_contexts - - -async def run_connector_pipeline( - ctx: click.Context, - pipeline_description: str, - enable_report_auto_open: bool, - connector_pipeline: Callable, - *args: Any, -) -> bool: - connectors_contexts = get_connector_contexts(ctx, pipeline_description, enable_report_auto_open=enable_report_auto_open) - await run_connectors_pipelines( - connectors_contexts, - connector_pipeline, - pipeline_description, - ctx.obj["concurrency"], - ctx.obj["dagger_logs_path"], - ctx.obj["execute_timeout"], - *args, - ) - - return True - - -async def run_connector_steps( - context: ConnectorContext, semaphore: "Semaphore", steps_to_run: STEP_TREE, restore_original_state: Step | None = None -) -> Report: - async with semaphore: - async with context: - try: - result_dict = await run_steps( - runnables=steps_to_run, - options=context.run_step_options, - ) - except Exception as e: - if restore_original_state: - await restore_original_state.run() - raise e - results = list(result_dict.values()) - if restore_original_state: - if any(step_result.status is StepStatus.FAILURE for step_result in results): - await restore_original_state.run() - else: - # cleanup if available - if hasattr(restore_original_state, "_cleanup"): - method = getattr(restore_original_state, "_cleanup") - if callable(method): - await method() - - report = ConnectorReport(context, steps_results=results, name="TEST RESULTS") - context.report = report - return report diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/dagger_fs.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/dagger_fs.py deleted file mode 100644 index e6fb8fa43f42..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/dagger_fs.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from pathlib import Path - -from dagger import Directory, QueryError - - -# TODO: sometimes we have the full path (connector.metadata_file_path) but we want to be using just the connector dir -# so we could pass in a subdir here: -# await file_exists(connector_dir, connector.metadata_file_path, relative_to=connector.code_directory) -async def dagger_file_exists(dir: Directory, path: Path | str) -> bool: - try: - await dir.file(str(path)) - return True - except QueryError: - return False - - -async def dagger_read_file(directory: Directory, path: Path | str) -> str: - if str(path) not in await directory.entries(): - raise FileNotFoundError(f"File {path} not found in directory {directory}") - content = await directory.file(str(path)).contents() - return content - - -def dagger_write_file(directory: Directory, path: Path | str, new_content: str) -> Directory: - directory = directory.with_new_file(str(path), contents=new_content) - return directory - - -async def dagger_export_file(directory: Directory, path: Path | str) -> bool: - await directory.file(str(path)).export(str(path)) - return True - - -async def dagger_dir_exists(dir: Directory, path: Path | str) -> bool: - try: - await dir.directory(str(path)) - return True - except QueryError: - return False diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/format.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/format.py deleted file mode 100644 index 10aab0946569..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/format.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import logging -import subprocess -from pathlib import Path -from typing import List - -from pipelines.cli.ensure_repo_root import get_airbyte_repo_path_with_fallback - - -async def format_prettier(files: List[Path], logger: logging.Logger) -> None: - if len(files) == 0: - return - - repo_root_path = get_airbyte_repo_path_with_fallback() - config_path = repo_root_path / ".prettierrc" - if not config_path.exists(): - raise Exception(f"Prettier config file not found: {config_path}") - - to_format = [str(file) for file in files] - - logger.info(f" Formatting files: npx prettier --write {' '.join(to_format)}") - command = ["npx", "prettier", "--config", str(config_path), "--write"] + to_format - result = subprocess.run(command, capture_output=True, text=True) - if result.returncode == 0: - logger.info(" Files formatted successfully.") - else: - logger.warn(" Error formatting files.") - - -def verify_formatters() -> None: - try: - subprocess.run(["npx", "--version"], check=True) - except subprocess.CalledProcessError: - raise Exception("npx is required to format files. Please install Node.js and npm.") diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/modifed.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/modifed.py deleted file mode 100644 index 7fcad4bfc83e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/modifed.py +++ /dev/null @@ -1,89 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from dataclasses import dataclass, field -from pathlib import Path -from typing import FrozenSet, Set, Union - -from connector_ops.utils import Connector # type: ignore - -from pipelines import main_logger -from pipelines.helpers.utils import IGNORED_FILE_EXTENSIONS, IGNORED_FILE_NAMES, METADATA_FILE_NAME - - -def get_connector_modified_files(connector: Connector, all_modified_files: Set[Path]) -> FrozenSet[Path]: - connector_modified_files = set() - for modified_file in all_modified_files: - modified_file_path = Path(modified_file) - if modified_file_path.is_relative_to(connector.code_directory): - connector_modified_files.add(modified_file) - return frozenset(connector_modified_files) - - -def _is_connector_modified_directly(connector: Connector, modified_files: Set[Path]) -> bool: - """Test if the connector is being impacted by file changes in the connector itself.""" - for file_path in modified_files: - if _is_ignored_file(file_path): - continue - - if Path(file_path).is_relative_to(Path(connector.code_directory)) or file_path == connector.documentation_file_path: - main_logger.info(f"Adding connector '{connector}' due to connector file modification: {file_path}.") - return True - - return False - - -def _is_connector_modified_indirectly(connector: Connector, modified_files: Set[Path]) -> bool: - """Test if the connector is being impacted by file changes in the connector's dependencies.""" - connector_dependencies = connector.get_local_dependency_paths() - - for file_path in modified_files: - if _is_ignored_file(file_path): - continue - - for connector_dependency in connector_dependencies: - if Path(file_path).is_relative_to(Path(connector_dependency)): - main_logger.info(f"Adding connector '{connector}' due to dependency modification: '{file_path}'.") - return True - - return False - - -def _is_ignored_file(file_path: Union[str, Path]) -> bool: - """Check if the provided file has an ignored extension.""" - return Path(file_path).suffix in IGNORED_FILE_EXTENSIONS or Path(file_path).name in IGNORED_FILE_NAMES - - -def get_modified_connectors(modified_files: Set[Path], all_connectors: Set[Connector], dependency_scanning: bool) -> Set[Connector]: - """Create a mapping of modified connectors (key) and modified files (value). - If dependency scanning is enabled any modification to a dependency will trigger connector pipeline for all connectors that depend on it. - It currently works only for Java connectors . - It's especially useful to trigger tests of strict-encrypt variant when a change is made to the base connector. - Or to tests all jdbc connectors when a change is made to source-jdbc or base-java. - We'll consider extending the dependency resolution to Python connectors once we confirm that it's needed and feasible in term of scale. - """ - modified_connectors = set() - active_connectors = {conn for conn in all_connectors if conn.support_level != "archived"} - main_logger.info( - f"Checking for modified files. Skipping {len(all_connectors) - len(active_connectors)} connectors with support level 'archived'." - ) - # Ignore files with certain extensions - active_modified_files = {f for f in modified_files if not _is_ignored_file(f)} - - for connector in active_connectors: - if _is_connector_modified_directly(connector, active_modified_files): - modified_connectors.add(connector) - elif dependency_scanning and _is_connector_modified_indirectly(connector, active_modified_files): - modified_connectors.add(connector) - - return modified_connectors - - -@dataclass(frozen=True) -class ConnectorWithModifiedFiles(Connector): - modified_files: FrozenSet[Path] = field(default_factory=frozenset) - - @property - def has_metadata_change(self) -> bool: - return any(path.name == METADATA_FILE_NAME for path in self.modified_files) diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/yaml.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/yaml.py deleted file mode 100644 index 5b6a544e6e46..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/connectors/yaml.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -import copy -import io -from pathlib import Path -from typing import List - -from dagger import Directory -from ruamel.yaml import YAML # type: ignore - - -def read_yaml(file_path: Path | str) -> dict: - yaml = YAML() - yaml.preserve_quotes = True - return yaml.load(file_path) - - -async def read_yaml_from_directory(directory: Directory, file_path: str | Path) -> dict: - yaml = YAML() - yaml.preserve_quotes = True - if str(file_path) not in await directory.entries(): - raise FileNotFoundError(f"File {file_path} not found in directory {directory}") - contents = await directory.file(str(file_path)).contents() - return yaml.load(contents) - - -async def write_yaml_to_directory(directory: Directory, yaml_input: dict | List, file_path: str | Path) -> Directory: - data = copy.deepcopy(yaml_input) - yaml = YAML() - buffer = io.BytesIO() - yaml.dump(data, buffer) - new_content = buffer.getvalue().decode("utf-8") - directory = await directory.with_new_file(str(file_path), contents=new_content) - return directory - - -def write_yaml(input: dict | List, file_path: str | Path) -> None: - data = copy.deepcopy(input) - yaml = YAML() - buffer = io.BytesIO() - yaml.dump(data, buffer) - with open(file_path, "w") as file: - file.write(buffer.getvalue().decode("utf-8")) diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/argument_parsing.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/argument_parsing.py deleted file mode 100644 index af32aa52b213..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/argument_parsing.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import re -from typing import TYPE_CHECKING - -import asyncclick as click - -if TYPE_CHECKING: - from enum import Enum - from typing import Callable, Dict, Tuple, Type - - from pipelines.models.steps import STEP_PARAMS - -# Pattern for extra param options: --.= -EXTRA_PARAM_PATTERN_FOR_OPTION = re.compile(r"^--([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_-][a-zA-Z0-9_-]*)=([^=]+)$") -# Pattern for extra param flag: --. -EXTRA_PARAM_PATTERN_FOR_FLAG = re.compile(r"^--([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_-][a-zA-Z0-9_-]*)$") -EXTRA_PARAM_PATTERN_ERROR_MESSAGE = "The extra flags must be structured as --. for flags or --.= for options. You can use - or -- for option/flag names." - - -def build_extra_params_mapping(SupportedStepIds: Type[Enum]) -> Callable: - def callback(ctx: click.Context, argument: click.core.Argument, raw_extra_params: Tuple[str]) -> Dict[str, STEP_PARAMS]: - """Build a mapping of step id to extra params. - Validate the extra params and raise a ValueError if they are invalid. - Validation rules: - - The extra params must be structured as --.= for options or --. for flags. - - The step id must be one of the existing step ids. - - - Args: - ctx (click.Context): The click context. - argument (click.core.Argument): The click argument. - raw_extra_params (Tuple[str]): The extra params provided by the user. - Raises: - ValueError: Raised if the extra params format is invalid. - ValueError: Raised if the step id in the extra params is not one of the unique steps to run. - - Returns: - Dict[Literal, STEP_PARAMS]: The mapping of step id to extra params. - """ - extra_params_mapping: Dict[str, STEP_PARAMS] = {} - for param in raw_extra_params: - is_flag = "=" not in param - pattern = EXTRA_PARAM_PATTERN_FOR_FLAG if is_flag else EXTRA_PARAM_PATTERN_FOR_OPTION - matches = pattern.match(param) - if not matches: - raise ValueError(f"Invalid parameter {param}. {EXTRA_PARAM_PATTERN_ERROR_MESSAGE}") - if is_flag: - step_name, param_name = matches.groups() - param_value = None - else: - step_name, param_name, param_value = matches.groups() - try: - step_id = SupportedStepIds(step_name).value - except ValueError: - raise ValueError(f"Invalid step name {step_name}, it must be one of {[step_id.value for step_id in SupportedStepIds]}") - - extra_params_mapping.setdefault(step_id, {}).setdefault(param_name, []) - # param_value is None if the param is a flag - if param_value is not None: - extra_params_mapping[step_id][param_name].append(param_value) - return extra_params_mapping - - return callback diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/run_steps.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/run_steps.py deleted file mode 100644 index 2d6abeede1ad..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/execution/run_steps.py +++ /dev/null @@ -1,345 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""The actions package is made to declare reusable pipeline components.""" - -from __future__ import annotations - -import inspect -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Set, Tuple, Union - -import anyio -import asyncer -import dpath - -from pipelines import main_logger -from pipelines.models.steps import StepStatus - -if TYPE_CHECKING: - from pipelines.airbyte_ci.connectors.consts import CONNECTOR_TEST_STEP_ID - from pipelines.models.steps import STEP_PARAMS, Step, StepResult - - RESULTS_DICT = Dict[str, StepResult] - ARGS_TYPE = Union[Dict, Callable[[RESULTS_DICT], Dict], Awaitable[Dict]] - - -class InvalidStepConfiguration(Exception): - pass - - -def _get_dependency_graph(steps: STEP_TREE) -> Dict[str, List[str]]: - """ - Get the dependency graph of a step tree. - """ - dependency_graph: Dict[str, List[str]] = {} - for step in steps: - if isinstance(step, StepToRun): - dependency_graph[step.id] = step.depends_on - elif isinstance(step, list): - nested_dependency_graph = _get_dependency_graph(list(step)) - dependency_graph = {**dependency_graph, **nested_dependency_graph} - else: - raise Exception(f"Unexpected step type: {type(step)}") - - return dependency_graph - - -def _get_transitive_dependencies_for_step_id( - dependency_graph: Dict[str, List[str]], step_id: str, visited: Optional[Set[str]] = None -) -> List[str]: - """Get the transitive dependencies for a step id. - - Args: - dependency_graph (Dict[str, str]): The dependency graph to use. - step_id (str): The step id to get the transitive dependencies for. - visited (Optional[Set[str]], optional): The set of visited step ids. Defaults to None. - - Returns: - List[str]: List of transitive dependencies as step ids. - """ - if visited is None: - visited = set() - - if step_id not in visited: - visited.add(step_id) - - dependencies: List[str] = dependency_graph.get(step_id, []) - for dependency in dependencies: - dependencies.extend(_get_transitive_dependencies_for_step_id(dependency_graph, dependency, visited)) - - return dependencies - else: - return [] - - -@dataclass -class RunStepOptions: - """Options for the run_step function.""" - - fail_fast: bool = True - skip_steps: List[str] = field(default_factory=list) - keep_steps: List[str] = field(default_factory=list) - log_step_tree: bool = True - concurrency: int = 10 - step_params: Dict[CONNECTOR_TEST_STEP_ID, STEP_PARAMS] = field(default_factory=dict) - - def __post_init__(self) -> None: - if self.skip_steps and self.keep_steps: - raise ValueError("Cannot use both skip_steps and keep_steps at the same time") - - def get_step_ids_to_skip(self, runnables: STEP_TREE) -> List[str]: - if self.skip_steps: - return self.skip_steps - if self.keep_steps: - step_ids_to_keep = set(self.keep_steps) - dependency_graph = _get_dependency_graph(runnables) - all_step_ids = set(dependency_graph.keys()) - for step_id in self.keep_steps: - step_ids_to_keep.update(_get_transitive_dependencies_for_step_id(dependency_graph, step_id)) - return list(all_step_ids - step_ids_to_keep) - return [] - - @staticmethod - def get_item_or_default(options: Dict[str, List[Any]], key: str, default: Any) -> Any: # noqa: ANN401 - try: - item = dpath.util.get(options, key, separator="/") - except KeyError: - return default - - if not isinstance(item, List): - return item - if len(item) > 1: - raise ValueError(f"Only one value for {key} is allowed. Got {len(item)}") - return item[0] if item else default - - -@dataclass(frozen=True) -class StepToRun: - """ - A class to wrap a Step with its id and args. - - Used to coordinate the execution of multiple steps inside a pipeline. - """ - - id: CONNECTOR_TEST_STEP_ID - step: Step - args: ARGS_TYPE = field(default_factory=dict) - depends_on: List[str] = field(default_factory=list) - - -STEP_TREE = List[StepToRun | List[StepToRun]] - - -async def evaluate_run_args(args: ARGS_TYPE, results: RESULTS_DICT) -> Dict: - """ - Evaluate the args of a StepToRun using the results of previous steps. - """ - if inspect.iscoroutinefunction(args): - return await args(results) - elif callable(args): - return args(results) - elif isinstance(args, dict): - return args - - raise TypeError(f"Unexpected args type: {type(args)}") - - -def _skip_remaining_steps(remaining_steps: STEP_TREE) -> RESULTS_DICT: - """ - Skip all remaining steps. - """ - skipped_results: Dict[str, StepResult] = {} - for runnable_step in remaining_steps: - if isinstance(runnable_step, StepToRun): - skipped_results[runnable_step.id] = runnable_step.step.skip() - elif isinstance(runnable_step, list): - nested_skipped_results = _skip_remaining_steps(list(runnable_step)) - skipped_results = {**skipped_results, **nested_skipped_results} - else: - raise Exception(f"Unexpected step type: {type(runnable_step)}") - - return skipped_results - - -def _step_dependencies_succeeded(step_to_eval: StepToRun, results: RESULTS_DICT) -> bool: - """ - Check if all dependencies of a step have succeeded. - """ - main_logger.info(f"Checking if dependencies {step_to_eval.depends_on} have succeeded") - - # Check if all depends_on keys are in the results dict - # If not, that means a step has not been run yet - # Implying that the order of the steps are not correct - for step_id in step_to_eval.depends_on: - if step_id not in results: - raise InvalidStepConfiguration( - f"Step {step_to_eval.id} depends on {step_id} which has not been run yet. This implies that the order of the steps is not correct. Please check that the steps are in the correct order." - ) - - return all( - results[step_id] and (results[step_id].status is StepStatus.SUCCESS or not results[step_id].consider_in_overall_status) - for step_id in step_to_eval.depends_on - ) - - -def _filter_skipped_steps(steps_to_evaluate: STEP_TREE, skip_steps: List[str], results: RESULTS_DICT) -> Tuple[STEP_TREE, RESULTS_DICT]: - """ - Filter out steps that should be skipped. - - Either because they are in the skip list or because one of their dependencies failed. - """ - steps_to_run: STEP_TREE = [] - for step_to_eval in steps_to_evaluate: - # ignore nested steps - if isinstance(step_to_eval, list): - steps_to_run.append(step_to_eval) - continue - - # skip step if its id is in the skip list - if step_to_eval.id in skip_steps: - main_logger.info(f"Skipping step {step_to_eval.id}") - results[step_to_eval.id] = step_to_eval.step.skip("Skipped by user") - - # skip step if a dependency failed - elif not _step_dependencies_succeeded(step_to_eval, results): - main_logger.info( - f"Skipping step {step_to_eval.id} because one of the dependencies have not been met: {step_to_eval.depends_on}" - ) - results[step_to_eval.id] = step_to_eval.step.skip("Skipped because a dependency was not met") - - else: - steps_to_run.append(step_to_eval) - - return steps_to_run, results - - -def _get_next_step_group(steps: STEP_TREE) -> Tuple[STEP_TREE, STEP_TREE]: - """ - Get the next group of steps to run concurrently. - """ - if not steps: - return [], [] - - if isinstance(steps[0], list): - return list(steps[0]), list(steps[1:]) - else: - # Termination case: if the next step is not a list that means we have reached the max depth - return steps, [] - - -def _log_step_tree(step_tree: STEP_TREE, options: RunStepOptions, depth: int = 0) -> None: - """ - Log the step tree to the console. - - e.g. - Step tree - - step1 - - step2 - - step3 - - step4 (skip) - - step5 - - step6 - """ - indent = " " - for steps in step_tree: - if isinstance(steps, list): - _log_step_tree(list(steps), options, depth + 1) - else: - if steps.id in options.skip_steps: - main_logger.info(f"{indent * depth}- {steps.id} (skip)") - else: - main_logger.info(f"{indent * depth}- {steps.id}") - - -async def run_steps( - runnables: STEP_TREE, - results: RESULTS_DICT = {}, - options: RunStepOptions = RunStepOptions(), -) -> RESULTS_DICT: - """Run multiple steps sequentially, or in parallel if steps are wrapped into a sublist. - - Examples - -------- - >>> from pipelines.models.steps import Step, StepResult, StepStatus - >>> class TestStep(Step): - ... async def _run(self) -> StepResult: - ... return StepResult(step=self, status=StepStatus.SUCCESS) - >>> steps = [ - ... StepToRun(id="step1", step=TestStep()), - ... [ - ... StepToRun(id="step2", step=TestStep()), - ... StepToRun(id="step3", step=TestStep()), - ... ], - ... StepToRun(id="step4", step=TestStep()), - ... ] - >>> results = await run_steps(steps) - >>> results["step1"].status - - >>> results["step2"].status - - >>> results["step3"].status - - >>> results["step4"].status - - - - Args: - runnables (List[StepToRun]): List of steps to run. - results (RESULTS_DICT, optional): Dictionary of step results, used for recursion. - - Returns: - RESULTS_DICT: Dictionary of step results. - """ - # If there are no steps to run, return the results - if not runnables: - return results - - step_ids_to_skip = options.get_step_ids_to_skip(runnables) - # Log the step tree - if options.log_step_tree: - main_logger.info(f"STEP TREE: {runnables}") - _log_step_tree(runnables, options) - options.log_step_tree = False - - # If any of the previous steps failed, skip the remaining steps - if options.fail_fast and any(result.status is StepStatus.FAILURE and result.consider_in_overall_status for result in results.values()): - skipped_results = _skip_remaining_steps(runnables) - return {**results, **skipped_results} - - # Pop the next step to run - steps_to_evaluate, remaining_steps = _get_next_step_group(runnables) - - # Remove any skipped steps - steps_to_run, results = _filter_skipped_steps(steps_to_evaluate, step_ids_to_skip, results) - - # Run all steps in list concurrently - semaphore = anyio.Semaphore(options.concurrency) - async with semaphore: - async with asyncer.create_task_group() as task_group: - tasks = [] - for step_to_run in steps_to_run: - # if the step to run is a list, run it in parallel - if isinstance(step_to_run, list): - tasks.append(task_group.soonify(run_steps)(list(step_to_run), results, options)) - else: - step_args = await evaluate_run_args(step_to_run.args, results) - step_to_run.step.extra_params = options.step_params.get(step_to_run.id, {}) - main_logger.info(f"QUEUING STEP {step_to_run.id}") - tasks.append(task_group.soonify(step_to_run.step.run)(**step_args)) - - # Apply new results - new_results: Dict[str, Any] = {} - for i, task in enumerate(tasks): - step_to_run = steps_to_run[i] - if isinstance(step_to_run, list): - new_results = {**new_results, **task.value} - else: - new_results[step_to_run.id] = task.value - - return await run_steps( - runnables=remaining_steps, - results={**results, **new_results}, - options=options, - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/gcs.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/gcs.py deleted file mode 100644 index a73cf7f686ec..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/gcs.py +++ /dev/null @@ -1,50 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import json -from pathlib import Path -from typing import Tuple - -from google.cloud import storage # type: ignore -from google.oauth2 import service_account # type: ignore - -from pipelines import main_logger -from pipelines.consts import GCS_PUBLIC_DOMAIN - - -def upload_to_gcs(file_path: Path, bucket_name: str, object_name: str, credentials: str) -> Tuple[str, str]: - """Upload a file to a GCS bucket. - - Args: - file_path (Path): The path to the file to upload. - bucket_name (str): The name of the GCS bucket. - object_name (str): The name of the object in the GCS bucket. - credentials (str): The GCS credentials as a JSON string. - """ - # Exit early if file does not exist - if not file_path.exists(): - main_logger.warning(f"File {file_path} does not exist. Skipping upload to GCS.") - return "", "" - - credentials = service_account.Credentials.from_service_account_info(json.loads(credentials)) - client = storage.Client(credentials=credentials) - bucket = client.get_bucket(bucket_name) - blob = bucket.blob(object_name) - blob.upload_from_filename(str(file_path)) - gcs_uri = f"gs://{bucket_name}/{object_name}" - public_url = f"{GCS_PUBLIC_DOMAIN}/{bucket_name}/{object_name}" - return gcs_uri, public_url - - -def sanitize_gcp_credentials(raw_value: str) -> str: - """Try to parse the raw string input that should contain a json object with the GCS credentials. - It will raise an exception if the parsing fails and help us to fail fast on invalid credentials input. - - Args: - raw_value (str): A string representing a json object with the GCS credentials. - - Returns: - str: The raw value string if it was successfully parsed. - """ - return json.dumps(json.loads(raw_value)) diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/git.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/git.py deleted file mode 100644 index 926a07a76867..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/git.py +++ /dev/null @@ -1,129 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import functools -from typing import Set - -import git -from dagger import Connection, SessionError - -from pipelines.consts import CIContext -from pipelines.dagger.containers.git import checked_out_git_container -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL -from pipelines.helpers.utils import DAGGER_CONFIG, DIFF_FILTER - - -def get_current_git_revision() -> str: # noqa D103 - return git.Repo(search_parent_directories=True).head.object.hexsha - - -def get_current_git_branch() -> str: # noqa D103 - return git.Repo(search_parent_directories=True).active_branch.name - - -async def get_modified_files_in_branch_remote( - current_git_repo_url: str, current_git_branch: str, current_git_revision: str, diffed_branch: str = "master", retries: int = 3 -) -> Set[str]: - """Use git diff to spot the modified files on the remote branch.""" - try: - async with Connection(DAGGER_CONFIG) as dagger_client: - container = await checked_out_git_container( - dagger_client, current_git_branch, current_git_revision, diffed_branch, repo_url=current_git_repo_url - ) - modified_files = await container.with_exec( - ["diff", f"--diff-filter={DIFF_FILTER}", "--name-only", f"origin/{diffed_branch}...target/{current_git_branch}"], - use_entrypoint=True, - ).stdout() - except SessionError: - if retries > 0: - return await get_modified_files_in_branch_remote( - current_git_repo_url, current_git_branch, current_git_revision, diffed_branch, retries - 1 - ) - else: - raise - return set(modified_files.split("\n")) - - -def get_modified_files_local(current_git_revision: str, diffed: str = "master") -> Set[str]: - """Use git diff and git status to spot the modified files in the local repo.""" - airbyte_repo = git.Repo() - modified_files = airbyte_repo.git.diff(f"--diff-filter={DIFF_FILTER}", "--name-only", f"{diffed}...{current_git_revision}").split("\n") - status_output = airbyte_repo.git.status("--porcelain") - for not_committed_change in status_output.split("\n"): - file_path = not_committed_change.strip().split(" ")[-1] - if file_path: - modified_files.append(file_path) - return set(modified_files) - - -async def get_modified_files_in_branch( - current_repo_url: str, current_git_branch: str, current_git_revision: str, diffed_branch: str, is_local: bool = True -) -> Set[str]: - """Retrieve the list of modified files on the branch.""" - if is_local: - return get_modified_files_local(current_git_revision, diffed_branch) - else: - return await get_modified_files_in_branch_remote(current_repo_url, current_git_branch, current_git_revision, diffed_branch) - - -async def get_modified_files_in_commit_remote(current_git_branch: str, current_git_revision: str, retries: int = 3) -> Set[str]: - try: - async with Connection(DAGGER_CONFIG) as dagger_client: - container = await checked_out_git_container(dagger_client, current_git_branch, current_git_revision) - modified_files = await container.with_exec( - ["diff-tree", "--no-commit-id", "--name-only", current_git_revision, "-r"], use_entrypoint=True - ).stdout() - except SessionError: - if retries > 0: - return await get_modified_files_in_commit_remote(current_git_branch, current_git_revision, retries - 1) - else: - raise - return set(modified_files.split("\n")) - - -def get_modified_files_in_commit_local(current_git_revision: str) -> Set[str]: - airbyte_repo = git.Repo() - modified_files = airbyte_repo.git.diff_tree("--no-commit-id", "--name-only", current_git_revision, "-r").split("\n") - return set(modified_files) - - -async def get_modified_files_in_commit(current_git_branch: str, current_git_revision: str, is_local: bool = True) -> Set[str]: - if is_local: - return get_modified_files_in_commit_local(current_git_revision) - else: - return await get_modified_files_in_commit_remote(current_git_branch, current_git_revision) - - -@functools.cache -def get_git_repo() -> git.Repo: - """Retrieve the git repo.""" - return git.Repo(search_parent_directories=True) - - -@functools.cache -def get_git_repo_path() -> str: - """Retrieve the git repo path.""" - return str(get_git_repo().working_tree_dir) - - -async def get_modified_files( - git_branch: str, - git_revision: str, - diffed_branch: str, - is_local: bool, - ci_context: CIContext, - git_repo_url: str = AIRBYTE_GITHUB_REPO_URL, -) -> Set[str]: - """Get the list of modified files in the current git branch. - If the current branch is master, it will return the list of modified files in the head commit. - The head commit on master should be the merge commit of the latest merged pull request as we squash commits on merge. - Pipelines like "publish on merge" are triggered on each new commit on master. - - If the CI context is a pull request, it will return the list of modified files in the pull request, without using git diff. - If the current branch is not master, it will return the list of modified files in the current branch. - This latest case is the one we encounter when running the pipeline locally, on a local branch, or manually on GHA with a workflow dispatch event. - """ - if ci_context is CIContext.MASTER or (ci_context is CIContext.MANUAL and git_branch == "master"): - return await get_modified_files_in_commit(git_branch, git_revision, is_local) - return await get_modified_files_in_branch(git_repo_url, git_branch, git_revision, diffed_branch, is_local) diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/github.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/github.py deleted file mode 100644 index 086cdce36c44..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/github.py +++ /dev/null @@ -1,237 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""Module grouping functions interacting with the GitHub API.""" - -from __future__ import annotations - -import base64 -import os -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING - -import github as github_sdk -from connector_ops.utils import console # type: ignore - -from pipelines import main_logger -from pipelines.consts import CIContext -from pipelines.models.secrets import Secret - -if TYPE_CHECKING: - from logging import Logger - from typing import Iterable, List, Optional - - -DEFAULT_AIRBYTE_GITHUB_REPO = "airbytehq/airbyte" -AIRBYTE_GITHUB_REPO = os.environ.get("AIRBYTE_GITHUB_REPO", DEFAULT_AIRBYTE_GITHUB_REPO) -AIRBYTE_GITHUBUSERCONTENT_URL_PREFIX = f"https://raw.githubusercontent.com/{AIRBYTE_GITHUB_REPO}" -AIRBYTE_GITHUB_REPO_URL_PREFIX = f"https://github.com/{AIRBYTE_GITHUB_REPO}" -AIRBYTE_GITHUB_REPO_URL = f"{AIRBYTE_GITHUB_REPO_URL_PREFIX}.git" -BASE_BRANCH = "master" - - -def safe_log(logger: Optional[Logger], message: str, level: str = "info") -> None: - """Log a message to a logger if one is available, otherwise print to the console.""" - if logger: - log_method = getattr(logger, level.lower()) - log_method(message) - else: - main_logger.info(message) - - -def update_commit_status_check( - sha: str, - state: str, - target_url: str, - description: str, - context: str, - is_optional: bool = False, - should_send: bool = True, - logger: Optional[Logger] = None, -) -> None: - """Call the GitHub API to create commit status check. - - Args: - sha (str): Hash of the commit for which you want to create a status check. - state (str): The check state (success, failure, pending) - target_url (str): The URL to attach to the commit check for details. - description (str): Description of the check that is run. - context (str): Name of the Check context e.g: source-pokeapi tests - should_send (bool, optional): Whether the commit check should actually be sent to GitHub API. Defaults to True. - logger (Logger, optional): A logger to log info about updates. Defaults to None. - """ - if not should_send: - return - - safe_log(logger, f"Attempting to create {state} status for commit {sha} on Github in {context} context.") - try: - github_client = github_sdk.Github(auth=github_sdk.Auth.Token(os.environ["CI_GITHUB_ACCESS_TOKEN"])) - airbyte_repo = github_client.get_repo(AIRBYTE_GITHUB_REPO) - except Exception as e: - if logger: - logger.error("No commit status check sent, the connection to Github API failed", exc_info=True) - else: - console.print(e) - return - - # If the check is optional, we don't want to fail the build if it fails. - # Instead, we want to mark it as a warning. - # Unfortunately, Github doesn't have a warning state, so we use success instead. - if is_optional and state == "failure": - state = "success" - description = f"[WARNING] optional check failed {context}: {description}" - - context = context if bool(os.environ.get("PRODUCTION", False)) is True else f"[please ignore] {context}" - airbyte_repo.get_commit(sha=sha).create_status( - state=state, - target_url=target_url, - description=description, - context=context, - ) - safe_log(logger, f"Created {state} status for commit {sha} on Github in {context} context with desc: {description}.") - - -def get_pull_request(pull_request_number: int, github_access_token: Secret) -> github_sdk.PullRequest.PullRequest: - """Get a pull request object from its number. - - Args: - pull_request_number (str): The number of the pull request to get. - github_access_token (Secret): The GitHub access token to use to authenticate. - Returns: - PullRequest: The pull request object. - """ - github_client = github_sdk.Github(auth=github_sdk.Auth.Token(github_access_token.value)) - airbyte_repo = github_client.get_repo(AIRBYTE_GITHUB_REPO) - return airbyte_repo.get_pull(pull_request_number) - - -def update_global_commit_status_check_for_tests(click_context: dict, github_state: str, logger: Optional[Logger] = None) -> None: - update_commit_status_check( - click_context["git_revision"], - github_state, - click_context["gha_workflow_run_url"], - click_context["global_status_check_description"], - click_context["global_status_check_context"], - should_send=click_context.get("ci_context") == CIContext.PULL_REQUEST, - logger=logger, - ) - - -@dataclass -class ChangedFile: - path: str - sha: str | None - - -def create_or_update_github_pull_request( - modified_repo_files: Iterable[Path], - github_token: str, - branch_id: str, - commit_message: str, - pr_title: str, - pr_body: str, - repo_name: str = AIRBYTE_GITHUB_REPO, - logger: Optional[Logger] = None, - skip_ci: bool = False, - labels: Optional[Iterable[str]] = None, - force_push: bool = True, - github_auto_merge: bool = False, -) -> github_sdk.PullRequest.PullRequest: - logger = logger or main_logger - g = github_sdk.Github(auth=github_sdk.Auth.Token(github_token)) - repo = g.get_repo(repo_name) - commit_message = commit_message if not skip_ci else f"[skip ci] {commit_message}" - - changed_files: List[ChangedFile] = [] - for modified_file in modified_repo_files: # these are relative to the repo root - if modified_file.exists(): - with open(modified_file, "rb") as file: - logger.info(f"Reading file: {modified_file}") - content = base64.b64encode(file.read()).decode("utf-8") # Encode file content to base64 - blob = repo.create_git_blob(content, "base64") - changed_file = ChangedFile(path=str(modified_file), sha=blob.sha) - changed_files.append(changed_file) - else: - logger.info(f"{modified_file} no longer exists, adding to PR as a deletion") - changed_file = ChangedFile(path=str(modified_file), sha=None) - changed_files.append(changed_file) - existing_ref = None - try: - existing_ref = repo.get_git_ref(f"heads/{branch_id}") - logger.info(f"Git ref {branch_id} already exists") - except github_sdk.GithubException: - pass - - base_sha = repo.get_branch(BASE_BRANCH).commit.sha - if not existing_ref: - repo.create_git_ref(f"refs/heads/{branch_id}", base_sha) - - parent_commit = repo.get_git_commit(base_sha) - parent_tree = repo.get_git_tree(base_sha) - - # Filter and update tree elements - tree_elements: List[github_sdk.InputGitTreeElement] = [] - for changed_file in changed_files: - if changed_file.sha is None: - # make sure it's actually in the current tree - try: - # Attempt to get the file from the specified commit - repo.get_contents(changed_file.path, ref=base_sha) - # logger.info(f"File {changed_file.path} exists in commit {base_sha}") - except github_sdk.UnknownObjectException: - # don't need to add it to the tree - logger.info(f"{changed_file.path} not in parent: {base_sha}") - continue - - # Update or new file addition or needed deletion - tree_elements.append( - github_sdk.InputGitTreeElement( - path=changed_file.path, - mode="100644", - type="blob", - sha=changed_file.sha, - ) - ) - - # Create a new commit pointing to that tree - tree = repo.create_git_tree(tree_elements, base_tree=parent_tree) - commit = repo.create_git_commit(commit_message, tree, [parent_commit]) - repo.get_git_ref(f"heads/{branch_id}").edit(sha=commit.sha, force=force_push) - # Check if there's an existing pull request - found_pr = None - open_pulls = repo.get_pulls(state="open", base=BASE_BRANCH) - for pr in open_pulls: - if pr.head.ref == branch_id: - found_pr = pr - logger.info(f"Pull request already exists: {pr.html_url}") - if found_pr: - pull_request = found_pr - found_pr.edit(title=pr_title, body=pr_body) - else: - pull_request = repo.create_pull( - title=pr_title, - body=pr_body, - base=BASE_BRANCH, - head=branch_id, - ) - logger.info(f"Created pull request: {pull_request.html_url}") - - labels = labels or [] - for label in labels: - pull_request.add_to_labels(label) - logger.info(f"Added label {label} to pull request") - - if github_auto_merge: - logger.info("Enabling (native) GitHub auto-merge for the pull request") - pull_request.enable_automerge("SQUASH") - - return pull_request - - -def is_automerge_pull_request(pull_request: Optional[github_sdk.PullRequest.PullRequest]) -> bool: - labels = [label.name for label in pull_request.get_labels()] if pull_request else [] - if labels and "auto-merge" in labels: - return True - return False diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/pip.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/pip.py deleted file mode 100644 index ea982af53364..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/pip.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from typing import Optional - -import requests - - -def is_package_published(package_name: Optional[str], version: Optional[str], registry_url: str) -> bool: - """ - Check if a package with a specific version is published on a python registry. - """ - if not package_name or not version: - return False - - url = f"{registry_url}/{package_name}/{version}/json" - - try: - response = requests.get(url) - return response.status_code == 200 - except requests.exceptions.ConnectionError: - return False diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/sentry_utils.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/sentry_utils.py deleted file mode 100644 index ae190629e364..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/sentry_utils.py +++ /dev/null @@ -1,92 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import importlib.metadata -import os -from typing import TYPE_CHECKING - -import sentry_sdk -from connector_ops.utils import Connector # type: ignore - -if TYPE_CHECKING: - from typing import Any, Callable, Dict, Optional - - from asyncclick import Command, Context - - from pipelines.models.steps import Step - - -def initialize() -> None: - if "SENTRY_DSN" in os.environ: - sentry_sdk.init( - dsn=os.environ.get("SENTRY_DSN"), - environment=os.environ.get("SENTRY_ENVIRONMENT") or "production", - before_send=before_send, # type: ignore - release=f"pipelines@{importlib.metadata.version('pipelines')}", - ) - - -def before_send(event: Dict[str, Any], hint: Dict[str, Any]) -> Optional[Dict[str, Any]]: - # Ignore logged errors that do not contain an exception - if "log_record" in hint and "exc_info" not in hint: - return None - - return event - - -def with_step_context(func: Callable) -> Callable: - def wrapper(self: Step, *args: Any, **kwargs: Any) -> Step: - with sentry_sdk.configure_scope() as scope: - step_name = self.__class__.__name__ - scope.set_tag("pipeline_step", step_name) - scope.set_context( - "Pipeline Step", - { - "name": step_name, - "step_title": self.title, - "max_retries": self.max_retries, - "max_duration": self.max_duration, - "retry_count": self.retry_count, - }, - ) - - if hasattr(self.context, "connector"): - connector: Connector = self.context.connector - scope.set_tag("connector", connector.technical_name) - scope.set_context( - "Connector", - { - "name": connector.name, - "technical_name": connector.technical_name, - "language": connector.language, - "version": connector.version, - "support_level": connector.support_level, - }, - ) - - return func(self, *args, **kwargs) - - return wrapper - - -def with_command_context(func: Callable) -> Callable: - def wrapper(self: Command, ctx: Context, *args: Any, **kwargs: Any) -> Command: - with sentry_sdk.configure_scope() as scope: - scope.set_tag("pipeline_command", self.name) - scope.set_context( - "Pipeline Command", - { - "name": self.name, - "params": self.params, - }, - ) - - scope.set_context("Click Context", ctx.obj) - scope.set_tag("git_branch", ctx.obj.get("git_branch", "unknown")) - scope.set_tag("git_revision", ctx.obj.get("git_revision", "unknown")) - - return func(self, ctx, *args, **kwargs) - - return wrapper diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/slack.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/slack.py deleted file mode 100644 index 38c1818b0b7b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/slack.py +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# -from __future__ import annotations - -import json -import typing - -import requests - -from pipelines import main_logger - -if typing.TYPE_CHECKING: - from typing import List - - -def send_message_to_webhook(message: str, channels: List[str], webhook: str) -> List[requests.Response]: - responses = [] - for channel in channels: - channel = channel[1:] if channel.startswith("#") else channel - payload = {"channel": f"#{channel}", "username": "Connectors CI/CD Bot", "text": message} - response = requests.post(webhook, data={"payload": json.dumps(payload)}) - - # log if the request failed, but don't fail the pipeline - if not response.ok: - main_logger.error(f"Failed to send message to slack webhook: {response.text}") - responses.append(response) - - return responses diff --git a/airbyte-ci/connectors/pipelines/pipelines/helpers/utils.py b/airbyte-ci/connectors/pipelines/pipelines/helpers/utils.py deleted file mode 100644 index c93394e4d215..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/helpers/utils.py +++ /dev/null @@ -1,409 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module groups util function used in pipelines.""" - -from __future__ import annotations - -import contextlib -import datetime -import functools -import os -import re -import sys -import unicodedata -import warnings -import xml.sax.saxutils -from io import TextIOWrapper -from pathlib import Path -from typing import TYPE_CHECKING - -import asyncclick as click -import asyncer -from dagger import Client, Config, Container, Directory, ExecError, File, ImageLayerCompression, Platform, Secret -from exceptiongroup import ExceptionGroup -from more_itertools import chunked - -if TYPE_CHECKING: - from typing import Any, Callable, Generator, List, Optional, Set, Tuple - - from pipelines.airbyte_ci.connectors.context import ConnectorContext - -DAGGER_CONFIG = Config(log_output=sys.stderr) -METADATA_FILE_NAME = "metadata.yaml" -MANIFEST_FILE_NAME = "manifest.yaml" -METADATA_ICON_FILE_NAME = "icon.svg" -DIFF_FILTER = "MADRT" # Modified, Added, Deleted, Renamed, Type changed -IGNORED_FILE_EXTENSIONS: list[str] = [".md"] -IGNORED_FILE_NAMES: list[str] = [ - ".coveragerc", - "poe_tasks.toml", -] - - -# This utils will probably be redundant once https://github.com/dagger/dagger/issues/3764 is implemented -async def check_path_in_workdir(container: Container, path: str) -> bool: - """Check if a local path is mounted to the working directory of a container. - - Args: - container (Container): The container on which we want the check the path existence. - path (str): Directory or file path we want to check the existence in the container working directory. - - Returns: - bool: Whether the path exists in the container working directory. - """ - workdir = (await container.with_exec(["pwd"]).stdout()).strip() - mounts = await container.mounts() - if workdir in mounts: - expected_file_path = Path(workdir[1:]) / path - return expected_file_path.is_file() or expected_file_path.is_dir() - else: - return False - - -def secret_host_variable(client: Client, name: str, default: str = "") -> Callable[[Container], Container]: - """Add a host environment variable as a secret in a container. - - Example: - container.with_(secret_host_variable(client, "MY_SECRET")) - - Args: - client (Client): The dagger client. - name (str): The name of the environment variable. The same name will be - used in the container, for the secret name and for the host variable. - default (str): The default value to use if the host variable is not set. Defaults to "". - - Returns: - Callable[[Container], Container]: A function that can be used in a `Container.with_()` method. - """ - - def _secret_host_variable(container: Container) -> Container: - return container.with_secret_variable(name, get_secret_host_variable(client, name, default)) - - return _secret_host_variable - - -def get_secret_host_variable(client: Client, name: str, default: str = "") -> Secret: - """Creates a dagger.Secret from a host environment variable. - - Args: - client (Client): The dagger client. - name (str): The name of the environment variable. The same name will be used for the secret. - default (str): The default value to use if the host variable is not set. Defaults to "". - - Returns: - Secret: A dagger secret. - """ - return client.set_secret(name, os.environ.get(name, default)) - - -# This utils will probably be redundant once https://github.com/dagger/dagger/issues/3764 is implemented -async def get_file_contents(container: Container, path: str) -> Optional[str]: - """Retrieve a container file contents. - - Args: - container (Container): The container hosting the file you want to read. - path (str): Path, in the container, to the file you want to read. - - Returns: - Optional[str]: The file content if the file exists in the container, None otherwise. - """ - dir_name, file_name = os.path.split(path) - if file_name not in set(await container.directory(dir_name).entries()): - return None - return await container.file(path).contents() - - -@contextlib.contextmanager -def catch_exec_error_group() -> Generator: - try: - yield - except ExceptionGroup as eg: - for e in eg.exceptions: - if isinstance(e, ExecError): - raise e - raise - - -async def get_container_output(container: Container) -> Tuple[str, str]: - """Retrieve both stdout and stderr of a container, concurrently. - - Args: - container (Container): The container to execute. - - Returns: - Tuple[str, str]: The stdout and stderr of the container, respectively. - """ - with catch_exec_error_group(): - async with asyncer.create_task_group() as task_group: - soon_stdout = task_group.soonify(container.stdout)() - soon_stderr = task_group.soonify(container.stderr)() - return soon_stdout.value, soon_stderr.value - - -async def get_exec_result(container: Container) -> Tuple[int, str, str]: - """Retrieve the exit_code along with stdout and stderr of a container by handling the ExecError. - - Note: It is preferrable to not worry about the exit code value and just capture - ExecError to handle errors. This is offered as a convenience when the exit code - value is actually needed. - - If the container has a file at /exit_code, the exit code will be read from it. - See hacks.never_fail_exec for more details. - - Args: - container (Container): The container to execute. - - Returns: - Tuple[int, str, str]: The exit_code, stdout and stderr of the container, respectively. - """ - try: - exit_code = 0 - in_file_exit_code = await get_file_contents(container, "/exit_code") - if in_file_exit_code: - exit_code = int(in_file_exit_code) - return exit_code, *(await get_container_output(container)) - except ExecError as e: - return e.exit_code, e.stdout, e.stderr - - -async def with_exit_code(container: Container) -> int: - """Read the container exit code. - - Args: - container (Container): The container from which you want to read the exit code. - - Returns: - int: The exit code. - """ - try: - await container - except ExecError as e: - return e.exit_code - return 0 - - -async def with_stderr(container: Container) -> str: - """Retrieve the stderr of a container even on execution error.""" - try: - return await container.stderr() - except ExecError as e: - return e.stderr - - -async def with_stdout(container: Container) -> str: - """Retrieve the stdout of a container even on execution error.""" - try: - return await container.stdout() - except ExecError as e: - return e.stdout - - -def get_current_epoch_time() -> int: # noqa D103 - return round(datetime.datetime.utcnow().timestamp()) - - -def slugify(value: object, allow_unicode: bool = False) -> str: - """ - Taken from https://github.com/django/django/blob/master/django/utils/text.py. - - Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated - dashes to single dashes. Remove characters that aren't alphanumerics, - underscores, or hyphens. Convert to lowercase. Also strip leading and - trailing whitespace, dashes, and underscores. - """ - value = str(value) - if allow_unicode: - value = unicodedata.normalize("NFKC", value) - else: - value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii") - value = re.sub(r"[^\w\s-]", "", value.lower()) - return re.sub(r"[-\s]+", "-", value).strip("-_") - - -def key_value_text_to_dict(text: str) -> dict: - kv = {} - for line in text.split("\n"): - if "=" in line: - try: - k, v = line.split("=") - except ValueError: - continue - kv[k] = v - return kv - - -async def key_value_file_to_dict(file: File) -> dict: - return key_value_text_to_dict(await file.contents()) - - -async def get_dockerfile_labels(dockerfile: File) -> dict: - return {k.replace("LABEL ", ""): v for k, v in (await key_value_file_to_dict(dockerfile)).items() if k.startswith("LABEL")} - - -async def get_version_from_dockerfile(dockerfile: File) -> str: - dockerfile_labels = await get_dockerfile_labels(dockerfile) - try: - return dockerfile_labels["io.airbyte.version"] - except KeyError: - raise Exception("Could not get the version from the Dockerfile labels.") - - -def create_and_open_file(file_path: Path) -> TextIOWrapper: - """Create a file and open it for writing. - - Args: - file_path (Path): The path to the file to create. - - Returns: - File: The file object. - """ - file_path.parent.mkdir(parents=True, exist_ok=True) - file_path.touch() - return file_path.open("w") - - -async def execute_concurrently(steps: List[Callable], concurrency: int = 5) -> List[Any]: - tasks = [] - # Asyncer does not have builtin semaphore, so control concurrency via chunks of steps - # Anyio has semaphores but does not have the soonify method which allow access to results via the value task attribute. - for chunk in chunked(steps, concurrency): - async with asyncer.create_task_group() as task_group: - tasks += [task_group.soonify(step)() for step in chunk] - return [task.value for task in tasks] - - -async def export_container_to_tarball( - context: ConnectorContext, container: Container, platform: Platform, tar_file_name: Optional[str] = None -) -> Tuple[Optional[File], Optional[Path]]: - """Save the container image to the host filesystem as a tar archive. - - Exports a container to a tarball file. - The tarball file is saved to the host filesystem in the directory specified by the host_image_export_dir_path attribute of the context. - - Args: - context (ConnectorContext): The current connector context. - container (Container) : The list of container variants to export. - platform (Platform): The platform of the container to export. - tar_file_name (Optional[str], optional): The name of the tar archive file. Defaults to None. - - Returns: - Tuple[Optional[File], Optional[Path]]: A tuple with the file object holding the tar archive on the host and its path. - """ - tar_file_name = ( - f"{slugify(context.connector.technical_name)}_{context.git_revision}_{platform.replace('/', '_')}.tar" - if tar_file_name is None - else tar_file_name - ) - local_path = Path(f"{context.host_image_export_dir_path}/{tar_file_name}") - export_success = await container.export(str(local_path), forced_compression=ImageLayerCompression.Gzip) - if export_success: - return context.dagger_client.host().file(str(local_path)), local_path - return None, None - - -def format_duration(time_delta: datetime.timedelta) -> str: - total_seconds = time_delta.total_seconds() - if total_seconds < 60: - return "{:.2f}s".format(total_seconds) - minutes = int(total_seconds // 60) - seconds = int(total_seconds % 60) - return "{:02d}mn{:02d}s".format(minutes, seconds) - - -def sh_dash_c(lines: List[str]) -> List[str]: - """Wrap sequence of commands in shell for safe usage of dagger Container's with_exec method.""" - return ["sh", "-c", " && ".join(["set -o xtrace"] + lines)] - - -def transform_strs_to_paths(str_paths: Set[str]) -> List[Path]: - """Transform a list of string paths to an ordered list of Path objects. - - Args: - str_paths (Set[str]): A set of string paths. - - Returns: - List[Path]: A list of Path objects. - """ - return sorted([Path(str_path) for str_path in str_paths]) - - -def fail_if_missing_docker_hub_creds(ctx: click.Context) -> None: - if ctx.obj["docker_hub_username"] is None or ctx.obj["docker_hub_password"] is None: - raise click.UsageError( - "You need to be logged to DockerHub registry to run this command. Please set DOCKER_HUB_USERNAME and DOCKER_HUB_PASSWORD environment variables." - ) - - -def java_log_scrub_pattern(secrets_to_mask: List[str]) -> str: - """Transforms a list of secrets into a LOG_SCRUB_PATTERN env var value for our log4j test configuration.""" - # Build a regex pattern that matches any of the secrets to mask. - regex_pattern = "|".join(map(re.escape, secrets_to_mask)) - # Now, make this string safe to consume by the log4j configuration. - # Its parser is XML-based so the pattern needs to be escaped again, and carefully. - return xml.sax.saxutils.escape( - regex_pattern, - # Annoyingly, the log4j properties file parser is quite brittle when it comes to - # handling log message patterns. In our case the env var is injected like this: - # - # ${env:LOG_SCRUB_PATTERN:-defaultvalue} - # - # We must avoid confusing the parser with curly braces or colons otherwise the - # printed log messages will just consist of `%replace`. - { - "\t": " ", - "'": "'", - '"': """, - "{": "{", - "}": "}", - ":": ":", - }, - ) - - -def dagger_directory_as_zip_file(dagger_client: Client, directory: Directory, directory_name: str) -> File: - """Compress a directory and return a File object representing the zip file. - - Args: - dagger_client (Client): The dagger client. - directory (Path): The directory to compress. - directory_name (str): The name of the directory. - - Returns: - File: The File object representing the zip file. - """ - return ( - dagger_client.container() - .from_("alpine:3.19.1") - .with_exec(sh_dash_c(["apk update", "apk add zip"])) - .with_mounted_directory(f"/{directory_name}", directory) - .with_exec(["zip", "-r", "/zipped.zip", f"/{directory_name}"]) - .file("/zipped.zip") - ) - - -async def raise_if_not_user(container: Container, expected_user: str) -> None: - """Raise an error if the container is not running as the specified user. - - Args: - container (Container): The container to check. - expected_user (str): The expected user. - """ - actual_user = (await container.with_exec(["whoami"]).stdout()).strip() - - assert ( - actual_user == expected_user - ), f"Container is not running as the expected user '{expected_user}', it is running as '{actual_user}'." - - -def deprecated(reason: str) -> Callable: - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: - warnings.warn(f"{func.__name__} is deprecated: {reason}", DeprecationWarning, stacklevel=2) - return func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/models/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/artifacts.py b/airbyte-ci/connectors/pipelines/pipelines/models/artifacts.py deleted file mode 100644 index 295faf9acbcb..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/artifacts.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -from dataclasses import dataclass -from pathlib import Path -from typing import Optional - -import dagger - -from pipelines.consts import GCS_PUBLIC_DOMAIN -from pipelines.dagger.actions import remote_storage -from pipelines.models.secrets import Secret - - -@dataclass(kw_only=True) -class Artifact: - """A dataclass to represent an artifact produced by a pipeline execution.""" - - name: str - content_type: str - content: dagger.File - to_upload: bool = True - local_path: Optional[Path] = None - gcs_url: Optional[str] = None - - async def save_to_local_path(self, path: Path) -> Path: - exported = await self.content.export(str(path)) - if exported: - self.local_path = path - return path - else: - raise Exception(f"Failed to save artifact {self.name} to local path {path}") - - async def upload_to_gcs(self, dagger_client: dagger.Client, bucket: str, key: str, gcs_credentials: Secret) -> str: - gcs_cp_flags = [f'--content-disposition=filename="{self.name}"'] - if self.content_type is not None: - gcs_cp_flags = gcs_cp_flags + [f"--content-type={self.content_type}"] - - report_upload_exit_code, _, _ = await remote_storage.upload_to_gcs( - dagger_client=dagger_client, - file_to_upload=self.content, - key=key, - bucket=bucket, - gcs_credentials=gcs_credentials, - flags=gcs_cp_flags, - ) - if report_upload_exit_code != 0: - raise Exception(f"Failed to upload artifact {self.name} to GCS. Exit code: {report_upload_exit_code}.") - self.gcs_url = f"{GCS_PUBLIC_DOMAIN}/{bucket}/{key}" - return f"{GCS_PUBLIC_DOMAIN}/{bucket}/{key}" diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/ci_requirements.py b/airbyte-ci/connectors/pipelines/pipelines/models/ci_requirements.py deleted file mode 100644 index 8da589d2fffa..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/ci_requirements.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. - -import json -from dataclasses import dataclass -from importlib import metadata - - -@dataclass -class CIRequirements: - """ - A dataclass to store the CI requirements. - It used to make airbyte-ci client define the CI runners it will run on. - """ - - dagger_version = metadata.version("dagger-io") - - @property - def dagger_engine_image(self) -> str: - return f"registry.dagger.io/engine:v{self.dagger_version}" - - def to_json(self) -> str: - return json.dumps( - { - "dagger_version": self.dagger_version, - "dagger_engine_image": self.dagger_engine_image, - } - ) diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/__init__.py b/airbyte-ci/connectors/pipelines/pipelines/models/contexts/__init__.py deleted file mode 100644 index c941b3045795..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/click_pipeline_context.py b/airbyte-ci/connectors/pipelines/pipelines/models/contexts/click_pipeline_context.py deleted file mode 100644 index 471b5840addb..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/click_pipeline_context.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -import io -import sys -import tempfile -from pathlib import Path -from typing import Any, Callable, Dict, Optional, TextIO, Tuple - -import anyio -import dagger -from asyncclick import Context, get_current_context -from pydantic import BaseModel, Field, PrivateAttr - -from pipelines import main_logger -from pipelines.cli.click_decorators import LazyPassDecorator - -from ..singleton import Singleton - - -class ClickPipelineContext(BaseModel, Singleton): - """ - A replacement class for the Click context object passed to click functions. - - This class is meant to serve as a singleton object that initializes and holds onto a single instance of the - Dagger client, which is used to create containers for running pipelines. - """ - - dockerd_service: Optional[dagger.Container] = Field(default=None) - _dagger_client: Optional[dagger.Client] = PrivateAttr(default=None) - _click_context: Callable[[], Context] = PrivateAttr(default_factory=lambda: get_current_context) - _og_click_context: Context = PrivateAttr(default=None) - - @property - def params(self) -> Dict[str, Any]: - """ - Returns a combination of the click context object and the click context params. - - This means that any arguments or options defined in the parent command will be available to the child command. - """ - ctx = self._click_context() - click_obj = ctx.obj - click_params = ctx.params - command_name = ctx.command.name - - # Error if click_obj and click_params have the same key, and not the same value - intersection = set(click_obj.keys()) & set(click_params.keys()) - if intersection: - for key in intersection: - if click_obj[key] != click_params[key]: - raise ValueError( - f"Your command '{command_name}' has defined options/arguments with the same key as its parent, but with different values: {intersection}" - ) - - return {**click_obj, **click_params} - - class Config: - arbitrary_types_allowed = True - - def __init__(self, **data: dict[str, Any]) -> None: - """ - Initialize the ClickPipelineContext instance. - - This method checks the _initialized flag for the ClickPipelineContext class in the Singleton base class. - If the flag is False, the initialization logic is executed and the flag is set to True. - If the flag is True, the initialization logic is skipped. - - This ensures that the initialization logic is only executed once, even if the ClickPipelineContext instance is retrieved multiple times. - This can be useful if the initialization logic is expensive (e.g., it involves network requests or database queries). - """ - if not Singleton._initialized[ClickPipelineContext]: - super().__init__(**data) - Singleton._initialized[ClickPipelineContext] = True - - """ - Note: Its important to hold onto the original click context object, as it is used to hold onto the Dagger client. - """ - self._og_click_context = self._click_context() - - _dagger_client_lock: anyio.Lock = PrivateAttr(default_factory=anyio.Lock) - - async def get_dagger_client(self) -> dagger.Client: - """ - Get (or initialize) the Dagger Client instance. - """ - if not self._dagger_client: - async with self._dagger_client_lock: - if not self._dagger_client: - connection = dagger.Connection(dagger.Config(log_output=self.get_log_output())) - """ - Sets up the '_dagger_client' attribute, intended for single-threaded use within connectors. - - Caution: - Avoid using this client across multiple thread pools, as it can lead to errors. - Cross-thread pool calls are generally considered an anti-pattern. - """ - self._dagger_client = await self._og_click_context.with_async_resource(connection) - - assert self._dagger_client, "Error initializing Dagger client" - return self._dagger_client - - def get_log_output(self) -> TextIO: - # This `show_dagger_logs` flag is likely going to be removed in the future. - # See https://github.com/airbytehq/airbyte/issues/33487 - if self.params.get("show_dagger_logs", False): - return sys.stdout - else: - log_output, self._click_context().obj["dagger_logs_path"] = self._create_dagger_client_log_file() - return log_output - - def _create_dagger_client_log_file(self) -> Tuple[TextIO, Path]: - """ - Create the dagger client log file. - """ - dagger_logs_file_descriptor, dagger_logs_temp_file_path = tempfile.mkstemp(dir="/tmp", prefix="dagger_client_", suffix=".log") - main_logger.info(f"Dagger client logs stored in {dagger_logs_temp_file_path}") - return io.TextIOWrapper(io.FileIO(dagger_logs_file_descriptor, "w+")), Path(dagger_logs_temp_file_path) - - -# Create @pass_pipeline_context decorator for use in click commands -pass_pipeline_context: LazyPassDecorator = LazyPassDecorator(ClickPipelineContext) diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/pipeline_context.py b/airbyte-ci/connectors/pipelines/pipelines/models/contexts/pipeline_context.py deleted file mode 100644 index 8b816c31334e..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/pipeline_context.py +++ /dev/null @@ -1,351 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""Module declaring context related classes.""" - -from __future__ import annotations - -import logging -import os -from datetime import datetime -from functools import lru_cache -from glob import glob -from types import TracebackType -from typing import TYPE_CHECKING, Dict - -from asyncer import asyncify -from dagger import Client, Directory, File, GitRepository, Service -from dagger import Secret as DaggerSecret -from github import PullRequest - -from pipelines.airbyte_ci.connectors.reports import ConnectorReport -from pipelines.consts import MANUAL_PIPELINE_STATUS_CHECK_OVERRIDE_PREFIXES, CIContext, ContextState -from pipelines.helpers.execution.run_steps import RunStepOptions -from pipelines.helpers.github import AIRBYTE_GITHUB_REPO_URL, update_commit_status_check -from pipelines.helpers.slack import send_message_to_webhook -from pipelines.helpers.utils import java_log_scrub_pattern -from pipelines.models.reports import Report -from pipelines.models.secrets import Secret, SecretStore - -if TYPE_CHECKING: - from typing import List, Optional - - -class PipelineContext: - """The pipeline context is used to store configuration for a specific pipeline run.""" - - _dagger_client: Optional[Client] - _report: Optional[Report | ConnectorReport] - dockerd_service: Optional[Service] - started_at: Optional[datetime] - stopped_at: Optional[datetime] - - secrets_to_mask: List[str] - - PRODUCTION = bool(os.environ.get("PRODUCTION", False)) # Set this to True to enable production mode (e.g. to send PR comments) - - @lru_cache - def get_default_excluded_files(self) -> list[str]: - return ( - [".git", "airbyte-ci/connectors/pipelines/*"] - + glob("**/build", recursive=True) - + glob("**/.venv", recursive=True) - + glob("**/secrets", recursive=True) - + glob("**/__pycache__", recursive=True) - + glob("**/*.egg-info", recursive=True) - + glob("**/.vscode", recursive=True) - + glob("**/.pytest_cache", recursive=True) - + glob("**/.eggs", recursive=True) - + glob("**/.mypy_cache", recursive=True) - + glob("**/.DS_Store", recursive=True) - + glob("**/airbyte_ci_logs", recursive=True) - + glob("**/.gradle", recursive=True) - ) - - def __init__( - self, - pipeline_name: str, - is_local: bool, - git_branch: str, - git_revision: str, - diffed_branch: str, - git_repo_url: str, - report_output_prefix: str, - gha_workflow_run_url: Optional[str] = None, - dagger_logs_url: Optional[str] = None, - pipeline_start_timestamp: Optional[int] = None, - ci_context: Optional[str] = None, - is_ci_optional: bool = False, - slack_webhook: Optional[str] = None, - pull_request: Optional[PullRequest.PullRequest] = None, - ci_report_bucket: Optional[str] = None, - ci_gcp_credentials: Optional[Secret] = None, - ci_git_user: Optional[str] = None, - ci_github_access_token: Optional[Secret] = None, - run_step_options: RunStepOptions = RunStepOptions(), - enable_report_auto_open: bool = True, - secret_stores: Dict[str, SecretStore] | None = None, - ) -> None: - """Initialize a pipeline context. - - Args: - pipeline_name (str): The pipeline name. - is_local (bool): Whether the context is for a local run or a CI run. - git_branch (str): The current git branch name. - git_revision (str): The current git revision, commit hash. - diffed_branch (str): The branch to diff against. - git_repo_url (str): The git repository URL. - report_output_prefix (str): The prefix to use for the report output. - gha_workflow_run_url (Optional[str], optional): URL to the github action workflow run. Only valid for CI run. Defaults to None. - dagger_logs_url (Optional[str], optional): URL to the dagger logs. Only valid for CI run. Defaults to None. - pipeline_start_timestamp (Optional[int], optional): Timestamp at which the pipeline started. Defaults to None. - ci_context (Optional[str], optional): Pull requests, workflow dispatch or nightly build. Defaults to None. - is_ci_optional (bool, optional): Whether the CI is optional. Defaults to False. - slack_webhook (Optional[str], optional): Slack webhook to send messages to. Defaults to None. - pull_request (PullRequest, optional): The pull request object if the pipeline was triggered by a pull request. Defaults to None. - """ - self.pipeline_name = pipeline_name - self.is_local = is_local - self.git_branch = git_branch - self.git_revision = git_revision - self.diffed_branch = diffed_branch - self.git_repo_url = git_repo_url - self.report_output_prefix = report_output_prefix - self.gha_workflow_run_url = gha_workflow_run_url - self.dagger_logs_url = dagger_logs_url - self.pipeline_start_timestamp = pipeline_start_timestamp - self.created_at = datetime.utcnow() - self.ci_context = ci_context - self.state = ContextState.INITIALIZED - self.is_ci_optional = is_ci_optional - self.slack_webhook = slack_webhook - self.pull_request = pull_request - self.logger = logging.getLogger(self.pipeline_name) - self._dagger_client = None - self._report = None - self.dockerd_service = None - self.ci_gcp_credentials = ci_gcp_credentials - self.ci_report_bucket = ci_report_bucket - self.ci_git_user = ci_git_user - self.ci_github_access_token = ci_github_access_token - self.started_at = None - self.stopped_at = None - self.secrets_to_mask = [] - self.run_step_options = run_step_options - self.enable_report_auto_open = enable_report_auto_open - self.secret_stores = secret_stores if secret_stores else {} - update_commit_status_check(**self.github_commit_status) - - @property - def dagger_client(self) -> Client: - assert self._dagger_client is not None, "The dagger client was not set on this PipelineContext" - return self._dagger_client - - @dagger_client.setter - def dagger_client(self, dagger_client: Client) -> None: - self._dagger_client = dagger_client - - @property - def is_ci(self) -> bool: - return self.is_local is False - - @property - def is_pr(self) -> bool: - return self.ci_context == CIContext.PULL_REQUEST - - @property - def repo(self) -> GitRepository: - return self.dagger_client.git(AIRBYTE_GITHUB_REPO_URL, keep_git_dir=True) - - @property - def report(self) -> Report | ConnectorReport | None: - return self._report - - @report.setter - def report(self, report: Report | ConnectorReport) -> None: - self._report = report - - @property - def java_log_scrub_pattern_secret(self) -> Optional[DaggerSecret]: - if not self.secrets_to_mask: - return None - return self.dagger_client.set_secret("log_scrub_pattern", java_log_scrub_pattern(self.secrets_to_mask)) - - @property - def github_commit_status(self) -> dict: - """Build a dictionary used as kwargs to the update_commit_status_check function.""" - target_url: Optional[str] = self.gha_workflow_run_url - - if ( - self.remote_storage_enabled - and self.state not in [ContextState.RUNNING, ContextState.INITIALIZED] - and isinstance(self.report, ConnectorReport) - ): - target_url = self.report.html_report_url - - return { - "sha": self.git_revision, - "state": self.state.value["github_state"], - "target_url": target_url, - "description": self.state.value["description"], - "context": self.pipeline_name, - "should_send": self._should_send_status_check(), - "logger": self.logger, - "is_optional": self.is_ci_optional, - } - - @property - def should_send_slack_message(self) -> bool: - return self.slack_webhook is not None - - @property - def has_dagger_cloud_token(self) -> bool: - return "_EXPERIMENTAL_DAGGER_CLOUD_TOKEN" in os.environ - - @property - def dagger_cloud_url(self) -> Optional[str]: - """Gets the link to the Dagger Cloud runs page for the current commit.""" - if self.is_local or not self.has_dagger_cloud_token: - return None - - return f"https://alpha.dagger.cloud/changeByPipelines?filter=dagger.io/git.ref:{self.git_revision}" - - @property - def remote_storage_enabled(self) -> bool: - return bool(self.ci_report_bucket) and bool(self.ci_gcp_credentials) - - def _should_send_status_check(self) -> bool: - should_send = self.is_pr or any( - self.pipeline_name.startswith(override) for override in MANUAL_PIPELINE_STATUS_CHECK_OVERRIDE_PREFIXES - ) - self.logger.info(f"Should send status check: {should_send}") - return should_send - - def get_repo_file(self, file_path: str) -> File: - """Get a file from the current repository. - - The file is extracted from the host file system. - - Args: - file_path (str): Path to the file to get. - - Returns: - Path: The selected repo file. - """ - return self.dagger_client.host().file(file_path) - - def get_repo_dir(self, subdir: str = ".", exclude: Optional[List[str]] = None, include: Optional[List[str]] = None) -> Directory: - """Get a directory from the current repository. - - The directory is extracted from the host file system. - A couple of files or directories that could corrupt builds are exclude by default (check DEFAULT_EXCLUDED_FILES). - - Args: - subdir (str, optional): Path to the subdirectory to get. Defaults to "." to get the full repository. - exclude ([List[str], optional): List of files or directories to exclude from the directory. Defaults to None. - include ([List[str], optional): List of files or directories to include in the directory. Defaults to None. - - Returns: - Directory: The selected repo directory. - """ - - if exclude is None: - exclude = self.get_default_excluded_files() - else: - exclude += self.get_default_excluded_files() - exclude = list(set(exclude)) - exclude.sort() # sort to make sure the order is always the same to not burst the cache. Casting exclude to set can change the order - if subdir != ".": - subdir = f"{subdir}/" if not subdir.endswith("/") else subdir - exclude = [f.replace(subdir, "") for f in exclude if subdir in f] - return self.dagger_client.host().directory(subdir, exclude=exclude, include=include) - - def create_slack_message(self) -> str: - raise NotImplementedError() - - def get_slack_channels(self) -> List[str]: - raise NotImplementedError() - - async def __aenter__(self) -> PipelineContext: - """Perform setup operation for the PipelineContext. - - Updates the current commit status on Github. - - Raises: - Exception: An error is raised when the context was not initialized with a Dagger client - Returns: - PipelineContext: A running instance of the PipelineContext. - """ - if self.dagger_client is None: - raise Exception("A Pipeline can't be entered with an undefined dagger_client") - self.state = ContextState.RUNNING - self.started_at = datetime.utcnow() - self.logger.info("Caching the latest CDK version...") - await asyncify(update_commit_status_check)(**self.github_commit_status) - if self.should_send_slack_message: - # Using a type ignore here because the should_send_slack_message property is checking for non nullity of the slack_webhook - await asyncify(send_message_to_webhook)(self.create_slack_message(), self.get_slack_channels(), self.slack_webhook) # type: ignore - return self - - @staticmethod - def determine_final_state(report: Optional[Report], exception_value: Optional[BaseException]) -> ContextState: - """Determine the final state of the context from the report or the exception value. - - Args: - report (Optional[Report]): The pipeline report if any. - exception_value (Optional[BaseException]): The exception value if an exception was raised in the context execution, None otherwise. - Returns: - ContextState: The final state of the context. - """ - if exception_value is not None or report is None: - return ContextState.ERROR - if report is not None and report.considered_failed_steps: - return ContextState.FAILURE - if report is not None and report.success: - return ContextState.SUCCESSFUL - raise Exception( - f"The final state of the context could not be determined for the report and exception value provided. Report: {report}, Exception: {exception_value}" - ) - - async def __aexit__( - self, exception_type: Optional[type[BaseException]], exception_value: Optional[BaseException], traceback: Optional[TracebackType] - ) -> bool: - """Perform teardown operation for the PipelineContext. - - On the context exit the following operations will happen: - - Log the error value if an error was handled. - - Log the test report. - - Update the commit status check on GitHub if running in a CI environment. - - It should gracefully handle all the execution errors that happened and always upload a test report and update commit status check. - - Args: - exception_type (Optional[type[BaseException]]): The exception type if an exception was raised in the context execution, None otherwise. - exception_value (Optional[BaseException]): The exception value if an exception was raised in the context execution, None otherwise. - traceback (Optional[TracebackType]): The traceback if an exception was raised in the context execution, None otherwise. - Returns: - bool: Whether the teardown operation ran successfully. - """ - if exception_value: - self.logger.error("An error was handled by the Pipeline", exc_info=True) - - if self.report is None: - self.logger.error("No test report was provided. This is probably due to an upstream error") - self.report = Report(self, steps_results=[]) - - self.state = self.determine_final_state(self.report, exception_value) - self.stopped_at = datetime.utcnow() - - self.report.print() - - await asyncify(update_commit_status_check)(**self.github_commit_status) - if self.should_send_slack_message: - # Using a type ignore here because the should_send_slack_message property is checking for non nullity of the slack_webhook - await asyncify(send_message_to_webhook)( - self.create_slack_message(), - self.get_slack_channels(), - self.slack_webhook, # type: ignore - ) - # supress the exception if it was handled - return True diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/python_registry_publish.py b/airbyte-ci/connectors/pipelines/pipelines/models/contexts/python_registry_publish.py deleted file mode 100644 index 9e8a75371b13..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/contexts/python_registry_publish.py +++ /dev/null @@ -1,116 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -from dataclasses import dataclass -from datetime import datetime -from typing import Optional, Type - -from pipelines.airbyte_ci.connectors.context import PipelineContext -from pipelines.airbyte_ci.connectors.publish.context import PublishConnectorContext -from pipelines.consts import DEFAULT_PYTHON_PACKAGE_REGISTRY_URL -from pipelines.models.secrets import Secret - - -@dataclass -class PythonPackageMetadata: - name: Optional[str] - version: Optional[str] - - -class PythonRegistryPublishContext(PipelineContext): - def __init__( - self, - python_registry_token: Secret, - registry_check_url: str, - package_path: str, - report_output_prefix: str, - is_local: bool, - git_branch: str, - git_revision: str, - diffed_branch: str, - git_repo_url: str, - ci_report_bucket: Optional[str] = None, - registry: str = DEFAULT_PYTHON_PACKAGE_REGISTRY_URL, - gha_workflow_run_url: Optional[str] = None, - dagger_logs_url: Optional[str] = None, - pipeline_start_timestamp: Optional[int] = None, - ci_context: Optional[str] = None, - ci_gcp_credentials: Optional[Secret] = None, - package_name: Optional[str] = None, - version: Optional[str] = None, - ) -> None: - self.python_registry_token = python_registry_token - self.registry = registry - self.registry_check_url = registry_check_url - self.package_path = package_path - self.package_metadata = PythonPackageMetadata(package_name, version) - - pipeline_name = f"Publish PyPI {package_path}" - - super().__init__( - pipeline_name=pipeline_name, - report_output_prefix=report_output_prefix, - ci_report_bucket=ci_report_bucket, - is_local=is_local, - git_branch=git_branch, - git_revision=git_revision, - diffed_branch=diffed_branch, - git_repo_url=git_repo_url, - gha_workflow_run_url=gha_workflow_run_url, - dagger_logs_url=dagger_logs_url, - pipeline_start_timestamp=pipeline_start_timestamp, - ci_context=ci_context, - ci_gcp_credentials=ci_gcp_credentials, - ) - - @classmethod - async def from_publish_connector_context( - cls: Type["PythonRegistryPublishContext"], connector_context: PublishConnectorContext - ) -> Optional["PythonRegistryPublishContext"]: - """ - Create a PythonRegistryPublishContext from a ConnectorContext. - - The metadata of the connector is read from the current workdir to capture changes that are not yet published. - If pypi is not enabled, this will return None. - """ - - current_metadata = connector_context.connector.metadata - connector_context.logger.info(f"Current metadata: {str(current_metadata)}") - if ( - "remoteRegistries" not in current_metadata - or "pypi" not in current_metadata["remoteRegistries"] - or not current_metadata["remoteRegistries"]["pypi"]["enabled"] - ): - return None - - version = current_metadata["dockerImageTag"] - if connector_context.pre_release: - # use current date as pre-release version - # we can't use the git revision because not all python registries allow local version identifiers. Public version identifiers must conform to PEP 440 and only allow digits. - release_candidate_tag = datetime.now().strftime("%Y%m%d%H%M") - version = f"{version}.dev{release_candidate_tag}" - - assert connector_context.python_registry_token is not None, "The connector context must have python_registry_token Secret attribute" - pypi_context = cls( - python_registry_token=connector_context.python_registry_token, - registry=str(connector_context.python_registry_url), - registry_check_url=str(connector_context.python_registry_check_url), - package_path=str(connector_context.connector.code_directory), - package_name=current_metadata["remoteRegistries"]["pypi"]["packageName"], - version=version, - ci_report_bucket=connector_context.ci_report_bucket, - report_output_prefix=connector_context.report_output_prefix, - is_local=connector_context.is_local, - git_branch=connector_context.git_branch, - git_revision=connector_context.git_revision, - diffed_branch=connector_context.diffed_branch, - git_repo_url=connector_context.git_repo_url, - gha_workflow_run_url=connector_context.gha_workflow_run_url, - dagger_logs_url=connector_context.dagger_logs_url, - pipeline_start_timestamp=connector_context.pipeline_start_timestamp, - ci_context=connector_context.ci_context, - ci_gcp_credentials=connector_context.ci_gcp_credentials, - ) - pypi_context.dagger_client = connector_context.dagger_client - return pypi_context diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/reports.py b/airbyte-ci/connectors/pipelines/pipelines/models/reports.py deleted file mode 100644 index 408b5ecded6b..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/reports.py +++ /dev/null @@ -1,212 +0,0 @@ -# -# Copyright (c) 2023 Airbyte, Inc., all rights reserved. -# - -"""This module declare base / abstract models to be reused in a pipeline lifecycle.""" - -from __future__ import annotations - -import json -import time -import typing -from dataclasses import dataclass, field -from datetime import datetime, timedelta -from pathlib import Path -from typing import List - -from connector_ops.utils import console # type: ignore -from rich.console import Group -from rich.panel import Panel -from rich.style import Style -from rich.table import Table -from rich.text import Text - -from pipelines.consts import LOCAL_REPORTS_PATH_ROOT -from pipelines.helpers.utils import format_duration, slugify -from pipelines.models.artifacts import Artifact -from pipelines.models.steps import StepResult, StepStatus - -if typing.TYPE_CHECKING: - from rich.tree import RenderableType - - from pipelines.models.contexts.pipeline_context import PipelineContext - - -@dataclass(frozen=True) -class Report: - """A dataclass to build reports to share pipelines executions results with the user.""" - - pipeline_context: PipelineContext - steps_results: List[StepResult] - created_at: datetime = field(default_factory=datetime.utcnow) - name: str = "REPORT" - filename: str = "output" - - @property - def report_output_prefix(self) -> str: - return self.pipeline_context.report_output_prefix - - @property - def report_dir_path(self) -> Path: - return Path(f"{LOCAL_REPORTS_PATH_ROOT}/{self.report_output_prefix}") - - @property - def json_report_file_name(self) -> str: - return self.filename + ".json" - - @property - def json_report_remote_storage_key(self) -> str: - return f"{self.report_output_prefix}/{self.json_report_file_name}" - - @property - def failed_steps(self) -> List[StepResult]: - return [step_result for step_result in self.steps_results if step_result.status is StepStatus.FAILURE] - - @property - def considered_failed_steps(self) -> List[StepResult]: - return [step_result for step_result in self.failed_steps if step_result.consider_in_overall_status] - - @property - def successful_steps(self) -> List[StepResult]: - return [step_result for step_result in self.steps_results if step_result.status is StepStatus.SUCCESS] - - @property - def skipped_steps(self) -> List[StepResult]: - return [step_result for step_result in self.steps_results if step_result.status is StepStatus.SKIPPED] - - @property - def success(self) -> bool: - return len(self.considered_failed_steps) == 0 and (len(self.skipped_steps) > 0 or len(self.successful_steps) > 0) - - @property - def run_duration(self) -> timedelta: - assert self.pipeline_context.started_at is not None, "The pipeline started_at timestamp must be set to save reports." - assert self.pipeline_context.stopped_at is not None, "The pipeline stopped_at timestamp must be set to save reports." - return self.pipeline_context.stopped_at - self.pipeline_context.started_at - - @property - def lead_duration(self) -> timedelta: - assert self.pipeline_context.started_at is not None, "The pipeline started_at timestamp must be set to save reports." - assert self.pipeline_context.stopped_at is not None, "The pipeline stopped_at timestamp must be set to save reports." - return self.pipeline_context.stopped_at - self.pipeline_context.created_at - - async def save(self) -> None: - self.report_dir_path.mkdir(parents=True, exist_ok=True) - await self.save_json_report() - await self.save_step_result_artifacts() - - async def save_json_report(self) -> None: - """Save the report as JSON, upload it to GCS if the pipeline is running in CI""" - - json_report_path = self.report_dir_path / self.json_report_file_name - report_dir = self.pipeline_context.dagger_client.host().directory(str(self.report_dir_path)) - local_json_report_file = report_dir.with_new_file(self.json_report_file_name, self.to_json()).file(self.json_report_file_name) - json_report_artifact = Artifact(name="JSON Report", content_type="application/json", content=local_json_report_file) - await json_report_artifact.save_to_local_path(json_report_path) - absolute_path = json_report_path.absolute() - self.pipeline_context.logger.info(f"Report saved locally at {absolute_path}") - if self.pipeline_context.remote_storage_enabled: - gcs_url = await json_report_artifact.upload_to_gcs( - dagger_client=self.pipeline_context.dagger_client, - bucket=self.pipeline_context.ci_report_bucket, # type: ignore - key=self.json_report_remote_storage_key, - gcs_credentials=self.pipeline_context.ci_gcp_credentials, # type: ignore - ) - self.pipeline_context.logger.info(f"JSON Report uploaded to {gcs_url}") - else: - self.pipeline_context.logger.info("JSON Report not uploaded to GCS because remote storage is disabled.") - - async def save_step_result_artifacts(self) -> None: - local_artifacts_dir = self.report_dir_path / "artifacts" - local_artifacts_dir.mkdir(parents=True, exist_ok=True) - # TODO: concurrent save and upload - for step_result in self.steps_results: - for artifact in step_result.artifacts: - step_artifacts_dir = local_artifacts_dir / slugify(step_result.step.title) - step_artifacts_dir.mkdir(parents=True, exist_ok=True) - await artifact.save_to_local_path(step_artifacts_dir / artifact.name) - if self.pipeline_context.remote_storage_enabled: - upload_time = int(time.time()) - gcs_url = await artifact.upload_to_gcs( - dagger_client=self.pipeline_context.dagger_client, - bucket=self.pipeline_context.ci_report_bucket, # type: ignore - key=f"{self.report_output_prefix}/artifacts/{slugify(step_result.step.title)}/{upload_time}_{artifact.name}", - gcs_credentials=self.pipeline_context.ci_gcp_credentials, # type: ignore - ) - self.pipeline_context.logger.info(f"Artifact {artifact.name} for {step_result.step.title} uploaded to {gcs_url}") - else: - self.pipeline_context.logger.info( - f"Artifact {artifact.name} for {step_result.step.title} not uploaded to GCS because remote storage is disabled." - ) - - def to_json(self) -> str: - """Create a JSON representation of the report. - - Returns: - str: The JSON representation of the report. - """ - assert self.pipeline_context.pipeline_start_timestamp is not None, "The pipeline start timestamp must be set to save reports." - assert self.pipeline_context.started_at is not None, "The pipeline started_at timestamp must be set to save reports." - assert self.pipeline_context.stopped_at is not None, "The pipeline stopped_at timestamp must be set to save reports." - return json.dumps( - { - "pipeline_name": self.pipeline_context.pipeline_name, - "run_timestamp": self.pipeline_context.started_at.isoformat(), - "run_duration": self.run_duration.total_seconds(), - "success": self.success, - "failed_steps": [s.step.__class__.__name__ for s in self.failed_steps], - "successful_steps": [s.step.__class__.__name__ for s in self.successful_steps], - "skipped_steps": [s.step.__class__.__name__ for s in self.skipped_steps], - "gha_workflow_run_url": self.pipeline_context.gha_workflow_run_url, - "pipeline_start_timestamp": self.pipeline_context.pipeline_start_timestamp, - "pipeline_end_timestamp": round(self.pipeline_context.stopped_at.timestamp()), - "pipeline_duration": round(self.pipeline_context.stopped_at.timestamp()) - self.pipeline_context.pipeline_start_timestamp, - "git_branch": self.pipeline_context.git_branch, - "git_revision": self.pipeline_context.git_revision, - "ci_context": self.pipeline_context.ci_context, - "pull_request_url": self.pipeline_context.pull_request.html_url if self.pipeline_context.pull_request else None, - "dagger_cloud_url": self.pipeline_context.dagger_cloud_url, - } - ) - - def print(self) -> None: - """Print the test report to the console in a nice way.""" - pipeline_name = self.pipeline_context.pipeline_name - main_panel_title = Text(f"{pipeline_name.upper()} - {self.name}") - main_panel_title.stylize(Style(color="blue", bold=True)) - duration_subtitle = Text(f"⏲️ Total pipeline duration for {pipeline_name}: {format_duration(self.run_duration)}") - step_results_table = Table(title="Steps results") - step_results_table.add_column("Step") - step_results_table.add_column("Result") - step_results_table.add_column("Finished after") - - for step_result in self.steps_results: - step = Text(step_result.step.title) - step.stylize(step_result.status.get_rich_style()) - result = Text(step_result.status.value) - result.stylize(step_result.status.get_rich_style()) - - if step_result.status is StepStatus.SKIPPED: - step_results_table.add_row(step, result, "N/A") - else: - assert step_result.step.started_at is not None, "The step started_at timestamp must be set to print reports." - run_time = format_duration((step_result.created_at - step_result.step.started_at)) - step_results_table.add_row(step, result, run_time) - - to_render: List[RenderableType] = [step_results_table] - if self.failed_steps: - sub_panels = [] - for failed_step in self.failed_steps: - errors = Text(failed_step.stderr) if failed_step.stderr else Text("") - panel_title = Text(f"{pipeline_name} {failed_step.step.title.lower()} failures") - panel_title.stylize(Style(color="red", bold=True)) - sub_panel = Panel(errors, title=panel_title) - sub_panels.append(sub_panel) - failures_group = Group(*sub_panels) - to_render.append(failures_group) - - if self.pipeline_context.dagger_cloud_url: - self.pipeline_context.logger.info(f"🔗 View runs for commit in Dagger Cloud: {self.pipeline_context.dagger_cloud_url}") - - main_panel = Panel(Group(*to_render), title=main_panel_title, subtitle=duration_subtitle) - console.print(main_panel) diff --git a/airbyte-ci/connectors/pipelines/pipelines/models/secrets.py b/airbyte-ci/connectors/pipelines/pipelines/models/secrets.py deleted file mode 100644 index a8a0aa7eb120..000000000000 --- a/airbyte-ci/connectors/pipelines/pipelines/models/secrets.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright (c) 2024 Airbyte, Inc., all rights reserved. - -from __future__ import annotations - -import hashlib -import json -import os -from abc import ABC, abstractmethod -from dataclasses import dataclass -from pathlib import Path -from typing import Dict, List - -from dagger import Client as DaggerClient -from dagger import Secret as DaggerSecret -from google.cloud import secretmanager_v1 # type: ignore -from google.oauth2 import service_account # type: ignore - - -class SecretNotFoundError(Exception): - pass - - -class SecretString(str): - """The use of this string subtype will prevent accidental prints of secret value to the console.""" - - @property - def _masked_value(self) -> str: - return "