diff --git a/.bandit.yaml b/.bandit.yaml new file mode 100644 index 0000000..1bf9b48 --- /dev/null +++ b/.bandit.yaml @@ -0,0 +1,2 @@ +skips: + - B101 # Use of assert detected. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7f3323e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +max_line_length = 110 +quote_type = single + +[*.{yaml,json,json5,graphql,js,md,whitesource}] +indent_size = 2 + +[{Makefile,*.mk}] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..be981bb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text whitespace=trailing-space,tab-in-indent,cr-at-eol,tabwidth=4 eol=lf +Makefile text whitespace=indent-with-non-tab,tabwidth=2 +*.mk text whitespace=indent-with-non-tab,tabwidth=2 +*.rst text conflict-marker-size=100 diff --git a/.github/publish.yaml b/.github/publish.yaml new file mode 100644 index 0000000..5e5b883 --- /dev/null +++ b/.github/publish.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/camptocamp/tag-publish/master/tag_publish/schema.json + +pypi: + versions: + - version_tag + - version_branch + - rebuild diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..1641bdd --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,98 @@ +{ + extends: ['config:base'], + timezone: 'Europe/Zurich', + schedule: 'after 5pm on the first day of the month', + labels: ['dependencies'], + separateMajorMinor: true, + separateMinorPatch: true, + prHourlyLimit: 0, + prConcurrentLimit: 0, + baseBranches: ['master'], + 'pre-commit': { enabled: true }, + lockFileMaintenance: { + enabled: true, + automerge: true, + schedule: 'after 5pm on the first day of the month', + }, + regexManagers: [ + /** Do updates on pre-commit additional dependencies */ + { + fileMatch: ['^\\.pre\\-commit\\-config\\.yaml$'], + matchStrings: [" +- '?(?[^' @=]+)(@|==)(?[^' @=]+)'? # (?.+)"], + }, + /** Do update on the schema present in the ci/config.yaml */ + { + fileMatch: ['^ci/config\\.yaml$'], + matchStrings: [ + '.*https://raw\\.githubusercontent\\.com/(?[^\\s]+)/(?[0-9\\.]+)/.*', + ], + datasourceTemplate: 'github-tags', + }, + /** Python version in actions/setup-python action */ + { + fileMatch: ['^\\.github/workflows/.*\\.yaml$'], + matchStrings: [' python-version: [\'"](?[0-9\\.]+)[\'"]'], + datasourceTemplate: 'python-version', + depNameTemplate: 'python', + }, + ], + packageRules: [ + /** Automerge the patch, the minor and the dev dependency */ + { + matchBaseBranches: ['master'], + matchUpdateTypes: ['minor', 'patch'], + automerge: true, + }, + /** Auto merge the dev dependency update */ + { + matchDepTypes: ['devDependencies'], + automerge: true, + }, + /** Group and auto merge the patch updates */ + { + matchUpdateTypes: ['patch'], + groupName: 'all patch versions', + automerge: true, + }, + /** Group and auto merge the minor updates */ + { + matchUpdateTypes: ['minor'], + groupName: 'all minor versions', + automerge: true, + }, + /** Group Poetry packages */ + { + matchPackagePrefixes: ['poetry'], + groupName: 'CI/build dependencies', + automerge: true, + }, + /** Group and auto merge the CI dependencies */ + { + matchFileNames: ['.github/**', '.pre-commit-config.yaml', 'ci/**'], + groupName: 'CI/build dependencies', + automerge: true, + }, + /** Accept only the patch on stabilization branches */ + { + matchBaseBranches: ['/^[0-9]+\\.[0-9]+$/'], + matchUpdateTypes: ['major', 'minor', 'pin', 'digest', 'lockFileMaintenance', 'rollback', 'bump'], + enabled: false, + }, + /** Support the 4 parts of shellcheck-py version with a v prefix */ + { + versioning: 'regex:^v(?\\d+)\\.(?\\d+)\\.(?\\d+)\\.(?\\d+)$', + matchDepNames: ['shellcheck-py/shellcheck-py'], + }, + /** Disable upgrading the supported Python version */ + { + matchFileNames: ['pyproject.toml'], + enabled: false, + matchDepNames: ['python'], + }, + /** Packages published very recently are not pushed to stabilization branches for security reasons */ + { + matchBaseBranches: ['/^[0-9]+\\.[0-9]+$/'], + minimumReleaseAge: '7 days', + }, + ], +} diff --git a/.github/spell-ignore-words.txt b/.github/spell-ignore-words.txt new file mode 100644 index 0000000..78c2e21 --- /dev/null +++ b/.github/spell-ignore-words.txt @@ -0,0 +1,2 @@ +pypi +Snyk diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 0000000..0aa5c0d --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,22 @@ +name: Backport + +on: + pull_request: + types: + - closed + - labeled + +env: + HAS_SECRETS: ${{ secrets.HAS_SECRETS }} + +jobs: + backport: + name: Backport + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - uses: camptocamp/backport-action@master + with: + token: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }} + if: env.HAS_SECRETS == 'HAS_SECRETS' diff --git a/.github/workflows/clean.yaml b/.github/workflows/clean.yaml new file mode 100644 index 0000000..234a11e --- /dev/null +++ b/.github/workflows/clean.yaml @@ -0,0 +1,29 @@ +name: Clean docker hub tags + +on: + delete: + pull_request: + types: + - closed + +jobs: + clean: + name: Clean docker hub tags + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + + - uses: camptocamp/initialise-gopass-summon-action@v2 + with: + ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}} + github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}} + + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - run: python3 -m pip install c2cciutils + + - name: Clean Docker hub tags + run: c2cciutils-clean diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..63cd630 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,77 @@ +name: Continuous integration + +on: + push: + branches: + - master + - '[0-9]+.[0-9]+' + tags: + - '*' + pull_request: + +permissions: + packages: write + +env: + HAS_SECRETS: ${{ secrets.HAS_SECRETS }} + +jobs: + main: + name: Continuous integration + runs-on: ubuntu-24.04 + timeout-minutes: 20 + if: "!startsWith(github.event.head_commit.message, '[skip ci] ')" + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: camptocamp/initialise-gopass-summon-action@v2 + with: + ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}} + github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}} + patterns: pypi + if: env.HAS_SECRETS == 'HAS_SECRETS' + + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - run: python3 -m pip install --requirement=requirements.txt + + - uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: "pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}\npre-commit-" + - run: pre-commit run --all-files + - run: git diff --exit-code --patch > /tmp/pre-commit.patch || true + if: failure() + - uses: actions/upload-artifact@v4 + with: + name: Apply pre-commit fix.patch + path: /tmp/pre-commit.patch + retention-days: 1 + if: failure() + + - name: Print the environment + run: c2cciutils-env + env: + GITHUB_EVENT: ${{ toJson(github) }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - run: poetry install + - name: Prospector + run: poetry run prospector --die-on-tool-error --output-format=pylint + + - name: Publish + run: tag-publish + + - run: git diff --exit-code --patch > /tmp/dpkg-versions.patch || true + if: failure() + - uses: actions/upload-artifact@v4 + with: + name: Update dpkg versions list.patch + path: /tmp/dpkg-versions.patch + retention-days: 1 + if: failure() diff --git a/.github/workflows/pull-request-automation.yaml b/.github/workflows/pull-request-automation.yaml new file mode 100644 index 0000000..6ef14ff --- /dev/null +++ b/.github/workflows/pull-request-automation.yaml @@ -0,0 +1,51 @@ +name: Auto reviews, merge and close pull requests + +on: + pull_request: + types: + - opened + - reopened + +jobs: + auto-merge: + name: Auto reviews pull requests from bots + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - name: Print event + run: echo "${GITHUB}" | jq + env: + GITHUB: ${{ toJson(github) }} + - name: Print context + uses: actions/github-script@v7 + with: + script: |- + console.log(context); + - name: Auto reviews GHCI updates + uses: actions/github-script@v7 + with: + script: |- + github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: 'APPROVE', + }) + if: |- + startsWith(github.head_ref, 'ghci/audit/') + && (github.event.pull_request.user.login == 'geo-ghci-test[bot]' + || github.event.pull_request.user.login == 'geo-ghci-int[bot]' + || github.event.pull_request.user.login == 'geo-ghci[bot]') + - name: Auto reviews Renovate updates + uses: actions/github-script@v7 + with: + script: |- + github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: 'APPROVE', + }) + if: |- + github.event.pull_request.user.login == 'renovate[bot]' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1819d6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +/dist/ +/tag_publish/node_modules/ +/.mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b413ca9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,123 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.1.0 + hooks: + - id: prettier + additional_dependencies: + - prettier@2.8.4 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: detect-private-key + - id: check-merge-conflict + - id: check-ast + - id: debug-statements + - id: check-toml + - id: check-yaml + - id: check-json + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - repo: https://github.com/camptocamp/jsonschema-gentypes + rev: 2.8.1 + hooks: + - id: jsonschema-gentypes + files: |- + (?x)( + ^ĵsonschema-gentypes.yaml$ + |^tag_publish/schema.json$ + ) + - repo: https://github.com/sbrunner/jsonschema-validator + rev: 0.1.0 + hooks: + - id: jsonschema-validator + files: |- + (?x)^( + ci/config\.yaml + )$ + - repo: https://github.com/sbrunner/jsonschema2md2 + rev: 1.3.0 + hooks: + - id: jsonschema2md + files: tag_publish/schema.json + args: + - --pre-commit + - tag_publish/schema.json + - config.md + - repo: https://github.com/sbrunner/hooks + rev: 1.1.2 + hooks: + - id: copyright + - id: poetry-lock + additional_dependencies: + - poetry==1.8.4 # pypi + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + exclude: |- + (?x)( + (.*/)?poetry\.lock$ + ) + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: git-check + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.29.4 + hooks: + - id: check-github-actions + - id: check-github-workflows + - id: check-jsonschema + name: Check GitHub Workflows set timeout-minutes + files: ^\.github/workflows/[^/]+$ + types: + - yaml + args: + - --builtin-schema + - github-workflows-require-timeout + - repo: https://github.com/renovatebot/pre-commit-hooks + rev: 38.142.1 + hooks: + - id: renovate-config-validator + - repo: https://github.com/sirwart/ripsecrets + rev: v0.1.8 + hooks: + - id: ripsecrets + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.0 + hooks: + - id: ruff-format + - id: ruff + args: + - --fix + - --unsafe-fixes + - repo: https://github.com/PyCQA/prospector + rev: v1.12.1 + hooks: + - id: prospector + args: + - --tool=pydocstyle + - --die-on-tool-error + - --output-format=pylint + additional_dependencies: + - prospector-profile-duplicated==1.6.0 # pypi + - prospector-profile-utils==1.9.1 # pypi + - repo: https://github.com/mheap/json-schema-spell-checker + rev: main + hooks: + - id: json-schema-spell-checker + files: |- + (?x)^( + tag_publish/schema\.json + )$ + args: + - --fields=description + - --spelling=.github/spell-ignore-words.txt + - --ignore-numbers + - --ignore-acronyms + - --en-us diff --git a/.prospector.yaml b/.prospector.yaml new file mode 100644 index 0000000..4c5b7f2 --- /dev/null +++ b/.prospector.yaml @@ -0,0 +1,18 @@ +inherits: + - utils:base + - utils:no-design-checks + - utils:fix + - duplicated + +doc-warnings: true + +ignore-paths: + - tag_publish/configuration.py + +pylint: + disable: + - missing-module-docstring + +bandit: + options: + config: .bandit.yaml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9426bff --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2020-2024, Camptocamp SA +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d6b750 --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# Tag Publish + +## Publishing + +The main goals of Tag Publish offer the commands to publish the project, +see the [documentation](https://github.com/camptocamp/c2cciutils/wiki/Publishing). + +## New version + +To create a new minor version you just should run `tag-publish-new --version=`. + +This will create the stabilization branch and will create a new pull request to update +the `SECURITY.md` file and the Renovate configuration. + +This will also create the tags for the backport. + +You are welcome to run `tag-publish-new --help` to see what's it's done. + +Note that it didn't create a tag, you should do it manually. + +To create a patch version you should just create tag. + +## SECURITY.md + +The `SECURITY.md` file should contain the security policy of the repository, especially the end of +support dates. + +For compatibility with [`security.md`](https://github.com/sbrunner/security.md/) it should contain an array +with at least the columns `Version` and `Supported Until`. The `Version` column will contain the concerned +version. +The `Supported Until` will contain the date of end of support `dd/mm/yyyy`. +It can also contain the following sentences: + +- `Unsupported`: no longer supported => no audit, no rebuild. +- `Best effort`: the support is ended, it is still rebuilt and audited, but this can be stopped without any notice. +- `To be defined`: not yet released or the date will be set related of another project release date (like for GeoMapFish). + +See also [GitHub Documentation](https://docs.github.com/en/github/managing-security-vulnerabilities/adding-a-security-policy-to-your-repository) + +## Publishing + +### To pypi + +The config is like this: + +```yaml +versions: + # List of kinds of versions you want to publish, that can be: + # rebuild (specified with --type), + # version_tag, version_branch, feature_branch, feature_tag (for pull request) +``` + +It we have a `setup.py` file, we will be in legacy mode: +When publishing, the version computed from arguments or `GITHUB_REF` is put in environment variable `VERSION`, thus you should use it in `setup.py`, example: + +```python +VERSION = os.environ.get("VERSION", "1.0.0") +``` + +Also we consider that we use `poetry` with [poetry-dynamic-versioning](https://pypi.org/project/poetry-dynamic-versioning/) to manage the version, and [poetry-plugin-tweak-dependencies-version](https://pypi.org/project/poetry-plugin-tweak-dependencies-version/) to manage the dependencies versions. + +Example of configuration: + +```toml +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +pattern = "^(?P\\d+(\\.\\d+)*)" +format-jinja = """ +{%- if env.get("VERSION_TYPE") == "version_branch" -%} +{{serialize_pep440(bump_version(base, 1 if env.get("IS_MASTER") == "TRUE" else 2), dev=distance)}} +{%- elif distance == 0 -%} +{{serialize_pep440(base)}} +{%- else -%} +{{serialize_pep440(bump_version(base), dev=distance)}} +{%- endif -%} +""" + +``` + +Note that we can access to the environment variables `VERSION`,`VERSION_TYPE` and `IS_MASTER`. + +Then by default: + +- Tag with `1.2.3` => release `1.2.3` +- Commit on feature branch just do a validation +- Commit on `master` branch after the tag 1.3.0 => release `1.4.0.dev1` +- Commit on `1.3` branch after the tag 1.3.0 => release `1.3.1.dev1` + +#### Authentication + +If the file `~/.pypirc` exists we consider that we ar already logged in also +we will do the login with the `pypi` server with OpenID Connect (OIDC). + +The OIDC login is recommended because it didn't needs any additional secrets, +but it need some configuration on pypi in the package, +see the [GitHub Documentation](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-pypi#adding-the-identity-provider-to-pypi). + +#### Integration if the package directly in a Docker image + +To make it working in the `Dockerfile` you should have in the `poetry` stage: + +```Dockerfile +ENV POETRY_DYNAMIC_VERSIONING_BYPASS=dev +RUN poetry export --extras=checks --extras=publish --output=requirements.txt \ + && poetry export --with=dev --output=requirements-dev.txt +``` + +And in the `run` stage + +```Dockerfile +ARG VERSION=dev +RUN --mount=type=cache,target=/root/.cache \ + POETRY_DYNAMIC_VERSIONING_BYPASS=${VERSION} python3 -m pip install --disable-pip-version-check --no-deps --editable=. +``` + +And in the `Makefile`: + +```Makefile +VERSION = $(strip $(shell poetry version --short)) + +.PHONY: build +build: ## Build the Docker images + docker build --build-arg=VERSION=$(VERSION) --tag=$(GITHUB_REPOSITORY) . +``` + +### To Docker registry + +The config is like this: + +```yaml +latest: True +images: + - # The base name of the image we want to publish + name: +repository: + : + # The fqdn name of the server if not Docker hub + server: + # List of kinds of versions you want to publish, that can be: rebuild (specified using --type), + # version_tag, version_branch, feature_branch, feature_tag (for pull request) + version: + # List of tags we want to publish interpreted with `format(version=version)` + # e.g. if you use `{version}-lite` when you publish the version `1.2.3` the source tag + # (that should be built by the application build) is `latest-lite`, and it will be published + # with the tag `1.2.3-lite`. + tags: + # If your images are published by different jobs you can separate them in different groups + # and publish them with `tag-publish --group=` + group: +``` + +By default, the last line of the `SECURITY.md` file will be published (`docker`) with the tag +`latest`. Set `latest` to `False` to disable it. + +## Use Renovate to trigger a new build instead of the legacy rebuild + +If the `ci/dpkg-versions.yaml` or `.github/dpkg-versions.yaml` file is present, the package list will be updated on publishing. + +The versions will be updated by [GHCI](https://github.com/camptocamp/github-app-geo-project/) application. + +## Contributing + +Install the pre-commit hooks: + +```bash +pip install pre-commit +pre-commit install --allow-missing-config +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6f5ea73 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +## Supported Versions + +| Version | Supported Until | +| ------- | --------------- | +| 0.7 | Unsupported | diff --git a/config.md b/config.md new file mode 100644 index 0000000..810ef4e --- /dev/null +++ b/config.md @@ -0,0 +1,72 @@ +# configuration + +_Tag Publish configuration file_ + +## Properties + +- **`version`** _(object)_: The version configurations. + - **`branch_to_version_re`**: Refer to _[#/definitions/version_transform](#definitions/version_transform)_. + - **`tag_to_version_re`**: Refer to _[#/definitions/version_transform](#definitions/version_transform)_. +- **`docker`**: Refer to _[#/definitions/publish_docker](#definitions/publish_docker)_. +- **`pypi`**: Refer to _[#/definitions/publish_pypi](#definitions/publish_pypi)_. +- **`helm`**: Refer to _[#/definitions/publish_helm](#definitions/publish_helm)_. + +## Definitions + +- **`publish_docker`**: The configuration used to publish on Docker. + - **One of** + - _object_: The configuration used to publish on Docker. + - **`latest`** _(boolean)_: Publish the latest version on tag latest. Default: `true`. + - **`images`** _(array)_: List of images to be published. + - **Items** _(object)_ + - **`group`** _(string)_: The image is in the group, should be used with the --group option of tag-publish script. Default: `"default"`. + - **`name`** _(string)_: The image name. + - **`tags`** _(array)_: The tag name, will be formatted with the version=, the image with version=latest should be present when we call the tag-publish script. Default: `["{version}"]`. + - **Items** _(string)_ + - **`repository`** _(object)_: The repository where we should publish the images. Can contain additional properties. Default: `{"github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]}, "dockerhub": {}}`. + - **Additional properties** _(object)_ + - **`server`** _(string)_: The server URL. + - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. Default: `["version_tag", "version_branch", "rebuild", "feature_branch"]`. + - **Items** _(string)_ + - **`dispatch`**: Send a dispatch event to an other repository. Default: `{}`. + - **One of** + - _object_: Send a dispatch event to an other repository. + - **`repository`** _(string)_: The repository name to be triggered. Default: `"camptocamp/argocd-gs-gmf-apps"`. + - **`event-type`** _(string)_: The event type to be triggered. Default: `"image-update"`. + - : Must be: `false`. + - **`snyk`** _(object)_: Checks the published images with Snyk. + - **`monitor_args`**: The arguments to pass to the Snyk container monitor command. Default: `["--app-vulns"]`. + - **One of** + - _array_ + - **Items** _(string)_ + - : Must be: `false`. + - **`test_args`**: The arguments to pass to the Snyk container test command. Default: `["--app-vulns", "--severity-threshold=critical"]`. + - **One of** + - _array_ + - **Items** _(string)_ + - : Must be: `false`. + - : Must be: `false`. +- **`publish_pypi`**: Configuration to publish on pypi. Default: `{}`. + - **One of** + - _object_: Configuration to publish on pypi. + - **`packages`** _(array)_: The configuration of packages that will be published. + - **Items** _(object)_: The configuration of package that will be published. + - **`group`** _(string)_: The image is in the group, should be used with the --group option of tag-publish script. Default: `"default"`. + - **`path`** _(string)_: The path of the pypi package. + - **`build_command`** _(array)_: The command used to do the build. + - **Items** _(string)_ + - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. + - **Items** _(string)_ + - : Must be: `false`. +- **`publish_helm`**: Configuration to publish Helm charts on GitHub release. + - **One of** + - _object_: Configuration to publish on Helm charts on GitHub release. + - **`folders`** _(array)_: The folders that will be published. + - **Items** _(string)_ + - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. + - **Items** _(string)_ + - : Must be: `false`. +- **`version_transform`** _(array)_: A version transformer definition. + - **Items** _(object)_ + - **`from`** _(string)_: The from regular expression. + - **`to`** _(string)_: The expand regular expression: https://docs.python.org/3/library/re.html#re.Match.expand. diff --git a/jsonschema-gentypes.yaml b/jsonschema-gentypes.yaml new file mode 100644 index 0000000..8f744bd --- /dev/null +++ b/jsonschema-gentypes.yaml @@ -0,0 +1,19 @@ +headers: | + """ + Automatically generated file from a JSON schema. + """ + +pre_commit: + enabled: true + hooks_skip: + - jsonschema-gentypes + - shellcheck + arguments: + - --color=never + +generate: + - source: tag_publish/schema.json + destination: tag_publish/configuration.py + root_name: Configuration + api_arguments: + additional_properties: Only explicit diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..4c5a9c5 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1684 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +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 = "astroid" +version = "3.3.5" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.9.0" +files = [ + {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"}, + {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +description = "Backport of CPython tarfile module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, + {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] + +[[package]] +name = "bandit" +version = "1.7.10" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"}, + {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + +[[package]] +name = "build" +version = "1.2.2.post1" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.8" +files = [ + {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, + {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} +packaging = ">=19.1" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +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" +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.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "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 = "43.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "dill" +version = "0.3.9" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, + {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "dodgy" +version = "0.2.1" +description = "Dodgy: Searches for dodgy looking lines in Python code" +optional = false +python-versions = "*" +files = [ + {file = "dodgy-0.2.1-py3-none-any.whl", hash = "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"}, + {file = "dodgy-0.2.1.tar.gz", hash = "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a"}, +] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "flake8-polyfill" +version = "1.0.2" +description = "Polyfill package for Flake8 plugins" +optional = false +python-versions = "*" +files = [ + {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, + {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, +] + +[package.dependencies] +flake8 = "*" + +[[package]] +name = "gitdb" +version = "4.0.11" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.43" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, + {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +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 = "id" +version = "1.4.0" +description = "A tool for generating OIDC identities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "id-1.4.0-py3-none-any.whl", hash = "sha256:a0391117c98fa9851ebd2b22df0dc6fd6aacbd89a4ec95c173f1311ca9bb7329"}, + {file = "id-1.4.0.tar.gz", hash = "sha256:23c06772e8bd3e3a44ee3f167868bf5a8e385b0c1e2cc707ad36eb7486b4765b"}, +] + +[package.dependencies] +pydantic = "*" +requests = "*" + +[package.extras] +dev = ["build", "bump (>=1.3.2)", "id[lint,test]"] +lint = ["bandit", "interrogate", "mypy", "ruff (<0.4.2)", "types-requests"] +test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] + +[[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-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +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 = "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 = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[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-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +description = "Useful decorators and context managers" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, + {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, +] + +[package.dependencies] +"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jaraco-functools" +version = "4.1.0" +description = "Functools like those found in stdlib" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, + {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, +] + +[package.dependencies] +more-itertools = "*" + +[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 = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "keyring" +version = "25.5.0" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-25.5.0-py3-none-any.whl", hash = "sha256:e67f8ac32b04be4714b42fe84ce7dad9c40985b9ca827c592cc303e7c26d9741"}, + {file = "keyring-25.5.0.tar.gz", hash = "sha256:4c753b3ec91717fe713c4edd522d625889d8973a349b0e582622f49766de58e6"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +"jaraco.context" = "*" +"jaraco.functools" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +completion = ["shtab (>=1.1.0)"] +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 = ["pyfakefs", "pytest (>=6,!=8.1.*)"] +type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"] + +[[package]] +name = "markdown" +version = "3.7" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[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" +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 = "markdown-table" +version = "2020.12.3" +description = "markown table generator" +optional = false +python-versions = "*" +files = [ + {file = "markdown-table-2020.12.3.tar.gz", hash = "sha256:df0de8e86d14183b1dab61aaa5a78ad683f8f7ca7dddda182ecbd403b321193f"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +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 = "more-itertools" +version = "10.5.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, + {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, +] + +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +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" +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 = "nh3" +version = "0.2.18" +description = "Python bindings to the ammonia HTML sanitization library." +optional = false +python-versions = "*" +files = [ + {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86"}, + {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307"}, + {file = "nh3-0.2.18-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50"}, + {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204"}, + {file = "nh3-0.2.18-cp37-abi3-win32.whl", hash = "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be"}, + {file = "nh3-0.2.18-cp37-abi3-win_amd64.whl", hash = "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844"}, + {file = "nh3-0.2.18.tar.gz", hash = "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pbr" +version = "6.1.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, + {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, +] + +[[package]] +name = "pep8-naming" +version = "0.10.0" +description = "Check PEP-8 naming conventions, plugin for flake8" +optional = false +python-versions = "*" +files = [ + {file = "pep8-naming-0.10.0.tar.gz", hash = "sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"}, + {file = "pep8_naming-0.10.0-py2.py3-none-any.whl", hash = "sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164"}, +] + +[package.dependencies] +flake8-polyfill = ">=1.0.2,<2" + +[[package]] +name = "pkginfo" +version = "1.10.0" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, + {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + +[[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" +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 = "prospector" +version = "1.12.1" +description = "Prospector is a tool to analyse Python code by aggregating the result of other tools." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "prospector-1.12.1-py3-none-any.whl", hash = "sha256:e2440b51f40626cbaea80edd97263d8c0a71a79e729415fb505096d4d39e2287"}, + {file = "prospector-1.12.1.tar.gz", hash = "sha256:b9bb4bcdd77b943c597ee4f374960e851cdd2a0b4b60eaeeaf0da465facafc60"}, +] + +[package.dependencies] +bandit = {version = ">=1.5.1", optional = true, markers = "extra == \"with-bandit\" or extra == \"with_everything\""} +dodgy = ">=0.2.1,<0.3.0" +flake8 = "*" +GitPython = ">=3.1.27,<4.0.0" +mccabe = ">=0.7.0,<0.8.0" +mypy = {version = ">=0.600", optional = true, markers = "extra == \"with-mypy\" or extra == \"with_everything\""} +packaging = "*" +pep8-naming = ">=0.3.3,<=0.10.0" +pycodestyle = ">=2.9.0" +pydocstyle = ">=2.0.0" +pyflakes = ">=2.2.0" +pylint = ">=3.0" +pylint-celery = "0.3" +pylint-django = ">=2.6.1" +pylint-flask = "0.6" +pyroma = {version = ">=2.4", optional = true, markers = "extra == \"with-pyroma\" or extra == \"with_everything\""} +PyYAML = "*" +requirements-detector = ">=1.3.1" +setoptconf-tmp = ">=0.3.1,<0.4.0" +toml = ">=0.10.2,<0.11.0" + +[package.extras] +with-bandit = ["bandit (>=1.5.1)"] +with-everything = ["bandit (>=1.5.1)", "mypy (>=0.600)", "pyright (>=1.1.3)", "pyroma (>=2.4)", "vulture (>=1.5)"] +with-mypy = ["mypy (>=0.600)"] +with-pyright = ["pyright (>=1.1.3)"] +with-pyroma = ["pyroma (>=2.4)"] +with-vulture = ["vulture (>=1.5)"] + +[[package]] +name = "prospector-profile-duplicated" +version = "1.6.0" +description = "Profile that can be used to disable the duplicated or conflict rules between Prospector and other tools" +optional = false +python-versions = "*" +files = [ + {file = "prospector_profile_duplicated-1.6.0-py2.py3-none-any.whl", hash = "sha256:bf6a6aae0c7de48043b95e4d42e23ccd090c6c7115b6ee8c8ca472ffb1a2022b"}, + {file = "prospector_profile_duplicated-1.6.0.tar.gz", hash = "sha256:9c2d541076537405e8b2484cb6222276a2df17492391b6af1b192695770aab83"}, +] + +[[package]] +name = "prospector-profile-utils" +version = "1.9.1" +description = "Some utility Prospector profiles." +optional = false +python-versions = "*" +files = [ + {file = "prospector_profile_utils-1.9.1-py2.py3-none-any.whl", hash = "sha256:b458d8c4d59bdb1547e4630a2c6de4971946c4f0999443db6a9eef6d216b26b8"}, + {file = "prospector_profile_utils-1.9.1.tar.gz", hash = "sha256:008efa6797a85233fd8093dcb9d86f5fa5d89673e431c15cb1496a91c9b2c601"}, +] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[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 = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = [ + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +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 = "pylint" +version = "3.3.1" +description = "python code static checker" +optional = false +python-versions = ">=3.9.0" +files = [ + {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"}, + {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"}, +] + +[package.dependencies] +astroid = ">=3.3.4,<=3.4.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pylint-celery" +version = "0.3" +description = "pylint-celery is a Pylint plugin to aid Pylint in recognising and understandingerrors caused when using the Celery library" +optional = false +python-versions = "*" +files = [ + {file = "pylint-celery-0.3.tar.gz", hash = "sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"}, +] + +[package.dependencies] +astroid = ">=1.0" +pylint = ">=1.0" +pylint-plugin-utils = ">=0.2.1" + +[[package]] +name = "pylint-django" +version = "2.6.1" +description = "A Pylint plugin to help Pylint understand the Django web framework" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "pylint-django-2.6.1.tar.gz", hash = "sha256:19e8c85a8573a04e3de7be2ba91e9a7c818ebf05e1b617be2bbae67a906b725f"}, + {file = "pylint_django-2.6.1-py3-none-any.whl", hash = "sha256:359f68fe8c810ee6bc8e1ab4c83c19b15a43b234a24b08978f47a23462b5ce28"}, +] + +[package.dependencies] +pylint = ">=3.0,<4" +pylint-plugin-utils = ">=0.8" + +[package.extras] +with-django = ["Django (>=2.2)"] + +[[package]] +name = "pylint-flask" +version = "0.6" +description = "pylint-flask is a Pylint plugin to aid Pylint in recognizing and understanding errors caused when using Flask" +optional = false +python-versions = "*" +files = [ + {file = "pylint-flask-0.6.tar.gz", hash = "sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"}, +] + +[package.dependencies] +pylint-plugin-utils = ">=0.2.1" + +[[package]] +name = "pylint-plugin-utils" +version = "0.8.2" +description = "Utilities and helpers for writing Pylint plugins" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pylint_plugin_utils-0.8.2-py3-none-any.whl", hash = "sha256:ae11664737aa2effbf26f973a9e0b6779ab7106ec0adc5fe104b0907ca04e507"}, + {file = "pylint_plugin_utils-0.8.2.tar.gz", hash = "sha256:d3cebf68a38ba3fba23a873809155562571386d4c1b03e5b4c4cc26c3eee93e4"}, +] + +[package.dependencies] +pylint = ">=1.7" + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, + {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, +] + +[[package]] +name = "pyroma" +version = "4.2" +description = "Test your project's packaging friendliness" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyroma-4.2-py3-none-any.whl", hash = "sha256:a59854b6f8a72b55384cc1de42410e5c5ac59d0c40a92e84fd8364aa6cec3e37"}, + {file = "pyroma-4.2.tar.gz", hash = "sha256:6c727dc4a7a10e12274faed5fb47ebd499ca0821995befec98e3cfcaf1e7383c"}, +] + +[package.dependencies] +build = ">=0.7.0" +docutils = "*" +packaging = "*" +pygments = "*" +requests = "*" +setuptools = ">=42" +trove-classifiers = ">=2022.6.26" + +[package.extras] +test = ["setuptools (>=60)", "zest.releaser[recommended]"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + +[[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 = "readme-renderer" +version = "44.0" +description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +optional = false +python-versions = ">=3.9" +files = [ + {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, + {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, +] + +[package.dependencies] +docutils = ">=0.21.2" +nh3 = ">=0.2.14" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +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-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "requirements-detector" +version = "1.3.1" +description = "Python tool to find and list requirements of a Python project" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "requirements_detector-1.3.1-py3-none-any.whl", hash = "sha256:3ef72e1c5c3ad11100058e8f074a5762a4902985e698099d2e7f1283758d4045"}, + {file = "requirements_detector-1.3.1.tar.gz", hash = "sha256:b89e34faf0e4d17f5736923918bd5401949cbe723294ccfefd698b3cda28e676"}, +] + +[package.dependencies] +astroid = ">=3.0,<4.0" +packaging = ">=21.3" +semver = ">=3.0.0,<4.0.0" +toml = ">=0.10.2,<0.11.0" + +[[package]] +name = "rfc3986" +version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[package.extras] +idna2008 = ["idna"] + +[[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" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "security-md" +version = "0.2.3" +description = "Common utilities for Camptocamp CI" +optional = false +python-versions = ">=3.9" +files = [ + {file = "security_md-0.2.3-py3-none-any.whl", hash = "sha256:e95e454d7c7b9786a7af16c8d5fa657cdad575d76b74c69d4b1f3f88548bbd82"}, + {file = "security_md-0.2.3.tar.gz", hash = "sha256:cc766d03b01d1f5e49ed616480cc5f92469eb5e278466f71066b7acbd011adad"}, +] + +[package.dependencies] +defusedxml = ">=0.0.0,<1.0.0" +markdown = ">=3.0,<4.0" +markdown-table = ">=2020.0.0,<2021.0.0" + +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + +[[package]] +name = "setoptconf-tmp" +version = "0.3.1" +description = "A module for retrieving program settings from various sources in a consistant method." +optional = false +python-versions = "*" +files = [ + {file = "setoptconf-tmp-0.3.1.tar.gz", hash = "sha256:e0480addd11347ba52f762f3c4d8afa3e10ad0affbc53e3ffddc0ca5f27d5778"}, + {file = "setoptconf_tmp-0.3.1-py3-none-any.whl", hash = "sha256:76035d5cd1593d38b9056ae12d460eca3aaa34ad05c315b69145e138ba80a745"}, +] + +[package.extras] +yaml = ["pyyaml"] + +[[package]] +name = "setuptools" +version = "75.3.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, + {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "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 (>=4.2.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 (>=5.5)", "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.12.*)", "pytest-mypy"] + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "stevedore" +version = "5.3.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, + {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, +] + +[package.dependencies] +pbr = ">=2.0.0" + +[[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.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "trove-classifiers" +version = "2024.10.21.16" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +files = [ + {file = "trove_classifiers-2024.10.21.16-py3-none-any.whl", hash = "sha256:0fb11f1e995a757807a8ef1c03829fbd4998d817319abcef1f33165750f103be"}, + {file = "trove_classifiers-2024.10.21.16.tar.gz", hash = "sha256:17cbd055d67d5e9d9de63293a8732943fabc21574e4c7b74edf112b4928cf5f3"}, +] + +[[package]] +name = "twine" +version = "5.1.1" +description = "Collection of utilities for publishing packages on PyPI" +optional = false +python-versions = ">=3.8" +files = [ + {file = "twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997"}, + {file = "twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +keyring = ">=15.1" +pkginfo = ">=1.8.1,<1.11" +readme-renderer = ">=35.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +rich = ">=12.0.0" +urllib3 = ">=1.26.0" + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20240917" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +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.20241016" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +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" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +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 = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +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"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<4.0" +content-hash = "ef4f65da1d058e9d892ff8182d8e8d38a5ef9f20ca3435cd344ae35221598570" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..931fdc5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,101 @@ +[tool.ruff] +line-length = 110 +target-version = "py39" + +[tool.ruff.lint] +fixable = ["ALL"] +extend-select = [ + "UP", # pyupgrade + "F", # Pyflakes + "E", "W", # Pycodestyle + "I", # isort + "S", # flake8-bandit + "SIM", # flake8-simplify + "B", # flake8-bugbear + # pydocstyle + "D213", "D214", "D215", "D405", "D406", "D407", "D408", "D409", "D410", "D411", "D413", "D416", "D417", +] +ignore= [ + "S101", # Use of assert detected + "S607", # start-process-with-partial-path + "S603", # subprocess-without-shell-equals-true +] +exclude = ["tag_publish/configuration.py"] + +[tool.mypy] +python_version = "3.9" +ignore_missing_imports = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_return_any = true +strict = true + +[tool.poetry] +name = "tag-publish" +version = "0.0.0" +description = "Tools used to publish Python packages, Docker images and Helm charts for GitHub tag and branch" +readme = "README.md" +authors = ["Camptocamp "] +keywords = ["ci"] +repository = "https://github.com/camptocamp/tag-publish" +license = "FreeBSD" +packages = [{ include = "tag_publish" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Typing :: Typed", +] +include = [ + "tag_publish/py.typed", + "tag_publish/*.json", +] +exclude = ["tag_publish/node_modules/**/test"] + +[tool.poetry.scripts] +tag_publish = "tag_publish.cli:main" +tag_publish_new = "tag_publish.new:main" + +[tool.poetry.dependencies] +python = ">=3.9,<4.0" +requests = "2.32.3" +twine = "5.1.1" +PyYAML = "6.0.2" +id = "1.4.0" +security-md = "0.2.3" + +[tool.poetry.group.dev.dependencies] +prospector = { version = "1.12.1", extras = ["with-bandit", "with-mypy", "with-pyroma"] } +prospector-profile-duplicated = "1.6.0" +prospector-profile-utils = "1.9.1" +types-requests = "2.32.0.20241016" +types-pyyaml = "6.0.12.20240917" + +[build-system] +requires = [ + "poetry-core>=1.0.0", + "poetry-dynamic-versioning[plugin]>=0.19.0", + "poetry-plugin-tweak-dependencies-version", + "poetry-plugin-drop-python-upper-constraint", +] +build-backend = "poetry.core.masonry.api" + +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +pattern = "^(?P\\d+(\\.\\d+)*)" +format-jinja = """ +{%- if env.get("VERSION_TYPE") == "version_branch" -%} +{{serialize_pep440(bump_version(base, 1 if env.get("IS_MASTER") == "TRUE" else 2), dev=distance)}} +{%- elif distance == 0 -%} +{{serialize_pep440(base)}} +{%- else -%} +{{serialize_pep440(bump_version(base), dev=distance)}} +{%- endif -%} +""" + +[tool.poetry-plugin-tweak-dependencies-version] +default = "major" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..03d157d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +poetry==1.8.4 +poetry-plugin-export==1.8.0 +poetry-dynamic-versioning[plugin]==1.4.1 +poetry-plugin-tweak-dependencies-version==1.5.2 +pre-commit==4.0.1 diff --git a/tag_publish/__init__.py b/tag_publish/__init__.py new file mode 100644 index 0000000..e4c29cb --- /dev/null +++ b/tag_publish/__init__.py @@ -0,0 +1,3 @@ +""" +Tag Publish main module. +""" diff --git a/tag_publish/cli.py b/tag_publish/cli.py new file mode 100644 index 0000000..e5c3aa5 --- /dev/null +++ b/tag_publish/cli.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 + +""" +The publish script. +""" + +import argparse +import os +import re +import subprocess # nosec +import sys +from re import Match +from typing import Optional, cast + +import requests +import security_md +import yaml + +import tag_publish +import tag_publish.configuration +import tag_publish.lib.docker +import tag_publish.lib.oidc +import tag_publish.publish +import tag_publish.scripts.download_applications +from tag_publish.scripts.trigger_image_update import dispatch + + +def match(tpe: str, base_re: str) -> Optional[Match[str]]: + """ + Return the match for `GITHUB_REF` basically like: `refs//`. + + Arguments: + tpe: The type of ref we want to match (heads, tag, ...) + base_re: The regular expression to match the value + + """ + if base_re[0] == "^": + base_re = base_re[1:] + if base_re[-1] != "$": + base_re += "$" + return re.match(f"^refs/{tpe}/{base_re}", os.environ["GITHUB_REF"]) + + +def to_version(full_config: tag_publish.configuration.Configuration, value: str, kind: str) -> str: + """ + Compute publish version from branch name or tag. + + Arguments: + full_config: The full configuration + value: The value to be transformed + kind: The name of the transformer in the configuration + + """ + item_re = tag_publish.compile_re( + cast( + tag_publish.configuration.VersionTransform, + full_config["version"].get(kind + "_to_version_re", []), + ) + ) + value_match = tag_publish.match(value, item_re) + if value_match[0] is not None: + return tag_publish.get_value(*value_match) + return value + + +def main() -> None: + """ + Run the publish. + """ + parser = argparse.ArgumentParser(description="Publish the project.") + parser.add_argument("--group", default="default", help="The publishing group") + parser.add_argument("--version", help="The version to publish to") + parser.add_argument( + "--docker-versions", + help="The versions to publish on Docker registry, comma separated, ex: 'x,x.y,x.y.z,latest'.", + ) + parser.add_argument("--snyk-version", help="The version to publish to Snyk") + parser.add_argument("--branch", help="The branch from which to compute the version") + parser.add_argument("--tag", help="The tag from which to compute the version") + parser.add_argument("--dry-run", action="store_true", help="Don't do the publish") + parser.add_argument( + "--type", + help="The type of version, if no argument provided auto-determinate, can be: " + "rebuild (in case of rebuild), version_tag, version_branch, feature_branch, feature_tag " + "(for pull request)", + ) + args = parser.parse_args() + + config = tag_publish.get_config() + + # Describe the kind of release we do: rebuild (specified with --type), version_tag, version_branch, + # feature_branch, feature_tag (for pull request) + version: str = "" + ref = os.environ.get("GITHUB_REF", "refs/heads/fake-local") + local = "GITHUB_REF" not in os.environ + + if len([e for e in [args.version, args.branch, args.tag] if e is not None]) > 1: + print("::error::you specified more than one of the arguments --version, --branch or --tag") + sys.exit(1) + + version_type = args.type + + tag_match = tag_publish.match( + ref, + tag_publish.compile_re(config["version"].get("tag_to_version_re", []), "refs/tags/"), + ) + branch_match = tag_publish.match( + ref, + tag_publish.compile_re(config["version"].get("branch_to_version_re", []), "refs/heads/"), + ) + ref_match = re.match(r"refs/pull/(.*)/merge", ref) + + if args.version is not None: + version = args.version + elif args.branch is not None: + version = to_version(config, args.branch, "branch") + elif args.tag is not None: + version = to_version(config, args.tag, "tag") + elif tag_match[0] is not None: + if version_type is None: + version_type = "version_tag" + else: + print("::warning::you specified the argument --type but not one of --version, --branch or --tag") + version = tag_publish.get_value(*tag_match) + elif branch_match[0] is not None: + if version_type is None: + version_type = "version_branch" + else: + print("::warning::you specified the argument --type but not one of --version, --branch or --tag") + version = tag_publish.get_value(*branch_match) + elif ref_match is not None: + version = tag_publish.get_value(ref_match, {}, ref) + if version_type is None: + version_type = "feature_branch" + elif ref.startswith("refs/heads/"): + if version_type is None: + version_type = "feature_branch" + else: + print("::warning::you specified the argument --type but not one of --version, --branch or --tag") + # By the way we replace '/' by '_' because it isn't supported by Docker + version = "_".join(ref.split("/")[2:]) + elif ref.startswith("refs/tags/"): + if version_type is None: + version_type = "feature_tag" + else: + print("::warning::you specified the argument --type but not one of --version, --branch or --tag") + # By the way we replace '/' by '_' because it isn't supported by Docker + version = "_".join(ref.split("/")[2:]) + else: + print( + f"WARNING: {ref} is not supported, only ref starting with 'refs/heads/' or 'refs/tags/' " + "are supported, ignoring" + ) + sys.exit(0) + + if version_type is None: + print( + "::error::you specified one of the arguments --version, --branch or --tag but not the --type, " + f"GitHub ref is: {ref}" + ) + sys.exit(1) + + if version_type is not None: + if args.dry_run: + print(f"Create release type {version_type}: {version} (dry run)") + else: + print(f"Create release type {version_type}: {version}") + + success = True + pypi_config = cast( + tag_publish.configuration.PublishPypiConfig, + config.get("publish", {}).get("pypi", {}) if config.get("publish", {}).get("pypi", False) else {}, + ) + if pypi_config: + if pypi_config["packages"]: + tag_publish.lib.oidc.pypi_login() + + for package in pypi_config["packages"]: + if ( + package.get("group", tag_publish.configuration.PUBLISH_PIP_PACKAGE_GROUP_DEFAULT) + == args.group + ): + publish = version_type in pypi_config.get("versions", []) + if args.dry_run: + print( + f"{'Publishing' if publish else 'Checking'} " + f"'{package.get('path')}' to pypi, skipping (dry run)" + ) + else: + success &= tag_publish.publish.pip(package, version, version_type, publish) + + docker_config = cast( + tag_publish.configuration.PublishDockerConfig, + config.get("publish", {}).get("docker", {}) if config.get("publish", {}).get("docker", False) else {}, + ) + if docker_config: + full_repo = tag_publish.get_repository() + full_repo_split = full_repo.split("/") + master_branch, _ = tag_publish.get_master_branch(full_repo_split) + security_text = "" + if local: + with open("SECURITY.md", encoding="utf-8") as security_file: + security_text = security_file.read() + else: + security_response = requests.get( + f"https://raw.githubusercontent.com/{full_repo}/{master_branch}/SECURITY.md", + headers=tag_publish.add_authorization_header({}), + timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")), + ) + tag_publish.check_response(security_response, False) + if security_response.ok: + security_text = security_response.text + elif security_response.status_code != 404: + print(f"::error:: {security_response.status_code} {security_response.text}") + sys.exit(1) + + security = security_md.Security(security_text) + version_index = security.version_index + alternate_tag_index = security.alternate_tag_index + + row_index = -1 + if version_index >= 0: + for index, row in enumerate(security.data): + if row[version_index] == version: + row_index = index + break + + alt_tags = set() + if alternate_tag_index >= 0 and row_index >= 0: + alt_tags = { + t.strip() for t in security.data[row_index][alternate_tag_index].split(",") if t.strip() + } + if version_index >= 0 and security.data[-1][version_index] == version: + add_latest = True + for data in security.data: + row_tags = {t.strip() for t in data[alternate_tag_index].split(",") if t.strip()} + print(row_tags) + if "latest" in row_tags: + print("latest found in ", row_tags) + add_latest = False + break + if add_latest: + alt_tags.add("latest") + + images_src: set[str] = set() + images_full: list[str] = [] + images_snyk: set[str] = set() + versions = args.docker_versions.split(",") if args.docker_versions else [version] + for image_conf in docker_config.get("images", []): + if ( + image_conf.get("group", tag_publish.configuration.PUBLISH_DOCKER_IMAGE_GROUP_DEFAULT) + == args.group + ): + for tag_config in image_conf.get( + "tags", tag_publish.configuration.PUBLISH_DOCKER_IMAGE_TAGS_DEFAULT + ): + tag_src = tag_config.format(version="latest") + image_source = f"{image_conf['name']}:{tag_src}" + images_src.add(image_source) + tag_snyk = tag_config.format(version=args.snyk_version or version).lower() + image_snyk = f"{image_conf['name']}:{tag_snyk}" + + # Workaround sine we have the business plan + image_snyk = f"{image_conf['name']}_{tag_snyk}" + + if not args.dry_run: + subprocess.run(["docker", "tag", image_source, image_snyk], check=True) + images_snyk.add(image_snyk) + if tag_snyk != tag_src and not args.dry_run: + subprocess.run( + [ + "docker", + "tag", + image_source, + f"{image_conf['name']}:{tag_snyk}", + ], + check=True, + ) + + tags_calendar = [] + for name, conf in { + **cast( + dict[str, tag_publish.configuration.PublishDockerRepository], + tag_publish.configuration.DOCKER_REPOSITORY_DEFAULT, + ), + **docker_config.get("repository", {}), + }.items(): + for docker_version in versions: + tag_dst = tag_config.format(version=docker_version) + if tag_dst not in tags_calendar: + tags_calendar.append(tag_dst) + if version_type in conf.get( + "versions", + tag_publish.configuration.PUBLISH_DOCKER_REPOSITORY_VERSIONS_DEFAULT, + ): + tags = [ + tag_config.format(version=alt_tag) + for alt_tag in [docker_version, *alt_tags] + ] + + if args.dry_run: + for tag in tags: + print( + f"Publishing {image_conf['name']}:{tag} to {name}, skipping " + "(dry run)" + ) + else: + success &= tag_publish.publish.docker( + conf, name, image_conf, tag_src, tags, images_full + ) + + if args.dry_run: + sys.exit(0) + + dispatch_config = docker_config.get("dispatch", {}) + if dispatch_config is not False and images_full: + dispatch( + dispatch_config.get( + "repository", tag_publish.configuration.DOCKER_DISPATCH_REPOSITORY_DEFAULT + ), + dispatch_config.get( + "event-type", tag_publish.configuration.DOCKER_DISPATCH_EVENT_TYPE_DEFAULT + ), + images_full, + ) + + snyk_exec, env = tag_publish.snyk_exec() + for image in images_snyk: + print(f"::group::Snyk check {image}") + sys.stdout.flush() + sys.stderr.flush() + try: + if version_type in ("version_branch", "version_tag"): + monitor_args = docker_config.get("snyk", {}).get( + "monitor_args", + tag_publish.configuration.PUBLISH_DOCKER_SNYK_MONITOR_ARGS_DEFAULT, + ) + if monitor_args is not False: + subprocess.run( # pylint: disable=subprocess-run-check + [ + snyk_exec, + "container", + "monitor", + *monitor_args, + # Available only on the business plan + # f"--project-tags=tag={image.split(':')[-1]}", + image, + ], + env=env, + ) + test_args = docker_config.get("snyk", {}).get( + "test_args", tag_publish.configuration.PUBLISH_DOCKER_SNYK_TEST_ARGS_DEFAULT + ) + snyk_error = False + if test_args is not False: + proc = subprocess.run( + [snyk_exec, "container", "test", *test_args, image], + check=False, + env=env, + ) + if proc.returncode != 0: + snyk_error = True + print("::endgroup::") + if snyk_error: + print("::error::Critical vulnerability found by Snyk in the published image.") + except subprocess.CalledProcessError as exception: + print(f"Error: {exception}") + print("::endgroup::") + print("::error::With error") + + versions_config, dpkg_config_found = tag_publish.lib.docker.get_versions_config() + dpkg_success = True + for image in images_src: + dpkg_success &= tag_publish.lib.docker.check_versions(versions_config.get(image, {}), image) + + if not dpkg_success: + current_versions_in_images: dict[str, dict[str, str]] = {} + if dpkg_config_found: + with open("ci/dpkg-versions.yaml", encoding="utf-8") as dpkg_versions_file: + current_versions_in_images = yaml.load(dpkg_versions_file, Loader=yaml.SafeLoader) + for image in images_src: + _, versions_image = tag_publish.lib.docker.get_dpkg_packages_versions(image) + for dpkg_package, package_version in versions_image.items(): + if dpkg_package not in current_versions_in_images.get(image, {}): + current_versions_in_images.setdefault(image, {})[dpkg_package] = str(package_version) + for dpkg_package in list(current_versions_in_images[image].keys()): + if dpkg_package not in versions_image: + del current_versions_in_images[image][dpkg_package] + if dpkg_config_found: + print( + "::error::Some packages are have a greater version in the config raster then " + "in the image." + ) + print("Current versions of the Debian packages in Docker images:") + print(yaml.dump(current_versions_in_images, Dumper=yaml.SafeDumper, default_flow_style=False)) + if dpkg_config_found: + with open("ci/dpkg-versions.yaml", "w", encoding="utf-8") as dpkg_versions_file: + yaml.dump( + current_versions_in_images, + dpkg_versions_file, + Dumper=yaml.SafeDumper, + default_flow_style=False, + ) + + if dpkg_config_found: + success = False + + helm_config = cast( + tag_publish.configuration.PublishHelmConfig, + config.get("publish", {}).get("helm", {}) if config.get("publish", {}).get("helm", False) else {}, + ) + if helm_config and helm_config["folders"] and version_type in helm_config.get("versions", []): + tag_publish.scripts.download_applications.download_tag_publish_applications("helm/chart-releaser") + + owner, repo = full_repo_split + commit_sha = ( + subprocess.run(["git", "rev-parse", "HEAD"], check=True, stdout=subprocess.PIPE) + .stdout.strip() + .decode() + ) + token = ( + os.environ["GITHUB_TOKEN"].strip() + if "GITHUB_TOKEN" in os.environ + else tag_publish.gopass("gs/ci/github/token/gopass") + ) + assert token is not None + if version_type == "version_branch": + last_tag = ( + subprocess.run( + ["git", "describe", "--abbrev=0", "--tags"], check=True, stdout=subprocess.PIPE + ) + .stdout.strip() + .decode() + ) + expression = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") + while expression.match(last_tag) is None: + last_tag = ( + subprocess.run( + ["git", "describe", "--abbrev=0", "--tags", f"{last_tag}^"], + check=True, + stdout=subprocess.PIPE, + ) + .stdout.strip() + .decode() + ) + + versions = last_tag.split(".") + versions[-1] = str(int(versions[-1]) + 1) + version = ".".join(versions) + + for folder in helm_config["folders"]: + success &= tag_publish.publish.helm(folder, version, owner, repo, commit_sha, token) + + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tag_publish/configuration.py b/tag_publish/configuration.py new file mode 100644 index 0000000..1c38955 --- /dev/null +++ b/tag_publish/configuration.py @@ -0,0 +1,463 @@ +""" +Automatically generated file from a JSON schema. +""" + +from typing import Any, Dict, List, Literal, TypedDict, Union + + +class Configuration(TypedDict, total=False): + """ + configuration. + + Tag Publish configuration file + """ + + version: "Version" + """ + Version. + + The version configurations + """ + + docker: "PublishDocker" + """ + Publish Docker. + + The configuration used to publish on Docker + + Aggregation type: oneOf + Subtype: "PublishDockerConfig" + """ + + pypi: "PublishPypi" + """ + publish pypi. + + Configuration to publish on pypi + + default: + {} + + Aggregation type: oneOf + Subtype: "PublishPypiConfig" + """ + + helm: "PublishHelm" + """ + publish helm. + + Configuration to publish Helm charts on GitHub release + + Aggregation type: oneOf + Subtype: "PublishHelmConfig" + """ + + +DISPATCH_CONFIG_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'Publish Docker config dispatch oneof0' """ + + +DOCKER_DISPATCH_EVENT_TYPE_DEFAULT = "image-update" +""" Default value of the field path 'dispatch config event-type' """ + + +DOCKER_DISPATCH_REPOSITORY_DEFAULT = "camptocamp/argocd-gs-gmf-apps" +""" Default value of the field path 'dispatch config repository' """ + + +DOCKER_REPOSITORY_DEFAULT = { + "github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]}, + "dockerhub": {}, +} +""" Default value of the field path 'Publish Docker config repository' """ + + +# | dispatch config. +# | +# | Send a dispatch event to an other repository +# | +# | default: +# | {} +DispatchConfig = TypedDict( + "DispatchConfig", + { + # | Docker dispatch repository. + # | + # | The repository name to be triggered + # | + # | default: camptocamp/argocd-gs-gmf-apps + "repository": str, + # | Docker dispatch event type. + # | + # | The event type to be triggered + # | + # | default: image-update + "event-type": str, + }, + total=False, +) + + +PUBLISH_DOCKER_IMAGE_GROUP_DEFAULT = "default" +""" Default value of the field path 'Publish Docker image group' """ + + +PUBLISH_DOCKER_IMAGE_TAGS_DEFAULT = ["{version}"] +""" Default value of the field path 'Publish Docker image tags' """ + + +PUBLISH_DOCKER_LATEST_DEFAULT = True +""" Default value of the field path 'Publish Docker config latest' """ + + +PUBLISH_DOCKER_REPOSITORY_VERSIONS_DEFAULT = ["version_tag", "version_branch", "rebuild", "feature_branch"] +""" Default value of the field path 'Publish Docker repository versions' """ + + +PUBLISH_DOCKER_SNYK_MONITOR_ARGS_DEFAULT = ["--app-vulns"] +""" Default value of the field path 'Publish Docker config snyk monitor_args' """ + + +PUBLISH_DOCKER_SNYK_TEST_ARGS_DEFAULT = ["--app-vulns", "--severity-threshold=critical"] +""" Default value of the field path 'Publish Docker config snyk test_args' """ + + +PUBLISH_PIP_PACKAGE_GROUP_DEFAULT = "default" +""" Default value of the field path 'publish pypi package group' """ + + +PUBLISH_PYPI_CONFIG_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'publish pypi oneof0' """ + + +PUBLISH_PYPI_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'publish_pypi' """ + + +PublishDocker = Union["PublishDockerConfig", Literal[False]] +""" +Publish Docker. + +The configuration used to publish on Docker + +Aggregation type: oneOf +Subtype: "PublishDockerConfig" +""" + + +class PublishDockerConfig(TypedDict, total=False): + """ + Publish Docker config. + + The configuration used to publish on Docker + """ + + latest: bool + """ + Publish Docker latest. + + Publish the latest version on tag latest + + default: True + """ + + images: List["PublishDockerImage"] + """ List of images to be published """ + + repository: Dict[str, "PublishDockerRepository"] + """ + Docker repository. + + The repository where we should publish the images + + default: + dockerhub: {} + github: + server: ghcr.io + versions: + - version_tag + - version_branch + - rebuild + """ + + dispatch: Union["DispatchConfig", "_PublishDockerConfigDispatchOneof1"] + """ + Send a dispatch event to an other repository + + default: + {} + + Aggregation type: oneOf + Subtype: "DispatchConfig" + """ + + snyk: "_PublishDockerConfigSnyk" + """ Checks the published images with Snyk """ + + +class PublishDockerImage(TypedDict, total=False): + """Publish Docker image.""" + + group: str + """ + Publish Docker image group. + + The image is in the group, should be used with the --group option of tag-publish script + + default: default + """ + + name: str + """ The image name """ + + tags: List[str] + """ + publish docker image tags. + + The tag name, will be formatted with the version=, the image with version=latest should be present when we call the tag-publish script + + default: + - '{version}' + """ + + +class PublishDockerRepository(TypedDict, total=False): + """Publish Docker repository.""" + + server: str + """ The server URL """ + + versions: List[str] + """ + Publish Docker repository versions. + + The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script + + default: + - version_tag + - version_branch + - rebuild + - feature_branch + """ + + +PublishHelm = Union["PublishHelmConfig", Literal[False]] +""" +publish helm. + +Configuration to publish Helm charts on GitHub release + +Aggregation type: oneOf +Subtype: "PublishHelmConfig" +""" + + +class PublishHelmConfig(TypedDict, total=False): + """ + publish helm config. + + Configuration to publish on Helm charts on GitHub release + """ + + folders: List[str] + """ The folders that will be published """ + + versions: List[str] + """ The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script """ + + +PublishPypi = Union["PublishPypiConfig", "_PublishPypiOneof1"] +""" +publish pypi. + +Configuration to publish on pypi + +default: + {} + +Aggregation type: oneOf +Subtype: "PublishPypiConfig" +""" + + +class PublishPypiConfig(TypedDict, total=False): + """ + publish pypi config. + + Configuration to publish on pypi + + default: + {} + """ + + packages: List["PublishPypiPackage"] + """ The configuration of packages that will be published """ + + versions: List[str] + """ The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script """ + + +class PublishPypiPackage(TypedDict, total=False): + """ + publish pypi package. + + The configuration of package that will be published + """ + + group: str + """ + Publish pip package group. + + The image is in the group, should be used with the --group option of tag-publish script + + default: default + """ + + path: str + """ The path of the pypi package """ + + build_command: List[str] + """ The command used to do the build """ + + +class Version(TypedDict, total=False): + """ + Version. + + The version configurations + """ + + branch_to_version_re: "VersionTransform" + """ + Version transform. + + A version transformer definition + """ + + tag_to_version_re: "VersionTransform" + """ + Version transform. + + A version transformer definition + """ + + +VersionTransform = List["_VersionTransformItem"] +""" +Version transform. + +A version transformer definition +""" + + +_PUBLISH_DOCKER_CONFIG_DISPATCH_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'Publish Docker config dispatch' """ + + +_PUBLISH_DOCKER_CONFIG_DISPATCH_ONEOF1_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'Publish Docker config dispatch oneof1' """ + + +_PUBLISH_DOCKER_SNYK_MONITOR_ARGS_ONEOF0_DEFAULT = ["--app-vulns"] +""" Default value of the field path 'Publish Docker Snyk monitor args oneof0' """ + + +_PUBLISH_DOCKER_SNYK_MONITOR_ARGS_ONEOF1_DEFAULT = ["--app-vulns"] +""" Default value of the field path 'Publish Docker Snyk monitor args oneof1' """ + + +_PUBLISH_DOCKER_SNYK_TEST_ARGS_ONEOF0_DEFAULT = ["--app-vulns", "--severity-threshold=critical"] +""" Default value of the field path 'Publish Docker Snyk test args oneof0' """ + + +_PUBLISH_DOCKER_SNYK_TEST_ARGS_ONEOF1_DEFAULT = ["--app-vulns", "--severity-threshold=critical"] +""" Default value of the field path 'Publish Docker Snyk test args oneof1' """ + + +_PUBLISH_PYPI_ONEOF1_DEFAULT: Dict[str, Any] = {} +""" Default value of the field path 'publish pypi oneof1' """ + + +_PublishDockerConfigDispatchOneof1 = Literal[False] +""" +default: + {} +""" + + +class _PublishDockerConfigSnyk(TypedDict, total=False): + """Checks the published images with Snyk""" + + monitor_args: Union["_PublishDockerSnykMonitorArgsOneof0", "_PublishDockerSnykMonitorArgsOneof1"] + """ + Publish Docker Snyk monitor args. + + The arguments to pass to the Snyk container monitor command + + default: + - --app-vulns + + Aggregation type: oneOf + """ + + test_args: Union["_PublishDockerSnykTestArgsOneof0", "_PublishDockerSnykTestArgsOneof1"] + """ + Publish Docker Snyk test args. + + The arguments to pass to the Snyk container test command + + default: + - --app-vulns + - --severity-threshold=critical + + Aggregation type: oneOf + """ + + +_PublishDockerSnykMonitorArgsOneof0 = List[str] +""" +default: + - --app-vulns +""" + + +_PublishDockerSnykMonitorArgsOneof1 = Literal[False] +""" +default: + - --app-vulns +""" + + +_PublishDockerSnykTestArgsOneof0 = List[str] +""" +default: + - --app-vulns + - --severity-threshold=critical +""" + + +_PublishDockerSnykTestArgsOneof1 = Literal[False] +""" +default: + - --app-vulns + - --severity-threshold=critical +""" + + +_PublishPypiOneof1 = Literal[False] +""" +default: + {} +""" + + +_VersionTransformItem = TypedDict( + "_VersionTransformItem", + { + # | The from regular expression + "from": str, + # | The expand regular expression: https://docs.python.org/3/library/re.html#re.Match.expand + "to": str, + }, + total=False, +) diff --git a/tag_publish/lib/docker.py b/tag_publish/lib/docker.py new file mode 100644 index 0000000..df2237b --- /dev/null +++ b/tag_publish/lib/docker.py @@ -0,0 +1,146 @@ +""" +Some utility functions for Docker images. +""" + +import os +import subprocess # nosec: B404 +from typing import Optional, cast + +import yaml +from debian_inspector.version import Version + +import tag_publish.configuration + + +def get_dpkg_packages_versions( + image: str, + default_distribution: Optional[str] = None, + default_release: Optional[str] = None, +) -> tuple[bool, dict[str, Version]]: + """ + Get the versions of the dpkg packages installed in the image. + + `get_dpkg_packages_versions("org/image:tag")` will return something like: + (true, {"debian_11/api": "2.2.0", ...}) + + Where `debian_11` corresponds on last path element for 'Debian 11' + from https://repology.org/repositories/statistics + """ + dpkg_configuration = tag_publish.get_config().get("dpkg", {}) + + os_release = {} + try: + os_release_process = subprocess.run( + ["docker", "run", "--rm", "--entrypoint=", image, "cat", "/etc/os-release"], + stdout=subprocess.PIPE, + check=True, + ) + os_release = dict([e.split("=") for e in os_release_process.stdout.decode().split("\n") if e]) + except subprocess.CalledProcessError: + print("Info: /etc/os-release not found in the image") + + lsb_release = {} + try: + lsb_release_process = subprocess.run( + ["docker", "run", "--rm", "--entrypoint=", image, "cat", "/etc/lsb-release"], + stdout=subprocess.PIPE, + check=True, + ) + lsb_release = dict([e.split("=") for e in lsb_release_process.stdout.decode().split("\n") if e]) + except subprocess.CalledProcessError: + print("Info: /etc/lsb-release not found in the image") + + distribution = os_release.get("ID", lsb_release.get("DISTRIB_ID", default_distribution)) + release = os_release.get("VERSION_ID", lsb_release.get("DISTRIB_RELEASE", default_release)) + if distribution is None: + print("Could not get the distribution of the image, you should provide a default distribution") + return False, {} + if release is None: + print("Could not get the release of the image, you should provide a default release") + return False, {} + + distribution_final = distribution.strip('"').lower() + release_final = release.strip('"').replace(".", "_") + prefix = f"{distribution_final}_{release_final}/" + print(f"Found distribution '{distribution_final}', release '{release_final}'.") + + if distribution_final == "ubuntu" and release_final == "18_04": + print("Warning: Ubuntu 18.04 is not supported") + return False, {} + + package_version: dict[str, Version] = {} + packages_status_process = subprocess.run( + ["docker", "run", "--rm", "--entrypoint=", image, "dpkg", "--status"], + stdout=subprocess.PIPE, + check=True, + ) + packages_status_1 = packages_status_process.stdout.decode().split("\n") + packages_status_2 = [e.split(": ", maxsplit=1) for e in packages_status_1] + packages_status = [e for e in packages_status_2 if len(e) == 2] + package = None + version = None + for name, value in packages_status: + if name == "Package": + if package is not None: + if version is None: + print(f"Error: Missing version for package {package}") + else: + if package not in dpkg_configuration.get("ignored_packages", []): + package = dpkg_configuration.get("packages_mapping", {}).get(package, package) + if package in package_version and version != package_version[package]: + print( + f"The package {package} has different version " + f"({package_version[package]} != {version})" + ) + if package not in ("base-files",): + package_version[package] = version + package = value + version = None + if name == "Version" and version is None: + version = Version.from_string(value) + + return True, {f"{prefix}{k}": v for k, v in package_version.items()} + + +def get_versions_config() -> tuple[dict[str, dict[str, str]], bool]: + """ + Get the versions from the config file. + """ + if os.path.exists("ci/dpkg-versions.yaml"): + with open("ci/dpkg-versions.yaml", encoding="utf-8") as versions_file: + return ( + cast(dict[str, dict[str, str]], yaml.load(versions_file.read(), Loader=yaml.SafeLoader)), + True, + ) + return {}, False + + +def check_versions( + versions_config: dict[str, str], + image: str, + default_distribution: Optional[str] = None, + default_release: Optional[str] = None, +) -> bool: + """ + Check if the versions are correct. + + The versions of packages in the image should be present in the config file. + The versions of packages in the image shouldn't be older than the versions of the config file. + """ + result, versions_image = get_dpkg_packages_versions(image, default_distribution, default_release) + if not result: + return False + + success = True + for package, version in versions_image.items(): + if package not in versions_config: + print(f"Package {package} with version {version} is not in the config file for the image {image}") + success = False + elif Version.from_string(versions_config[package]) > version: + print( + f"Package {package} is older than the config file for the image {image}: " + f"{versions_config[package]} > {version}." + ) + success = False + + return success diff --git a/tag_publish/lib/oidc.py b/tag_publish/lib/oidc.py new file mode 100755 index 0000000..84a2f95 --- /dev/null +++ b/tag_publish/lib/oidc.py @@ -0,0 +1,188 @@ +""" +Manage OpenID Connect (OIDC) token exchange for external services. + +Inspired by +https://github.com/pypa/gh-action-pypi-publish/blob/unstable/v1/oidc-exchange.py +""" + +import base64 +import json +import os +import sys +from typing import NoReturn + +import id as oidc_id +import requests + + +class _OidcError(Exception): + pass + + +def _fatal(message: str) -> NoReturn: + # HACK: GitHub Actions' annotations don't work across multiple lines naively; + # translating `\n` into `%0A` (i.e., HTML percent-encoding) is known to work. + # See: https://github.com/actions/toolkit/issues/193 + message = message.replace("\n", "%0A") + print(f"::error::Trusted publishing exchange failure: {message}", file=sys.stderr) + raise _OidcError(message) + + +def _debug(message: str) -> None: + print(f"::debug::{message.title()}", file=sys.stderr) + + +def _render_claims(token: str) -> str: + _, payload, _ = token.split(".", 2) + + # urlsafe_b64decode needs padding; JWT payloads don't contain any. + payload += "=" * (4 - (len(payload) % 4)) + claims = json.loads(base64.urlsafe_b64decode(payload)) + + return f""" +The claims rendered below are **for debugging purposes only**. You should **not** +use them to configure a trusted publisher unless they already match your expectations. + +If a claim is not present in the claim set, then it is rendered as `MISSING`. + +* `sub`: `{claims.get('sub', 'MISSING')}` +* `repository`: `{claims.get('repository', 'MISSING')}` +* `repository_owner`: `{claims.get('repository_owner', 'MISSING')}` +* `repository_owner_id`: `{claims.get('repository_owner_id', 'MISSING')}` +* `job_workflow_ref`: `{claims.get('job_workflow_ref', 'MISSING')}` +* `ref`: `{claims.get('ref')}` + +See https://docs.pypi.org/trusted-publishers/troubleshooting/ for more help. +""" + + +def _get_token(hostname: str) -> str: + # Indices are expected to support `https://{hostname}/_/oidc/audience`, + # which tells OIDC exchange clients which audience to use. + audience_resp = requests.get(f"https://{hostname}/_/oidc/audience", timeout=5) + audience_resp.raise_for_status() + + _debug(f"selected trusted publishing exchange endpoint: https://{hostname}/_/oidc/mint-token") + + try: + oidc_token = oidc_id.detect_credential(audience=audience_resp.json()["audience"]) + except oidc_id.IdentityError as identity_error: + _fatal( + f""" +OpenID Connect token retrieval failed: {identity_error} + +This generally indicates a workflow configuration error, such as insufficient +permissions. Make sure that your workflow has `id-token: write` configured +at the job level, e.g.: + +```yaml +permissions: + id-token: write +``` + +Learn more at https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings. +""" + ) + + # Now we can do the actual token exchange. + mint_token_resp = requests.post( + f"https://{hostname}/_/oidc/mint-token", + json={"token": oidc_token}, + timeout=5, + ) + + try: + mint_token_payload = mint_token_resp.json() + except requests.JSONDecodeError: + # Token exchange failure normally produces a JSON error response, but + # we might have hit a server error instead. + _fatal( + f""" +Token request failed: the index produced an unexpected +{mint_token_resp.status_code} response. + +This strongly suggests a server configuration or downtime issue; wait +a few minutes and try again. + +You can monitor PyPI's status here: https://status.python.org/ +""" # noqa: E702 + ) + + # On failure, the JSON response includes the list of errors that + # occurred during minting. + if not mint_token_resp.ok: + reasons = "\n".join( + f'* `{error["code"]}`: {error["description"]}' + for error in mint_token_payload["errors"] # noqa: W604 + ) + + rendered_claims = _render_claims(oidc_token) + + _fatal( + f""" +Token request failed: the server refused the request for the following reasons: + +{reasons} + +This generally indicates a trusted publisher configuration error, but could +also indicate an internal error on GitHub or PyPI's part. + +{rendered_claims} +""" + ) + + pypi_token = mint_token_payload.get("token") + if not isinstance(pypi_token, str): + _fatal( + """ +Token response error: the index gave us an invalid response. + +This strongly suggests a server configuration or downtime issue; wait +a few minutes and try again. +""" + ) + + # Mask the newly minted PyPI token, so that we don't accidentally leak it in logs. + print(f"::add-mask::{pypi_token}") + + # This final print will be captured by the subshell in `twine-upload.sh`. + return pypi_token + + +def pypi_login() -> None: + """ + Connect to PyPI using OpenID Connect and mint a token for the user. + + See Also + -------- + - https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect + - https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-pypi + + """ + pypirc_filename = os.path.expanduser("~/.pypirc") + + if os.path.exists(pypirc_filename): + print(f"::info::{pypirc_filename} already exists; consider as already logged in.") # noqa: E702 + return + + if "ACTIONS_ID_TOKEN_REQUEST_TOKEN" not in os.environ: + print( + """::error::Not available, you probably miss the permission `id-token: write`. + ``` + permissions: + id-token: write + ``` + See also: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect""" + ) + return + + try: + token = _get_token("pypi.org") + with open(pypirc_filename, "w", encoding="utf-8") as pypirc_file: + pypirc_file.write("[pypi]\n") + pypirc_file.write("repository: https://upload.pypi.org/legacy/\n") + pypirc_file.write("username: __token__\n") + pypirc_file.write(f"password: {token}\n") + except _OidcError: + # Already visible in logs; no need to re-raise. + return diff --git a/tag_publish/lib/trigger_image_update.py b/tag_publish/lib/trigger_image_update.py new file mode 100644 index 0000000..cb71893 --- /dev/null +++ b/tag_publish/lib/trigger_image_update.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +""" +Trigger an image update on the argocd repository. +""" + +import os.path +import random +import subprocess # nosec + +import requests + + +def dispatch(repository: str, event_type: str, images_full: list[str]) -> None: + """ + Trigger an image update on the argocd repository. + """ + id_ = random.randint(1, 100000) # nosec # noqa: S311 + print(f"Triggering {event_type}:{id_} on {repository} with {','.join(images_full)}") + + response = requests.post( + f"https://api.github.com/repos/{repository}/dispatches", + headers={ + "Content-Type": "application/json2", + "Accept": "application/vnd.github.v3+json", + "Authorization": "token " + + subprocess.run( + ["gopass", "show", "gs/ci/github/token/gopass"], check=True, stdout=subprocess.PIPE + ) + .stdout.decode() + .strip(), + }, + json={"event_type": event_type, "client_payload": {"name": " ".join(images_full), "id": id_}}, + timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")), + ) + response.raise_for_status() diff --git a/tag_publish/new.py b/tag_publish/new.py new file mode 100644 index 0000000..d0962d6 --- /dev/null +++ b/tag_publish/new.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import re +import subprocess # nosec + +import multi_repo_automation as mra +import ruamel + +import tag_publish + + +def main() -> None: + """Create a new version with its stabilization branch.""" + args_parser = argparse.ArgumentParser( + description="Create a new version with its stabilization branch", + usage=""" +This will: +- Stash all your changes +- Checkout the master branch +- Pull it from origin +- Push it to a new stabilization branch +- Checkout a new branch named new-version +- Do the changes for the new version + - Update the SECURITY.md config + - Update the Renovate config + - Update the audit workflow + - Create the backport label +- Push it +- Create a pull request +- Go back to your old branch + +If you run the tool without any version it will check that everything is OK +regarding the SECURITY.md available on GitHub. + """, + ) + args_parser.add_argument( + "--version", + help="The version to create", + ) + args_parser.add_argument( + "--force", + action="store_true", + help="Force create the branch and push it", + ) + args_parser.add_argument( + "--supported-until", + help="The date until the version is supported, can also be To be defined or Best effort", + default="Best effort", + ) + args_parser.add_argument( + "--upstream-supported-until", + help="The date until the version is supported upstream", + ) + arguments = args_parser.parse_args() + + # Get the repo information e.g.: + # { + # "name": "camptocamp/tag_publish", + # "remote": "origin", + # "dir": "/home/user/src/tag_publish", + # } + # can be override with a repo.yaml file + repo = mra.get_repo_config() + + # Stash all your changes + subprocess.run(["git", "stash", "--all", "--message=Stashed by release creation"], check=True) + old_branch_name = subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + stdout=subprocess.PIPE, + check=True, + ).stdout.strip() + + # Checkout the master branch + subprocess.run(["git", "checkout", repo.get("master_branch", "master")], check=True) + + # Pull it from origin + subprocess.run( + ["git", "pull", repo.get("remote", "origin"), repo.get("master_branch", "master")], check=True + ) + + # Push it to a new stabilization branch + if arguments.version: + subprocess.run( + [ + "git", + "push", + *(["--force"] if arguments.force else []), + repo.get("remote", "origin"), + f"HEAD:{arguments.version}", + ], + check=not arguments.force, + ) + + version = arguments.version + branch_name = "new-version" if version is None else f"new-version-{version}" + + # Checkout a new branch named new-version + if arguments.force: + subprocess.run(["git", "branch", "-D", branch_name]) # pylint: disable=subprocess-run-check + subprocess.run(["git", "checkout", "-b", branch_name], check=True) + + # # # Do the changes for the new version # # # + + remotes = [r for r in mra.run(["git", "remote"], stdout=subprocess.PIPE).stdout.split() if r != ""] + remote_branches = [ + b.strip()[len("remotes/") :] + for b in mra.run(["git", "branch", "--all"], stdout=subprocess.PIPE).stdout.split() + if b != "" and b.strip().startswith("remotes/") + ] + if "upstream" in remotes: + remote_branches = [b[len("upstream") + 1 :] for b in remote_branches if b.startswith("upstream/")] + elif "origin" in remotes: + remote_branches = [b[len("origin") + 1 :] for b in remote_branches if b.startswith("origin/")] + else: + remote_branches = ["/".join(b.split("/")[1:]) for b in remote_branches] + + config = tag_publish.get_config() + branch_re = tag_publish.compile_re(config["version"].get("branch_to_version_re", [])) + branches_match = [tag_publish.match(b, branch_re) for b in remote_branches] + version_branch = {m.groups()[0] if m.groups() else b: b for m, c, b in branches_match if m is not None} + + stabilization_branches = [ + version_branch.get(version, version) for version in mra.get_stabilization_versions(repo) + ] + modified_files = [] + + if version: + stabilization_branches.append(version) + + if os.path.exists("SECURITY.md"): + modified_files.append("SECURITY.md") + with mra.Edit("SECURITY.md") as security_md: + security_md_lines = security_md.data.split("\n") + index = -1 + for i, line in enumerate(security_md_lines): + if line.startswith("| "): + index = i + + new_line = f"| {version} | {arguments.supported_until} |" + if arguments.upstream_supported_until: + new_line += f" {arguments.upstream_supported_until} |" + + security_md.data = "\n".join( + [*security_md_lines[: index + 1], new_line, *security_md_lines[index + 1 :]] + ) + + stabilization_branches_with_master = [*stabilization_branches, repo.get("master_branch", "master")] + + for labels in mra.gh_json("label", ["name"], "list"): + if ( + labels["name"].startswith("backport ") + and labels["name"].replace("backport ", "") not in stabilization_branches_with_master + ): + mra.gh("label", "delete", labels["name"], "--yes") + + for branch in stabilization_branches_with_master: + mra.gh( + "label", + "create", + "--force", + f"backport {branch}", + "--color=5aed94", + f"--description=Backport the pull request to the '{branch}' branch", + ) + + if os.path.exists(".github/renovate.json5"): + modified_files.append(".github/renovate.json5") + with mra.EditRenovateConfig(".github/renovate.json5") as renovate_config: + if stabilization_branches: + if "baseBranches: " in renovate_config.data: + renovate_config.data = re.sub( + r"(.*baseBranches: )\[[^\]]*\](.*)", + rf"\1{json.dumps(stabilization_branches_with_master)}\2", + renovate_config.data, + ) + else: + renovate_config.add( + f"baseBranches: {json.dumps(stabilization_branches_with_master)},\n", "baseBranches" + ) + + if stabilization_branches and os.path.exists(".github/workflows/audit.yaml"): + modified_files.append(".github/workflows/audit.yaml") + with mra.EditYAML(".github/workflows/audit.yaml") as yaml: + for job in yaml["jobs"].values(): + matrix = job.get("strategy", {}).get("matrix", {}) + if "include" in matrix and version: + new_include = dict(matrix["include"][-1]) + new_include["branch"] = version + matrix["include"].append(new_include) + + if "branch" in matrix and stabilization_branches: + yaml_stabilization_branches = ruamel.yaml.comments.CommentedSeq(stabilization_branches) + yaml_stabilization_branches._yaml_add_comment( # pylint: disable=protected-access + [ + ruamel.yaml.CommentToken("\n\n", ruamel.yaml.error.CommentMark(0), None), + None, + None, + None, + ], + len(stabilization_branches) - 1, + ) + job["strategy"]["matrix"]["branch"] = yaml_stabilization_branches + + # Commit the changes + message = f"Create the new version '{version}'" if version else "Update the supported versions" + if os.path.exists(".pre-commit-config.yaml"): + subprocess.run(["pre-commit", "run", "--color=never", "--all-files"], check=False) + subprocess.run(["git", "add", *modified_files], check=True) + subprocess.run(["git", "commit", f"--message={message}"], check=True) + + # Push it + subprocess.run( + [ + "git", + "push", + *(["--force"] if arguments.force else []), + repo.get("remote", "origin"), + branch_name, + ], + check=True, + ) + + # Create a pull request + url = mra.gh( + "pr", + "create", + f"--title={message}", + "--body=", + f"--head={branch_name}", + f"--base={repo.get('master_branch', 'master')}", + ).strip() + + # Go back to your old branch + subprocess.run(["git", "checkout", old_branch_name, "--"], check=True) + + if url: + subprocess.run([mra.get_browser(), url], check=True) + else: + mra.gh("browse") + + +if __name__ == "__main__": + main() diff --git a/tag_publish/package-lock.json b/tag_publish/package-lock.json new file mode 100644 index 0000000..f1557bc --- /dev/null +++ b/tag_publish/package-lock.json @@ -0,0 +1,437 @@ +{ + "name": "c2ccicheck", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "c2ccicheck", + "version": "1.0.0", + "dependencies": { + "snyk": "1.1294.0" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.119.2.tgz", + "integrity": "sha512-V2W+STWrafyGJhQv3ulMFXYDwWHiU6wHQAQBShsHVACiFaDrJ2kPRet38FKv4dMLlLlP2xN+ss2e5zv3tYlTiQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "7.119.2", + "@sentry/types": "7.119.2", + "@sentry/utils": "7.119.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.119.2.tgz", + "integrity": "sha512-hQr3d2yWq/2lMvoyBPOwXw1IHqTrCjOsU1vYKhAa6w9vGbJZFGhKGGE2KEi/92c3gqGn+gW/PC7cV6waCTDuVA==", + "license": "MIT", + "dependencies": { + "@sentry/types": "7.119.2", + "@sentry/utils": "7.119.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/integrations": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.119.2.tgz", + "integrity": "sha512-dCuXKvbUE3gXVVa696SYMjlhSP6CxpMH/gl4Jk26naEB8Xjsn98z/hqEoXLg6Nab73rjR9c/9AdKqBbwVMHyrQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "7.119.2", + "@sentry/types": "7.119.2", + "@sentry/utils": "7.119.2", + "localforage": "^1.8.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.119.2.tgz", + "integrity": "sha512-TPNnqxh+Myooe4jTyRiXrzrM2SH08R4+nrmBls4T7lKp2E5R/3mDSe/YTn5rRcUt1k1hPx1NgO/taG0DoS5cXA==", + "license": "MIT", + "dependencies": { + "@sentry-internal/tracing": "7.119.2", + "@sentry/core": "7.119.2", + "@sentry/integrations": "7.119.2", + "@sentry/types": "7.119.2", + "@sentry/utils": "7.119.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.119.2.tgz", + "integrity": "sha512-ydq1tWsdG7QW+yFaTp0gFaowMLNVikIqM70wxWNK+u98QzKnVY/3XTixxNLsUtnAB4Y+isAzFhrc6Vb5GFdFeg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.119.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.119.2.tgz", + "integrity": "sha512-TLdUCvcNgzKP0r9YD7tgCL1PEUp42TObISridsPJ5rhpVGQJvpr+Six0zIkfDUxerLYWZoK8QMm9KgFlPLNQzA==", + "license": "MIT", + "dependencies": { + "@sentry/types": "7.119.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snyk": { + "version": "1.1294.0", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1294.0.tgz", + "integrity": "sha512-4RBj3Lfccz5+6L2Kw9bt7icF+ex3antwt9PkSl2oEulI7mgqvc8VUFLnezg8c6PY60IPM9DrSSmNjXBac10I3Q==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@sentry/node": "^7.36.0", + "global-agent": "^3.0.0" + }, + "bin": { + "snyk": "bin/snyk" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tag_publish/package.json b/tag_publish/package.json new file mode 100644 index 0000000..001faa2 --- /dev/null +++ b/tag_publish/package.json @@ -0,0 +1,9 @@ +{ + "name": "c2ccicheck", + "version": "1.0.0", + "description": "", + "author": "", + "dependencies": { + "snyk": "1.1294.0" + } +} diff --git a/tag_publish/publish.py b/tag_publish/publish.py new file mode 100644 index 0000000..433e7ac --- /dev/null +++ b/tag_publish/publish.py @@ -0,0 +1,241 @@ +""" +The publishing functions. +""" + +import datetime +import glob +import os +import re +import subprocess # nosec +import sys + +import ruamel +import tomllib + +import tag_publish.configuration + + +def pip( + package: tag_publish.configuration.PublishPypiPackage, version: str, version_type: str, publish: bool +) -> bool: + """ + Publish to pypi. + + Arguments: + version: The version that will be published + version_type: Describe the kind of release we do: rebuild (specified using --type), version_tag, + version_branch, feature_branch, feature_tag (for pull request) + publish: If False only check the package + package: The package configuration + + """ + print(f"::group::{'Publishing' if publish else 'Checking'} '{package.get('path')}' to pypi") + sys.stdout.flush() + sys.stderr.flush() + + try: + env = {} + env["VERSION"] = version + env["VERSION_TYPE"] = version_type + full_repo = tag_publish.get_repository() + full_repo_split = full_repo.split("/") + master_branch, _ = tag_publish.get_master_branch(full_repo_split) + is_master = master_branch == version + env["IS_MASTER"] = "TRUE" if is_master else "FALSE" + + cwd = os.path.abspath(package.get("path", ".")) + + dist = os.path.join(cwd, "dist") + if not os.path.exists(dist): + os.mkdir(dist) + if os.path.exists(os.path.join(cwd, "setup.py")): + cmd = ["python3", "./setup.py", "egg_info", "--no-date"] + cmd += ( + ["--tag-build=dev" + datetime.datetime.now().strftime("%Y%m%d%H%M%S")] + if version_type in ("version_branch", "rebuild") + else [] + ) + cmd.append("bdist_wheel") + else: + if not os.path.exists(dist): + os.mkdir(dist) + cmd = ["pip", "wheel", "--no-deps", "--wheel-dir=dist", "."] + if os.path.exists(os.path.join(cwd, "pyproject.toml")): + use_poetry = False + if "build_command" not in package: + with open(os.path.join(cwd, "pyproject.toml"), "rb") as project_file: + pyproject = tomllib.load(project_file) + re_splitter = re.compile(r"[<>=]+") + for requirement in pyproject.get("build-system", {}).get("requires", []): + requirement_split = re_splitter.split(requirement) + if requirement_split[0] in ("poetry", "poetry-core"): + use_poetry = True + break + subprocess.run( + ["pip", "install", *pyproject.get("build-system", {}).get("requires", [])], check=True + ) + if use_poetry: + freeze = subprocess.run(["pip", "freeze"], check=True, stdout=subprocess.PIPE) + for freeze_line in freeze.stdout.decode("utf-8").split("\n"): + if freeze_line.startswith("poetry-") or freeze_line.startswith("poetry="): + print(freeze_line) + env_bash = " ".join([f"{key}={value}" for key, value in env.items()]) + print(f"Run in {cwd}: {env_bash} poetry build") + sys.stdout.flush() + sys.stderr.flush() + subprocess.run(["poetry", "build"], cwd=cwd, env={**os.environ, **env}, check=True) + cmd = [] + if cmd: + cmd = package.get("build_command", cmd) + subprocess.check_call(cmd, cwd=cwd, env=env) + cmd = ["twine"] + cmd += ["upload", "--verbose", "--disable-progress-bar"] if publish else ["check"] + cmd += glob.glob(os.path.join(cwd, "dist/*.whl")) + cmd += glob.glob(os.path.join(cwd, "dist/*.tar.gz")) + subprocess.check_call(cmd) + print("::endgroup::") + except subprocess.CalledProcessError as exception: + print(f"Error: {exception}") + print("::endgroup::") + print("::error::With error") + return False + return True + + +def docker( + config: tag_publish.configuration.PublishDockerRepository, + name: str, + image_config: tag_publish.configuration.PublishDockerImage, + tag_src: str, + dst_tags: list[str], + images_full: list[str], +) -> bool: + """ + Publish to a Docker registry. + + config is like: + server: # The server fqdn + + image_config is like: + name: # The image name + + Arguments: + config: The publishing config + name: The repository name, just used to print messages + image_config: The image config + tag_src: The source tag (usually latest) + dst_tags: Publish using the provided tags + images_full: The list of published images (with tag), used to build the dispatch event + + """ + print( + f"::group::Publishing {image_config['name']} to the server {name} " + f"using the tags {', '.join(dst_tags)}" + ) + sys.stdout.flush() + sys.stderr.flush() + + try: + new_images_full = [] + if "server" in config: + for tag in dst_tags: + subprocess.run( + [ + "docker", + "tag", + f"{image_config['name']}:{tag_src}", + f"{config['server']}/{image_config['name']}:{tag}", + ], + check=True, + ) + new_images_full.append(f"{config['server']}/{image_config['name']}:{tag}") + else: + for tag in dst_tags: + if tag_src != tag: + subprocess.run( + [ + "docker", + "tag", + f"{image_config['name']}:{tag_src}", + f"{image_config['name']}:{tag}", + ], + check=True, + ) + new_images_full.append(f"{image_config['name']}:{tag}") + + for image in new_images_full: + subprocess.run(["docker", "push", image], check=True) + images_full += new_images_full + + print("::endgroup::") + except subprocess.CalledProcessError as exception: + print(f"Error: {exception}") + print("::endgroup::") + print("::error::With error") + return False + return True + + +def helm(folder: str, version: str, owner: str, repo: str, commit_sha: str, token: str) -> bool: + """ + Publish to pypi. + + Arguments: + folder: The folder to be published + version: The version that will be published + owner: The GitHub repository owner + repo: The GitHub repository name + commit_sha: The sha of the current commit + token: The GitHub token + + """ + print(f"::group::Publishing Helm chart from '{folder}' to GitHub release") + sys.stdout.flush() + sys.stderr.flush() + + try: + yaml_ = ruamel.yaml.YAML() + with open(os.path.join(folder, "Chart.yaml"), encoding="utf-8") as open_file: + chart = yaml_.load(open_file) + chart["version"] = version + with open(os.path.join(folder, "Chart.yaml"), "w", encoding="utf-8") as open_file: + yaml_.dump(chart, open_file) + for index, dependency in enumerate(chart.get("dependencies", [])): + if dependency["repository"].startswith("https://"): + subprocess.run(["helm", "repo", "add", str(index), dependency["repository"]], check=True) + + subprocess.run(["cr", "package", folder], check=True) + subprocess.run( + [ + "cr", + "upload", + f"--owner={owner}", + f"--git-repo={repo}", + f"--commit={commit_sha}", + "--release-name-template={{ .Version }}", + f"--token={token}", + ], + check=True, + ) + if not os.path.exists(".cr-index"): + os.mkdir(".cr-index") + subprocess.run( + [ + "cr", + "index", + f"--owner={owner}", + f"--git-repo={repo}", + f"--charts-repo=https://{owner}.github.io/{repo}", + "--push", + "--release-name-template={{ .Version }}", + f"--token={token}", + ], + check=True, + ) + print("::endgroup::") + except subprocess.CalledProcessError as exception: + print(f"Error: {exception}") + print("::endgroup::") + print("::error::With error") + return False + return True diff --git a/tag_publish/py.typed b/tag_publish/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tag_publish/schema.json b/tag_publish/schema.json new file mode 100644 index 0000000..0e827aa --- /dev/null +++ b/tag_publish/schema.json @@ -0,0 +1,264 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://raw.githubusercontent.com/camptocamp/tilecloud-chain/master/tilecloud_chain/schema.json", + "type": "object", + "title": "configuration", + "description": "Tag Publish configuration file", + "additionalProperties": false, + "definitions": { + "publish_docker": { + "title": "Publish Docker", + "description": "The configuration used to publish on Docker", + "oneOf": [ + { + "title": "Publish Docker config", + "description": "The configuration used to publish on Docker", + "type": "object", + "properties": { + "latest": { + "description": "Publish the latest version on tag latest", + "title": "Publish Docker latest", + "default": true, + "type": "boolean" + }, + "images": { + "description": "List of images to be published", + "type": "array", + "items": { + "title": "Publish Docker image", + "type": "object", + "properties": { + "group": { + "description": "The image is in the group, should be used with the --group option of tag-publish script", + "title": "Publish Docker image group", + "default": "default", + "type": "string" + }, + "name": { + "description": "The image name", + "type": "string" + }, + "tags": { + "description": "The tag name, will be formatted with the version=, the image with version=latest should be present when we call the tag-publish script", + "title": "publish docker image tags", + "type": "array", + "default": ["{version}"], + "items": { + "type": "string" + } + } + } + } + }, + "repository": { + "title": "Docker repository", + "description": "The repository where we should publish the images", + "default": { + "github": { + "server": "ghcr.io", + "versions": ["version_tag", "version_branch", "rebuild"] + }, + "dockerhub": {} + }, + "type": "object", + "additionalProperties": { + "title": "Publish Docker repository", + "type": "object", + "properties": { + "server": { + "description": "The server URL", + "type": "string" + }, + "versions": { + "description": "The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script", + "title": "Publish Docker repository versions", + "type": "array", + "default": ["version_tag", "version_branch", "rebuild", "feature_branch"], + "items": { + "type": "string" + } + } + } + } + }, + "dispatch": { + "description": "Send a dispatch event to an other repository", + "default": {}, + "oneOf": [ + { + "type": "object", + "title": "dispatch config", + "description": "Send a dispatch event to an other repository", + "properties": { + "repository": { + "title": "Docker dispatch repository", + "description": "The repository name to be triggered", + "default": "camptocamp/argocd-gs-gmf-apps", + "type": "string" + }, + "event-type": { + "title": "Docker dispatch event type", + "description": "The event type to be triggered", + "default": "image-update", + "type": "string" + } + } + }, + { "const": false } + ] + }, + "snyk": { + "description": "Checks the published images with Snyk", + "type": "object", + "properties": { + "monitor_args": { + "description": "The arguments to pass to the Snyk container monitor command", + "title": "Publish Docker Snyk monitor args", + "default": ["--app-vulns"], + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { "const": false } + ] + }, + "test_args": { + "description": "The arguments to pass to the Snyk container test command", + "title": "Publish Docker Snyk test args", + "default": ["--app-vulns", "--severity-threshold=critical"], + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { "const": false } + ] + } + } + } + } + }, + { "const": false } + ] + }, + "publish_pypi": { + "title": "publish pypi", + "description": "Configuration to publish on pypi", + "default": {}, + "oneOf": [ + { + "title": "publish pypi config", + "description": "Configuration to publish on pypi", + "type": "object", + "properties": { + "packages": { + "description": "The configuration of packages that will be published", + "type": "array", + "items": { + "title": "publish pypi package", + "description": "The configuration of package that will be published", + "type": "object", + "properties": { + "group": { + "description": "The image is in the group, should be used with the --group option of tag-publish script", + "title": "Publish pip package group", + "default": "default", + "type": "string" + }, + "path": { + "description": "The path of the pypi package", + "type": "string" + }, + "build_command": { + "description": "The command used to do the build", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "versions": { + "description": "The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "const": false + } + ] + }, + "publish_helm": { + "title": "publish helm", + "description": "Configuration to publish Helm charts on GitHub release", + "oneOf": [ + { + "title": "publish helm config", + "description": "Configuration to publish on Helm charts on GitHub release", + "type": "object", + "properties": { + "folders": { + "description": "The folders that will be published", + "type": "array", + "items": { + "type": "string" + } + }, + "versions": { + "description": "The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "const": false + } + ] + }, + "version_transform": { + "title": "Version transform", + "description": "A version transformer definition", + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "description": "The from regular expression", + "type": "string" + }, + "to": { + "description": "The expand regular expression: https://docs.python.org/3/library/re.html#re.Match.expand", + "type": "string" + } + } + } + } + }, + "properties": { + "version": { + "title": "Version", + "description": "The version configurations", + "type": "object", + "properties": { + "branch_to_version_re": { "$ref": "#/definitions/version_transform" }, + "tag_to_version_re": { "$ref": "#/definitions/version_transform" } + } + }, + "docker": { "$ref": "#/definitions/publish_docker" }, + "pypi": { "$ref": "#/definitions/publish_pypi" }, + "helm": { "$ref": "#/definitions/publish_helm" } + } +}